Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormkuppe2009-02-18 15:39:00 +0000
committermkuppe2009-02-18 15:39:00 +0000
commit8fb07112c1349f4b83d2287106178b112d87fe41 (patch)
treef7789970d5408ec42e6720bd25e7648627f4926a /providers
parent1fb78c1b420a8fc532f5f5990f4bf6204d408f7e (diff)
downloadorg.eclipse.ecf-8fb07112c1349f4b83d2287106178b112d87fe41.tar.gz
org.eclipse.ecf-8fb07112c1349f4b83d2287106178b112d87fe41.tar.xz
org.eclipse.ecf-8fb07112c1349f4b83d2287106178b112d87fe41.zip
NEW - bug 265312: [Discovery][JMDNS] Replace binary jar with sources
https://bugs.eclipse.org/bugs/show_bug.cgi?id=265312
Diffstat (limited to 'providers')
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/.classpath2
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/META-INF/MANIFEST.MF3
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/build.properties3
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/JmDNS.java179
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceEvent.java43
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceInfo.java168
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceListener.java42
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceTypeListener.java24
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSCache.java252
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSConstants.java125
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSEntry.java148
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSIncoming.java530
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSListener.java24
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSOutgoing.java394
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSQuestion.java44
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSRecord.java742
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSState.java111
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/HostInfo.java172
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/JmDNSImpl.java1563
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/ServiceEventImpl.java99
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/ServiceInfoImpl.java668
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/SocketListener.java87
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Announcer.java159
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Canceler.java117
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Prober.java197
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/RecordReaper.java83
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Renewer.java154
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Responder.java291
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/ServiceInfoResolver.java101
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/ServiceResolver.java101
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/TypeResolver.java92
-rw-r--r--providers/bundles/org.eclipse.ecf.provider.jmdns/lib/jmdns.jarbin90934 -> 0 bytes
32 files changed, 6714 insertions, 4 deletions
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/.classpath b/providers/bundles/org.eclipse.ecf.provider.jmdns/.classpath
index 768caf2b2..ef967cb93 100644
--- a/providers/bundles/org.eclipse.ecf.provider.jmdns/.classpath
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/.classpath
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
- <classpathentry exported="true" kind="lib" path="lib/jmdns.jar" sourcepath="/jmdns"/>
+ <classpathentry kind="src" path="jmdns"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/CDC-1.1%Foundation-1.1"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="output" path="bin"/>
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/META-INF/MANIFEST.MF b/providers/bundles/org.eclipse.ecf.provider.jmdns/META-INF/MANIFEST.MF
index d47d4ee5d..f3c37e80a 100644
--- a/providers/bundles/org.eclipse.ecf.provider.jmdns/META-INF/MANIFEST.MF
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/META-INF/MANIFEST.MF
@@ -10,8 +10,7 @@ Require-Bundle: org.eclipse.equinox.common,
org.eclipse.ecf,
org.eclipse.ecf.discovery
Eclipse-LazyStart: true
-Bundle-ClassPath: .,
- lib/jmdns.jar
+Bundle-ClassPath: .
Bundle-RequiredExecutionEnvironment: CDC-1.1/Foundation-1.1,
J2SE-1.4
Export-Package: org.eclipse.ecf.internal.provider.jmdns;x-internal:=true,
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/build.properties b/providers/bundles/org.eclipse.ecf.provider.jmdns/build.properties
index aa2c9e258..cc9bb512d 100644
--- a/providers/bundles/org.eclipse.ecf.provider.jmdns/build.properties
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/build.properties
@@ -14,5 +14,6 @@ src.includes = about.html,\
lib/,\
NOTICE.txt
output.jmdns.jar = bin/
-source.. = src/
+source.. = src/,\
+ jmdns/
output.. = bin/
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/JmDNS.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/JmDNS.java
new file mode 100644
index 000000000..e0b6c822e
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/JmDNS.java
@@ -0,0 +1,179 @@
+///Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+
+package javax.jmdns;
+
+import java.io.IOException;
+import java.net.InetAddress;
+
+import javax.jmdns.impl.JmDNSImpl;
+
+/**
+ * mDNS implementation in Java.
+ *
+ * @version %I%, %G%
+ * @author Arthur van Hoff, Rick Blair, Jeff Sonstein,
+ * Werner Randelshofer, Pierre Frisch, Scott Lewis, Scott Cytacki
+ */
+public abstract class JmDNS
+{
+ /**
+ * The version of JmDNS.
+ */
+ public static String VERSION = "2.0";
+
+ /**
+ * Create an instance of JmDNS.
+ */
+ public static JmDNS create() throws IOException
+ {
+ return new JmDNSImpl();
+ }
+
+ /**
+ * Create an instance of JmDNS and bind it to a
+ * specific network interface given its IP-address.
+ */
+ public static JmDNS create(InetAddress addr) throws IOException
+ {
+ return new JmDNSImpl(addr);
+ }
+
+ /**
+ * Return the HostName associated with this JmDNS instance.
+ * Note: May not be the same as what started. The host name is subject to
+ * negotiation.
+ */
+ public abstract String getHostName();
+
+ /**
+ * Return the address of the interface to which this instance of JmDNS is
+ * bound.
+ */
+ public abstract InetAddress getInterface() throws IOException;
+
+ /**
+ * Get service information. If the information is not cached, the method
+ * will block until updated information is received.
+ * <p/>
+ * Usage note: Do not call this method from the AWT event dispatcher thread.
+ * You will make the user interface unresponsive.
+ *
+ * @param type fully qualified service type, such as <code>_http._tcp.local.</code> .
+ * @param name unqualified service name, such as <code>foobar</code> .
+ * @return null if the service information cannot be obtained
+ */
+ public abstract ServiceInfo getServiceInfo(String type, String name);
+
+ /**
+ * Get service information. If the information is not cached, the method
+ * will block for the given timeout until updated information is received.
+ * <p/>
+ * Usage note: If you call this method from the AWT event dispatcher thread,
+ * use a small timeout, or you will make the user interface unresponsive.
+ *
+ * @param type full qualified service type, such as <code>_http._tcp.local.</code> .
+ * @param name unqualified service name, such as <code>foobar</code> .
+ * @param timeout timeout in milliseconds
+ * @return null if the service information cannot be obtained
+ */
+ public abstract ServiceInfo getServiceInfo(String type, String name, int timeout);
+
+ /**
+ * Request service information. The information about the service is
+ * requested and the ServiceListener.resolveService method is called as soon
+ * as it is available.
+ * <p/>
+ * Usage note: Do not call this method from the AWT event dispatcher thread.
+ * You will make the user interface unresponsive.
+ *
+ * @param type full qualified service type, such as <code>_http._tcp.local.</code> .
+ * @param name unqualified service name, such as <code>foobar</code> .
+ */
+ public abstract void requestServiceInfo(String type, String name);
+
+ /**
+ * Request service information. The information about the service is requested
+ * and the ServiceListener.resolveService method is called as soon as it is available.
+ *
+ * @param type full qualified service type, such as <code>_http._tcp.local.</code> .
+ * @param name unqualified service name, such as <code>foobar</code> .
+ * @param timeout timeout in milliseconds
+ */
+ public abstract void requestServiceInfo(String type, String name, int timeout);
+
+ /**
+ * Listen for service types.
+ *
+ * @param listener listener for service types
+ */
+ public abstract void addServiceTypeListener(ServiceTypeListener listener) throws IOException;
+
+ /**
+ * Remove listener for service types.
+ *
+ * @param listener listener for service types
+ */
+ public abstract void removeServiceTypeListener(ServiceTypeListener listener);
+
+ /**
+ * Listen for services of a given type. The type has to be a fully qualified
+ * type name such as <code>_http._tcp.local.</code>.
+ *
+ * @param type full qualified service type, such as <code>_http._tcp.local.</code>.
+ * @param listener listener for service updates
+ */
+ public abstract void addServiceListener(String type, ServiceListener listener);
+
+ /**
+ * Remove listener for services of a given type.
+ *
+ * @param listener listener for service updates
+ */
+ public abstract void removeServiceListener(String type, ServiceListener listener);
+
+ /**
+ * Register a service. The service is registered for access by other jmdns clients.
+ * The name of the service may be changed to make it unique.
+ */
+ public abstract void registerService(ServiceInfo info) throws IOException;
+
+ /**
+ * Unregister a service. The service should have been registered.
+ */
+ public abstract void unregisterService(ServiceInfo info);
+
+ /**
+ * Unregister all services.
+ */
+ public abstract void unregisterAllServices();
+
+ /**
+ * Register a service type. If this service type was not already known,
+ * all service listeners will be notified of the new service type. Service types
+ * are automatically registered as they are discovered.
+ */
+ public abstract void registerServiceType(String type);
+
+ /**
+ * Close down jmdns. Release all resources and unregister all services.
+ */
+ public abstract void close();
+
+ /**
+ * List Services and serviceTypes.
+ * Debugging Only
+ */
+ public abstract void printServices();
+
+ /**
+ * Returns a list of service infos of the specified type.
+ *
+ * @param type Service type name, such as <code>_http._tcp.local.</code>.
+ * @return An array of service instance names.
+ */
+ public abstract ServiceInfo[] list(String type);
+
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceEvent.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceEvent.java
new file mode 100644
index 000000000..3d3807efb
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceEvent.java
@@ -0,0 +1,43 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns;
+
+import java.util.EventObject;
+
+public abstract class ServiceEvent extends EventObject
+{
+
+ public ServiceEvent(Object source)
+ {
+ super(source);
+ // TODO Auto-generated constructor stub
+ }
+
+ /**
+ * Returns the JmDNS instance which originated the event.
+ */
+ public abstract JmDNS getDNS();
+
+ /**
+ * Returns the fully qualified type of the service.
+ */
+ public abstract String getType();
+
+ /**
+ * Returns the instance name of the service.
+ * Always returns null, if the event is sent to a service type listener.
+ */
+ public abstract String getName();
+
+ /**
+ * Returns the service info record, or null if the service could not be
+ * resolved.
+ * Always returns null, if the event is sent to a service type listener.
+ */
+ /**
+ * @see javax.jmdns.ServiceEvent#getInfo()
+ */
+ public abstract ServiceInfo getInfo();
+} \ No newline at end of file
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceInfo.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceInfo.java
new file mode 100644
index 000000000..9d4763e2d
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceInfo.java
@@ -0,0 +1,168 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+package javax.jmdns;
+
+import java.net.InetAddress;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import javax.jmdns.impl.ServiceInfoImpl;
+
+public abstract class ServiceInfo
+{
+ public final static byte[] NO_VALUE = new byte[0];
+
+ /**
+ * Construct a service description for registrating with JmDNS.
+ *
+ * @param type fully qualified service type name, such as <code>_http._tcp.local.</code>.
+ * @param name unqualified service instance name, such as <code>foobar</code>
+ * @param port the local port on which the service runs
+ * @param text string describing the service
+ */
+ public static ServiceInfo create(String type, String name, int port, String text)
+ {
+ return new ServiceInfoImpl(type, name, port, text);
+ }
+
+ /**
+ * Construct a service description for registrating with JmDNS.
+ *
+ * @param type fully qualified service type name, such as <code>_http._tcp.local.</code>.
+ * @param name unqualified service instance name, such as <code>foobar</code>
+ * @param port the local port on which the service runs
+ * @param weight weight of the service
+ * @param priority priority of the service
+ * @param text string describing the service
+ */
+ public static ServiceInfo create(String type, String name, int port, int weight, int priority, String text)
+ {
+ return new ServiceInfoImpl(type, name, port, weight, priority, text);
+ }
+
+ /**
+ * Construct a service description for registrating with JmDNS. The properties hashtable must
+ * map property names to either Strings or byte arrays describing the property values.
+ *
+ * @param type fully qualified service type name, such as <code>_http._tcp.local.</code>.
+ * @param name unqualified service instance name, such as <code>foobar</code>
+ * @param port the local port on which the service runs
+ * @param weight weight of the service
+ * @param priority priority of the service
+ * @param props properties describing the service
+ */
+ public static ServiceInfo create(String type, String name, int port, int weight, int priority, Hashtable props)
+ {
+ return new ServiceInfoImpl(type, name, port, weight, priority, props);
+ }
+
+ /**
+ * Construct a service description for registrating with JmDNS.
+ *
+ * @param type fully qualified service type name, such as <code>_http._tcp.local.</code>.
+ * @param name unqualified service instance name, such as <code>foobar</code>
+ * @param port the local port on which the service runs
+ * @param weight weight of the service
+ * @param priority priority of the service
+ * @param text bytes describing the service
+ */
+ public static ServiceInfo create(String type, String name, int port, int weight, int priority, byte text[])
+ {
+ return new ServiceInfoImpl(type, name, port, weight, priority, text);
+ }
+
+ /**
+ * Fully qualified service type name, such as <code>_http._tcp.local.</code> .
+ */
+ public abstract String getType();
+
+ /**
+ * Unqualified service instance name, such as <code>foobar</code> .
+ */
+ public abstract String getName();
+
+ /**
+ * Fully qualified service name, such as <code>foobar._http._tcp.local.</code> .
+ */
+ public abstract String getQualifiedName();
+
+ /**
+ * Get the name of the server.
+ */
+ public abstract String getServer();
+
+ /**
+ * Get the host address of the service (ie X.X.X.X).
+ */
+ public abstract String getHostAddress();
+
+ public abstract InetAddress getAddress();
+
+ /**
+ * Get the InetAddress of the service.
+ */
+ public abstract InetAddress getInetAddress();
+
+ /**
+ * Get the port for the service.
+ */
+ public abstract int getPort();
+
+ /**
+ * Get the priority of the service.
+ */
+ public abstract int getPriority();
+
+ /**
+ * Get the weight of the service.
+ */
+ public abstract int getWeight();
+
+ /**
+ * Get the text for the serivce as raw bytes.
+ */
+ public abstract byte[] getTextBytes();
+
+ /**
+ * Get the text for the service. This will interpret the text bytes
+ * as a UTF8 encoded string. Will return null if the bytes are not
+ * a valid UTF8 encoded string.
+ */
+ public abstract String getTextString();
+
+ /**
+ * Get the URL for this service. An http URL is created by
+ * combining the address, port, and path properties.
+ */
+ public abstract String getURL();
+
+ /**
+ * Get the URL for this service. An URL is created by
+ * combining the protocol, address, port, and path properties.
+ */
+ public abstract String getURL(String protocol);
+
+ /**
+ * Get a property of the service. This involves decoding the
+ * text bytes into a property list. Returns null if the property
+ * is not found or the text data could not be decoded correctly.
+ */
+ public abstract byte[] getPropertyBytes(String name);
+
+ /**
+ * Get a property of the service. This involves decoding the
+ * text bytes into a property list. Returns null if the property
+ * is not found, the text data could not be decoded correctly, or
+ * the resulting bytes are not a valid UTF8 string.
+ */
+ public abstract String getPropertyString(String name);
+
+ /**
+ * Enumeration of the property names.
+ */
+ public abstract Enumeration getPropertyNames();
+
+ public abstract String getNiceTextString();
+
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceListener.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceListener.java
new file mode 100644
index 000000000..264aee730
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceListener.java
@@ -0,0 +1,42 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns;
+
+import java.util.EventListener;
+
+/**
+ * Listener for service updates.
+ *
+ * @version %I%, %G%
+ * @author Arthur van Hoff, Werner Randelshofer
+ */
+public interface ServiceListener extends EventListener
+{
+ /**
+ * A service has been added.
+ *
+ * @param event The ServiceEvent providing the name and fully qualified type
+ * of the service.
+ */
+ void serviceAdded(ServiceEvent event);
+
+ /**
+ * A service has been removed.
+ *
+ * @param event The ServiceEvent providing the name and fully qualified type
+ * of the service.
+ */
+ void serviceRemoved(ServiceEvent event);
+
+ /**
+ * A service has been resolved. Its details are now available in the
+ * ServiceInfo record.
+ *
+ * @param event The ServiceEvent providing the name, the fully qualified
+ * type of the service, and the service info record, or null if the service
+ * could not be resolved.
+ */
+ void serviceResolved(ServiceEvent event);
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceTypeListener.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceTypeListener.java
new file mode 100644
index 000000000..a3d1c835f
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/ServiceTypeListener.java
@@ -0,0 +1,24 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns;
+
+import java.util.EventListener;
+
+/**
+ * Listener for service types.
+ *
+ * @version %I%, %G%
+ * @author Arthur van Hoff, Werner Randelshofer
+ */
+public interface ServiceTypeListener extends EventListener
+{
+ /**
+ * A new service type was discovered.
+ *
+ * @param event The service event providing the fully qualified type of
+ * the service.
+ */
+ void serviceTypeAdded(ServiceEvent event);
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSCache.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSCache.java
new file mode 100644
index 000000000..6496cfea4
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSCache.java
@@ -0,0 +1,252 @@
+//Copyright 2003-2005 Arthur van Hoff Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.logging.Logger;
+
+/**
+ * A table of DNS entries. This is a hash table which can handle multiple
+ * entries with the same name. <p/> Storing multiple entries with the same name
+ * is implemented using a linked list of <code>CacheNode</code>'s. <p/> The
+ * current implementation of the API of DNSCache does expose the cache nodes to
+ * clients. Clients must explicitly deal with the nodes when iterating over
+ * entries in the cache. Here's how to iterate over all entries in the cache:
+ *
+ * <pre>
+ * for (Iterator i=dnscache.iterator(); i.hasNext(); ) {
+ * for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n.next()) {
+ * DNSEntry entry = n.getValue();
+ * ...do something with entry...
+ * }
+ * }
+ * </pre>
+ *
+ * <p/> And here's how to iterate over all entries having a given name:
+ *
+ * <pre>
+ * for (DNSCache.CacheNode n = (DNSCache.CacheNode) dnscache.find(name); n != null; n.next()) {
+ * DNSEntry entry = n.getValue();
+ * ...do something with entry...
+ * }
+ * </pre>
+ *
+ * @version %I%, %G%
+ * @author Arthur van Hoff, Werner Randelshofer, Rick Blair
+ */
+public class DNSCache
+{
+ private static Logger logger = Logger.getLogger(DNSCache.class.getName());
+ // Implementation note:
+ // We might completely hide the existence of CacheNode's in a future version
+ // of DNSCache. But this will require to implement two (inner) classes for
+ // the iterators that will be returned by method <code>iterator()</code> and
+ // method <code>find(name)</code>.
+ // Since DNSCache is not a public class, it does not seem worth the effort
+ // to clean its API up that much.
+
+ // [PJYF Oct 15 2004] This should implements Collections that would be amuch
+ // cleaner implementation
+
+ /**
+ * The number of DNSEntry's in the cache.
+ */
+ private int size;
+
+ /**
+ * The hashtable used internally to store the entries of the cache. Keys are
+ * instances of String. The String contains an unqualified service name.
+ * Values are linked lists of CacheNode instances.
+ */
+ private final HashMap hashtable;
+
+ /**
+ * Cache nodes are used to implement storage of multiple DNSEntry's of the
+ * same name in the cache.
+ */
+ public static class CacheNode
+ {
+ private static Logger logger = Logger.getLogger(CacheNode.class.getName());
+ private final DNSEntry value;
+ private CacheNode next;
+
+ public CacheNode(DNSEntry value)
+ {
+ this.value = value;
+ }
+
+ public CacheNode next()
+ {
+ return next;
+ }
+
+ public DNSEntry getValue()
+ {
+ return value;
+ }
+ }
+
+ /**
+ * Create a table with a given initial size.
+ */
+ public DNSCache(final int size)
+ {
+ hashtable = new HashMap(size);
+ }
+
+ /**
+ * Clears the cache.
+ */
+ public synchronized void clear()
+ {
+ hashtable.clear();
+ size = 0;
+ }
+
+ /**
+ * Adds an entry to the table.
+ */
+ public synchronized void add(final DNSEntry entry)
+ {
+ // logger.log("DNSCache.add("+entry.getName()+")");
+ final CacheNode newValue = new CacheNode(entry);
+ final CacheNode node = (CacheNode) hashtable.get(entry.getName());
+ if (node == null)
+ {
+ hashtable.put(entry.getName(), newValue);
+ }
+ else
+ {
+ newValue.next = node.next;
+ node.next = newValue;
+ }
+ size++;
+ }
+
+ /**
+ * Remove a specific entry from the table. Returns true if the entry was
+ * found.
+ */
+ public synchronized boolean remove(DNSEntry entry)
+ {
+ CacheNode node = (CacheNode) hashtable.get(entry.getName());
+ if (node != null)
+ {
+ if (node.value == entry)
+ {
+ if (node.next == null)
+ {
+ hashtable.remove(entry.getName());
+ }
+ else
+ {
+ hashtable.put(entry.getName(), node.next);
+ }
+ size--;
+ return true;
+ }
+
+ CacheNode previous = node;
+ node = node.next;
+ while (node != null)
+ {
+ if (node.value == entry)
+ {
+ previous.next = node.next;
+ size--;
+ return true;
+ }
+ previous = node;
+ node = node.next;
+ }
+ ;
+ }
+ return false;
+ }
+
+ /**
+ * Get a matching DNS entry from the table (using equals). Returns the entry
+ * that was found.
+ */
+ public synchronized DNSEntry get(DNSEntry entry)
+ {
+ for (CacheNode node = find(entry.getName()); node != null; node = node.next)
+ {
+ if (node.value.equals(entry))
+ {
+ return node.value;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get a matching DNS entry from the table.
+ */
+ public synchronized DNSEntry get(String name, int type, int clazz)
+ {
+ for (CacheNode node = find(name); node != null; node = node.next)
+ {
+ if (node.value.type == type && node.value.clazz == clazz)
+ {
+ return node.value;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Iterates over all cache nodes. The iterator returns instances of
+ * DNSCache.CacheNode. Each instance returned is the first node of a linked
+ * list. To retrieve all entries, one must iterate over this linked list.
+ * See code snippets in the header of the class.
+ */
+ public synchronized Iterator iterator()
+ {
+ return new ArrayList(hashtable.values()).iterator();
+ }
+
+ /**
+ * Iterate only over items with matching name. Returns an instance of
+ * DNSCache.CacheNode or null. If an instance is returned, it is the first
+ * node of a linked list. To retrieve all entries, one must iterate over
+ * this linked list.
+ */
+ public synchronized CacheNode find(String name)
+ {
+ return (CacheNode) hashtable.get(name);
+ }
+
+ /**
+ * List all entries for debugging.
+ */
+ public synchronized void print()
+ {
+ for (final Iterator i = iterator(); i.hasNext();)
+ {
+ for (CacheNode n = (CacheNode) i.next(); n != null; n = n.next)
+ {
+ System.out.println(n.value);
+ }
+ }
+ }
+
+ public synchronized String toString()
+ {
+ final StringBuffer aLog = new StringBuffer();
+ aLog.append("\t---- cache ----");
+ for (final Iterator i = iterator(); i.hasNext();)
+ {
+ for (CacheNode n = (CacheNode) i.next(); n != null; n = n.next)
+ {
+ aLog.append("\n\t\t" + n.value);
+ }
+ }
+ return aLog.toString();
+ }
+
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSConstants.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSConstants.java
new file mode 100644
index 000000000..b87012947
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSConstants.java
@@ -0,0 +1,125 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+
+package javax.jmdns.impl;
+
+/**
+ * DNS constants.
+ *
+ * @version %I%, %G%
+ * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer, Pierre Frisch, Rick Blair
+ */
+public final class DNSConstants
+{
+
+ // changed to final class - jeffs
+ public final static String MDNS_GROUP = "224.0.0.251";
+ public final static String MDNS_GROUP_IPV6 = "FF02::FB";
+ public final static int MDNS_PORT = 5353;
+ public final static int DNS_PORT = 53;
+ public final static int DNS_TTL = 60 * 60; // default one hour TTL
+ // public final static int DNS_TTL = 120 * 60; // two hour TTL (draft-cheshire-dnsext-multicastdns.txt ch 13)
+
+ public final static int MAX_MSG_TYPICAL = 1460;
+ public final static int MAX_MSG_ABSOLUTE = 8972;
+
+ public final static int FLAGS_QR_MASK = 0x8000; // Query response mask
+ public final static int FLAGS_QR_QUERY = 0x0000; // Query
+ public final static int FLAGS_QR_RESPONSE = 0x8000; // Response
+
+ public final static int FLAGS_AA = 0x0400; // Authorative answer
+ public final static int FLAGS_TC = 0x0200; // Truncated
+ public final static int FLAGS_RD = 0x0100; // Recursion desired
+ public final static int FLAGS_RA = 0x8000; // Recursion available
+
+ public final static int FLAGS_Z = 0x0040; // Zero
+ public final static int FLAGS_AD = 0x0020; // Authentic data
+ public final static int FLAGS_CD = 0x0010; // Checking disabled
+
+ public final static int CLASS_IN = 1; // public final static Internet
+ public final static int CLASS_CS = 2; // CSNET
+ public final static int CLASS_CH = 3; // CHAOS
+ public final static int CLASS_HS = 4; // Hesiod
+ public final static int CLASS_NONE = 254; // Used in DNS UPDATE [RFC 2136]
+ public final static int CLASS_ANY = 255; // Not a DNS class, but a DNS query class, meaning "all classes"
+ public final static int CLASS_MASK = 0x7FFF; // Multicast DNS uses the bottom 15 bits to identify the record class...
+ public final static int CLASS_UNIQUE = 0x8000; // ... and the top bit indicates that all other cached records are now invalid
+
+ public final static int TYPE_IGNORE = 0; // This is a hack to stop further processing
+ public final static int TYPE_A = 1; // Address
+ public final static int TYPE_NS = 2; // Name Server
+ public final static int TYPE_MD = 3; // Mail Destination
+ public final static int TYPE_MF = 4; // Mail Forwarder
+ public final static int TYPE_CNAME = 5; // Canonical Name
+ public final static int TYPE_SOA = 6; // Start of Authority
+ public final static int TYPE_MB = 7; // Mailbox
+ public final static int TYPE_MG = 8; // Mail Group
+ public final static int TYPE_MR = 9; // Mail Rename
+ public final static int TYPE_NULL = 10; // NULL RR
+ public final static int TYPE_WKS = 11; // Well-known-service
+ public final static int TYPE_PTR = 12; // Domain Name popublic final static inter
+ public final static int TYPE_HINFO = 13; // Host information
+ public final static int TYPE_MINFO = 14; // Mailbox information
+ public final static int TYPE_MX = 15; // Mail exchanger
+ public final static int TYPE_TXT = 16; // Arbitrary text string
+ public final static int TYPE_RP = 17; // for Responsible Person [RFC1183]
+ public final static int TYPE_AFSDB = 18; // for AFS Data Base location [RFC1183]
+ public final static int TYPE_X25 = 19; // for X.25 PSDN address [RFC1183]
+ public final static int TYPE_ISDN = 20; // for ISDN address [RFC1183]
+ public final static int TYPE_RT = 21; // for Route Through [RFC1183]
+ public final static int TYPE_NSAP = 22; // for NSAP address, NSAP style A record [RFC1706]
+ public final static int TYPE_NSAP_PTR = 23; //
+ public final static int TYPE_SIG = 24; // for security signature [RFC2931]
+ public final static int TYPE_KEY = 25; // for security key [RFC2535]
+ public final static int TYPE_PX = 26; // X.400 mail mapping information [RFC2163]
+ public final static int TYPE_GPOS = 27; // Geographical Position [RFC1712]
+ public final static int TYPE_AAAA = 28; // IP6 Address [Thomson]
+ public final static int TYPE_LOC = 29; // Location Information [Vixie]
+ public final static int TYPE_NXT = 30; // Next Domain - OBSOLETE [RFC2535, RFC3755]
+ public final static int TYPE_EID = 31; // Endpoint Identifier [Patton]
+ public final static int TYPE_NIMLOC = 32; // Nimrod Locator [Patton]
+ public final static int TYPE_SRV = 33; // Server Selection [RFC2782]
+ public final static int TYPE_ATMA = 34; // ATM Address [Dobrowski]
+ public final static int TYPE_NAPTR = 35; // Naming Authority Pointer [RFC2168, RFC2915]
+ public final static int TYPE_KX = 36; // Key Exchanger [RFC2230]
+ public final static int TYPE_CERT = 37; // CERT [RFC2538]
+ public final static int TYPE_A6 = 38; // A6 [RFC2874]
+ public final static int TYPE_DNAME = 39; // DNAME [RFC2672]
+ public final static int TYPE_SINK = 40; // SINK [Eastlake]
+ public final static int TYPE_OPT = 41; // OPT [RFC2671]
+ public final static int TYPE_APL = 42; // APL [RFC3123]
+ public final static int TYPE_DS = 43; // Delegation Signer [RFC3658]
+ public final static int TYPE_SSHFP = 44; // SSH Key Fingerprint [RFC-ietf-secsh-dns-05.txt]
+ public final static int TYPE_RRSIG = 46; // RRSIG [RFC3755]
+ public final static int TYPE_NSEC = 47; // NSEC [RFC3755]
+ public final static int TYPE_DNSKEY = 48; // DNSKEY [RFC3755]
+ public final static int TYPE_UINFO = 100; // [IANA-Reserved]
+ public final static int TYPE_UID = 101; // [IANA-Reserved]
+ public final static int TYPE_GID = 102; // [IANA-Reserved]
+ public final static int TYPE_UNSPEC = 103; // [IANA-Reserved]
+ public final static int TYPE_TKEY = 249; // Transaction Key [RFC2930]
+ public final static int TYPE_TSIG = 250; // Transaction Signature [RFC2845]
+ public final static int TYPE_IXFR = 251; // Incremental transfer [RFC1995]
+ public final static int TYPE_AXFR = 252; // Transfer of an entire zone [RFC1035]
+ public final static int TYPE_MAILA = 253; // Mailbox-related records (MB, MG or MR) [RFC1035]
+ public final static int TYPE_MAILB = 254; // Mail agent RRs (Obsolete - see MX) [RFC1035]
+ public final static int TYPE_ANY = 255; // Request for all records [RFC1035]
+
+ //Time Intervals for various functions
+
+ public final static int SHARED_QUERY_TIME = 20; //milliseconds before send shared query
+ public final static int QUERY_WAIT_INTERVAL = 225; //milliseconds between query loops.
+ public final static int PROBE_WAIT_INTERVAL = 250; //milliseconds between probe loops.
+ public final static int RESPONSE_MIN_WAIT_INTERVAL = 20; //minimal wait interval for response.
+ public final static int RESPONSE_MAX_WAIT_INTERVAL = 115; //maximal wait interval for response
+ public final static int PROBE_CONFLICT_INTERVAL = 1000; //milliseconds to wait after conflict.
+ public final static int PROBE_THROTTLE_COUNT = 10; //After x tries go 1 time a sec. on probes.
+ public final static int PROBE_THROTTLE_COUNT_INTERVAL = 5000; //We only increment the throttle count, if
+ // the previous increment is inside this interval.
+ public final static int ANNOUNCE_WAIT_INTERVAL = 1000; //milliseconds between Announce loops.
+ public final static int RECORD_REAPER_INTERVAL = 10000; //milliseconds between cache cleanups.
+ public final static int KNOWN_ANSWER_TTL = 120;
+ public final static int ANNOUNCED_RENEWAL_TTL_INTERVAL = DNS_TTL * 500; // 50% of the TTL in milliseconds
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSEntry.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSEntry.java
new file mode 100644
index 000000000..4c74137a5
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSEntry.java
@@ -0,0 +1,148 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+
+package javax.jmdns.impl;
+
+import java.util.logging.Logger;
+
+/**
+ * DNS entry with a name, type, and class. This is the base
+ * class for questions and records.
+ *
+ * @version %I%, %G%
+ * @author Arthur van Hoff, Pierre Frisch, Rick Blair
+ */
+public class DNSEntry
+{
+ private static Logger logger = Logger.getLogger(DNSEntry.class.getName());
+ String key;
+ String name;
+ int type;
+ int clazz;
+ boolean unique;
+
+ /**
+ * Create an entry.
+ */
+ DNSEntry(String name, int type, int clazz)
+ {
+ this.key = name.toLowerCase();
+ this.name = name;
+ this.type = type;
+ this.clazz = clazz & DNSConstants.CLASS_MASK;
+ this.unique = (clazz & DNSConstants.CLASS_UNIQUE) != 0;
+ }
+
+ /**
+ * Check if two entries have exactly the same name, type, and class.
+ */
+ public boolean equals(Object obj)
+ {
+ if (obj instanceof DNSEntry)
+ {
+ DNSEntry other = (DNSEntry) obj;
+ return name.equals(other.name) && type == other.type && clazz == other.clazz;
+ }
+ return false;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public int getType()
+ {
+ return type;
+ }
+
+ /**
+ * Overriden, to return a value which is consistent with the value returned
+ * by equals(Object).
+ */
+ public int hashCode()
+ {
+ return name.hashCode() + type + clazz;
+ }
+
+ /**
+ * Get a string given a clazz.
+ */
+ static String getClazz(int clazz)
+ {
+ switch (clazz & DNSConstants.CLASS_MASK)
+ {
+ case DNSConstants.CLASS_IN:
+ return "in";
+ case DNSConstants.CLASS_CS:
+ return "cs";
+ case DNSConstants.CLASS_CH:
+ return "ch";
+ case DNSConstants.CLASS_HS:
+ return "hs";
+ case DNSConstants.CLASS_NONE:
+ return "none";
+ case DNSConstants.CLASS_ANY:
+ return "any";
+ default:
+ return "?";
+ }
+ }
+
+ /**
+ * Get a string given a type.
+ */
+ static String getType(int type)
+ {
+ switch (type)
+ {
+ case DNSConstants.TYPE_A:
+ return "a";
+ case DNSConstants.TYPE_AAAA:
+ return "aaaa";
+ case DNSConstants.TYPE_NS:
+ return "ns";
+ case DNSConstants.TYPE_MD:
+ return "md";
+ case DNSConstants.TYPE_MF:
+ return "mf";
+ case DNSConstants.TYPE_CNAME:
+ return "cname";
+ case DNSConstants.TYPE_SOA:
+ return "soa";
+ case DNSConstants.TYPE_MB:
+ return "mb";
+ case DNSConstants.TYPE_MG:
+ return "mg";
+ case DNSConstants.TYPE_MR:
+ return "mr";
+ case DNSConstants.TYPE_NULL:
+ return "null";
+ case DNSConstants.TYPE_WKS:
+ return "wks";
+ case DNSConstants.TYPE_PTR:
+ return "ptr";
+ case DNSConstants.TYPE_HINFO:
+ return "hinfo";
+ case DNSConstants.TYPE_MINFO:
+ return "minfo";
+ case DNSConstants.TYPE_MX:
+ return "mx";
+ case DNSConstants.TYPE_TXT:
+ return "txt";
+ case DNSConstants.TYPE_SRV:
+ return "srv";
+ case DNSConstants.TYPE_ANY:
+ return "any";
+ default:
+ return "?";
+ }
+ }
+
+ public String toString(String hdr, String other)
+ {
+ return hdr + "[" + getType(type) + "," + getClazz(clazz) + (unique ? "-unique," : ",") + name + ((other != null) ? "," + other + "]" : "]");
+ }
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSIncoming.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSIncoming.java
new file mode 100644
index 000000000..17516967e
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSIncoming.java
@@ -0,0 +1,530 @@
+///Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+
+
+package javax.jmdns.impl;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Parse an incoming DNS message into its components.
+ *
+ * @version %I%, %G%
+ * @author Arthur van Hoff, Werner Randelshofer, Pierre Frisch, Daniel Bobbert
+ */
+public final class DNSIncoming
+{
+ private static Logger logger = Logger.getLogger(DNSIncoming.class.getName());
+
+ // This is a hack to handle a bug in the BonjourConformanceTest
+ // It is sending out target strings that don't follow the "domain name"
+ // format.
+ public static boolean USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET = true;
+
+ // Implementation note: This vector should be immutable.
+ // If a client of DNSIncoming changes the contents of this vector,
+ // we get undesired results. To fix this, we have to migrate to
+ // the Collections API of Java 1.2. i.e we replace Vector by List.
+ // final static Vector EMPTY = new Vector();
+
+ private DatagramPacket packet;
+ private int off;
+ private int len;
+ private byte data[];
+
+ int id;
+ private int flags;
+ private int numQuestions;
+ int numAnswers;
+ private int numAuthorities;
+ private int numAdditionals;
+ private long receivedTime;
+
+ private List questions;
+ List answers;
+
+ /**
+ * Parse a message from a datagram packet.
+ */
+ DNSIncoming(DatagramPacket packet) throws IOException
+ {
+ this.packet = packet;
+ InetAddress source = packet.getAddress();
+ this.data = packet.getData();
+ this.len = packet.getLength();
+ this.off = packet.getOffset();
+ this.questions = Collections.EMPTY_LIST;
+ this.answers = Collections.EMPTY_LIST;
+ this.receivedTime = System.currentTimeMillis();
+
+ try
+ {
+ id = readUnsignedShort();
+ flags = readUnsignedShort();
+ numQuestions = readUnsignedShort();
+ numAnswers = readUnsignedShort();
+ numAuthorities = readUnsignedShort();
+ numAdditionals = readUnsignedShort();
+
+ // parse questions
+ if (numQuestions > 0)
+ {
+ questions = Collections.synchronizedList(new ArrayList(numQuestions));
+ for (int i = 0; i < numQuestions; i++)
+ {
+ DNSQuestion question = new DNSQuestion(readName(), readUnsignedShort(), readUnsignedShort());
+ questions.add(question);
+ }
+ }
+
+ // parse answers
+ int n = numAnswers + numAuthorities + numAdditionals;
+ if (n > 0)
+ {
+ answers = Collections.synchronizedList(new ArrayList(n));
+ for (int i = 0; i < n; i++)
+ {
+ String domain = readName();
+ int type = readUnsignedShort();
+ int clazz = readUnsignedShort();
+ int ttl = readInt();
+ int len = readUnsignedShort();
+ int end = off + len;
+ DNSRecord rec = null;
+
+ switch (type)
+ {
+ case DNSConstants.TYPE_A: // IPv4
+ case DNSConstants.TYPE_AAAA: // IPv6 FIXME [PJYF Oct 14 2004] This has not been tested
+ rec = new DNSRecord.Address(domain, type, clazz, ttl, readBytes(off, len));
+ break;
+ case DNSConstants.TYPE_CNAME:
+ case DNSConstants.TYPE_PTR:
+ String service = "";
+ try {
+ service = readName();
+ } catch (IOException e){
+ // there was a problem reading the service name
+ e.printStackTrace();
+ }
+ rec = new DNSRecord.Pointer(domain, type, clazz, ttl, service);
+ break;
+ case DNSConstants.TYPE_TXT:
+ rec = new DNSRecord.Text(domain, type, clazz, ttl, readBytes(off, len));
+ break;
+ case DNSConstants.TYPE_SRV:
+ int priority = readUnsignedShort();
+ int weight = readUnsignedShort();
+ int port = readUnsignedShort();
+ String target = "";
+ try {
+ // This is a hack to handle a bug in the BonjourConformanceTest
+ // It is sending out target strings that don't follow the "domain name"
+ // format.
+
+ if(USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET){
+ target = readName();
+ } else {
+ target = readNonNameString();
+ }
+ } catch (IOException e) {
+ // this can happen if the type of the label
+ // cannot be handled.
+ // down below the offset gets advanced to the end
+ // of the record
+ e.printStackTrace();
+ }
+ rec = new DNSRecord.Service(domain, type, clazz, ttl,
+ priority, weight, port, target);
+ break;
+ case DNSConstants.TYPE_HINFO:
+ // Maybe we should do something with those
+ break;
+ default :
+ logger.finer("DNSIncoming() unknown type:" + type);
+ break;
+ }
+
+ if (rec != null)
+ {
+ rec.setRecordSource(source);
+ // Add a record, if we were able to create one.
+ answers.add(rec);
+ }
+ else
+ {
+ // Addjust the numbers for the skipped record
+ if (answers.size() < numAnswers)
+ {
+ numAnswers--;
+ }
+ else
+ {
+ if (answers.size() < numAnswers + numAuthorities)
+ {
+ numAuthorities--;
+ }
+ else
+ {
+ if (answers.size() < numAnswers + numAuthorities + numAdditionals)
+ {
+ numAdditionals--;
+ }
+ }
+ }
+ }
+ off = end;
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ logger.log(Level.WARNING, "DNSIncoming() dump " + print(true) + "\n exception ", e);
+ throw e;
+ }
+ }
+
+ /**
+ * Check if the message is a query.
+ */
+ boolean isQuery()
+ {
+ return (flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_QUERY;
+ }
+
+ /**
+ * Check if the message is truncated.
+ */
+ public boolean isTruncated()
+ {
+ return (flags & DNSConstants.FLAGS_TC) != 0;
+ }
+
+ /**
+ * Check if the message is a response.
+ */
+ boolean isResponse()
+ {
+ return (flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_RESPONSE;
+ }
+
+ private int get(int off) throws IOException
+ {
+ if ((off < 0) || (off >= len))
+ {
+ throw new IOException("parser error: offset=" + off);
+ }
+ return data[off] & 0xFF;
+ }
+
+ private int readUnsignedShort() throws IOException
+ {
+ return (get(off++) << 8) + get(off++);
+ }
+
+ private int readInt() throws IOException
+ {
+ return (readUnsignedShort() << 16) + readUnsignedShort();
+ }
+
+ private byte[] readBytes(int off, int len) throws IOException
+ {
+ byte bytes[] = new byte[len];
+ System.arraycopy(data, off, bytes, 0, len);
+ return bytes;
+ }
+
+ private void readUTF(StringBuffer buf, int off, int len) throws IOException
+ {
+ for (int end = off + len; off < end;)
+ {
+ int ch = get(off++);
+ switch (ch >> 4)
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ // 0xxxxxxx
+ break;
+ case 12:
+ case 13:
+ // 110x xxxx 10xx xxxx
+ ch = ((ch & 0x1F) << 6) | (get(off++) & 0x3F);
+ break;
+ case 14:
+ // 1110 xxxx 10xx xxxx 10xx xxxx
+ ch = ((ch & 0x0f) << 12) | ((get(off++) & 0x3F) << 6) | (get(off++) & 0x3F);
+ break;
+ default:
+ // 10xx xxxx, 1111 xxxx
+ ch = ((ch & 0x3F) << 4) | (get(off++) & 0x0f);
+ break;
+ }
+ buf.append((char) ch);
+ }
+ }
+
+ private String readNonNameString() throws IOException
+ {
+ StringBuffer buf = new StringBuffer();
+ int off = this.off;
+ int len = get(off++);
+ readUTF(buf, off, len);
+
+ return buf.toString();
+ }
+
+ private String readName() throws IOException
+ {
+ StringBuffer buf = new StringBuffer();
+ int off = this.off;
+ int next = -1;
+ int first = off;
+
+ while (true)
+ {
+ int len = get(off++);
+ if (len == 0)
+ {
+ break;
+ }
+ switch (len & 0xC0)
+ {
+ case 0x00:
+ //buf.append("[" + off + "]");
+ readUTF(buf, off, len);
+ off += len;
+ buf.append('.');
+ break;
+ case 0xC0:
+ //buf.append("<" + (off - 1) + ">");
+ if (next < 0)
+ {
+ next = off + 1;
+ }
+ off = ((len & 0x3F) << 8) | get(off++);
+ if (off >= first)
+ {
+ throw new IOException("bad domain name: possible circular name detected." +
+ " name start: " + first +
+ " bad offset: 0x" + Integer.toHexString(off));
+ }
+ first = off;
+ break;
+ default:
+ throw new IOException("unsupported dns label type: '" + Integer.toHexString(len & 0xC0) +"' at " + (off-1));
+ }
+ }
+ this.off = (next >= 0) ? next : off;
+ return buf.toString();
+ }
+
+ /**
+ * Debugging.
+ */
+ String print(boolean dump)
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append(toString() + "\n");
+ for (Iterator iterator = questions.iterator(); iterator.hasNext();)
+ {
+ buf.append(" ques:" + iterator.next() + "\n");
+ }
+ int count = 0;
+ for (Iterator iterator = answers.iterator(); iterator.hasNext(); count++)
+ {
+ if (count < numAnswers)
+ {
+ buf.append(" answ:");
+ }
+ else
+ {
+ if (count < numAnswers + numAuthorities)
+ {
+ buf.append(" auth:");
+ }
+ else
+ {
+ buf.append(" addi:");
+ }
+ }
+ buf.append(iterator.next() + "\n");
+ }
+ if (dump)
+ {
+ for (int off = 0, len = packet.getLength(); off < len; off += 32)
+ {
+ int n = Math.min(32, len - off);
+ if (off < 10)
+ {
+ buf.append(' ');
+ }
+ if (off < 100)
+ {
+ buf.append(' ');
+ }
+ buf.append(off);
+ buf.append(':');
+ for (int i = 0; i < n; i++)
+ {
+ if ((i % 8) == 0)
+ {
+ buf.append(' ');
+ }
+ buf.append(Integer.toHexString((data[off + i] & 0xF0) >> 4));
+ buf.append(Integer.toHexString((data[off + i] & 0x0F) >> 0));
+ }
+ buf.append("\n");
+ buf.append(" ");
+ for (int i = 0; i < n; i++)
+ {
+ if ((i % 8) == 0)
+ {
+ buf.append(' ');
+ }
+ buf.append(' ');
+ int ch = data[off + i] & 0xFF;
+ buf.append(((ch > ' ') && (ch < 127)) ? (char) ch : '.');
+ }
+ buf.append("\n");
+
+ // limit message size
+ if (off + 32 >= 256)
+ {
+ buf.append("....\n");
+ break;
+ }
+ }
+ }
+ return buf.toString();
+ }
+
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append(isQuery() ? "dns[query," : "dns[response,");
+ if (packet.getAddress() != null)
+ {
+ buf.append(packet.getAddress().getHostAddress());
+ }
+ buf.append(':');
+ buf.append(packet.getPort());
+ buf.append(",len=");
+ buf.append(packet.getLength());
+ buf.append(",id=0x");
+ buf.append(Integer.toHexString(id));
+ if (flags != 0)
+ {
+ buf.append(",flags=0x");
+ buf.append(Integer.toHexString(flags));
+ if ((flags & DNSConstants.FLAGS_QR_RESPONSE) != 0)
+ {
+ buf.append(":r");
+ }
+ if ((flags & DNSConstants.FLAGS_AA) != 0)
+ {
+ buf.append(":aa");
+ }
+ if ((flags & DNSConstants.FLAGS_TC) != 0)
+ {
+ buf.append(":tc");
+ }
+ }
+ if (numQuestions > 0)
+ {
+ buf.append(",questions=");
+ buf.append(numQuestions);
+ }
+ if (numAnswers > 0)
+ {
+ buf.append(",answers=");
+ buf.append(numAnswers);
+ }
+ if (numAuthorities > 0)
+ {
+ buf.append(",authorities=");
+ buf.append(numAuthorities);
+ }
+ if (numAdditionals > 0)
+ {
+ buf.append(",additionals=");
+ buf.append(numAdditionals);
+ }
+ buf.append("]");
+ return buf.toString();
+ }
+
+ /**
+ * Appends answers to this Incoming.
+ *
+ * @throws IllegalArgumentException If not a query or if Truncated.
+ */
+ void append(DNSIncoming that)
+ {
+ if (this.isQuery() && this.isTruncated() && that.isQuery())
+ {
+ if (that.numQuestions > 0) {
+ if (Collections.EMPTY_LIST.equals(this.questions))
+ this.questions = Collections.synchronizedList(new ArrayList(that.numQuestions));
+
+ this.questions.addAll(that.questions);
+ this.numQuestions += that.numQuestions;
+ }
+
+ if (Collections.EMPTY_LIST.equals(answers))
+ {
+ answers = Collections.synchronizedList(new ArrayList());
+ }
+
+ if (that.numAnswers > 0)
+ {
+ this.answers.addAll(this.numAnswers, that.answers.subList(0, that.numAnswers));
+ this.numAnswers += that.numAnswers;
+ }
+ if (that.numAuthorities > 0)
+ {
+ this.answers.addAll(this.numAnswers + this.numAuthorities, that.answers.subList(that.numAnswers, that.numAnswers + that.numAuthorities));
+ this.numAuthorities += that.numAuthorities;
+ }
+ if (that.numAdditionals > 0)
+ {
+ this.answers.addAll(that.answers.subList(that.numAnswers + that.numAuthorities, that.numAnswers + that.numAuthorities + that.numAdditionals));
+ this.numAdditionals += that.numAdditionals;
+ }
+ }
+ else
+ {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ public int elapseSinceArrival()
+ {
+ return (int) (System.currentTimeMillis() - receivedTime);
+ }
+
+ public List getQuestions()
+ {
+ return questions;
+ }
+
+ public List getAnswers()
+ {
+ return answers;
+ }
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSListener.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSListener.java
new file mode 100644
index 000000000..4ea15e2d0
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSListener.java
@@ -0,0 +1,24 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+
+package javax.jmdns.impl;
+
+// REMIND: Listener should follow Java idiom for listener or have a different
+// name.
+
+/**
+ * DNSListener.
+ * Listener for record updates.
+ *
+ * @author Werner Randelshofer, Rick Blair
+ * @version 1.0 May 22, 2004 Created.
+ */
+interface DNSListener
+{
+ /**
+ * Update a DNS record.
+ */
+ void updateRecord(JmDNSImpl jmdns, long now, DNSRecord record);
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSOutgoing.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSOutgoing.java
new file mode 100644
index 000000000..7e1b23b1a
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSOutgoing.java
@@ -0,0 +1,394 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+
+package javax.jmdns.impl;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.LinkedList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * An outgoing DNS message.
+ *
+ * @version %I%, %G%
+ * @author Arthur van Hoff, Rick Blair, Werner Randelshofer
+ */
+public final class DNSOutgoing
+{
+ /**
+ * This can be used to turn off domain name compression. This was helpful for
+ * tracking problems interacting with other mdns implementations.
+ */
+ public static boolean USE_DOMAIN_NAME_COMPRESSION = true;
+
+ private static Logger logger = Logger.getLogger(DNSOutgoing.class.getName());
+ int id;
+ int flags;
+ private boolean multicast;
+ private int numQuestions;
+ private int numAnswers;
+ private int numAuthorities;
+ private int numAdditionals;
+ private Hashtable names;
+
+ byte data[];
+ int off;
+ int len;
+
+ /**
+ * Create an outgoing multicast query or response.
+ */
+ public DNSOutgoing(int flags)
+ {
+ this(flags, true);
+ }
+
+ /**
+ * Create an outgoing query or response.
+ */
+ public DNSOutgoing(int flags, boolean multicast)
+ {
+ this.flags = flags;
+ this.multicast = multicast;
+ names = new Hashtable();
+ data = new byte[DNSConstants.MAX_MSG_TYPICAL];
+ off = 12;
+ }
+
+ /**
+ * Add a question to the message.
+ */
+ public void addQuestion(DNSQuestion rec) throws IOException
+ {
+ if (numAnswers > 0 || numAuthorities > 0 || numAdditionals > 0)
+ {
+ throw new IllegalStateException("Questions must be added before answers");
+ }
+ numQuestions++;
+ writeQuestion(rec);
+ }
+
+ /**
+ * Add an answer if it is not suppressed.
+ */
+ void addAnswer(DNSIncoming in, DNSRecord rec) throws IOException
+ {
+ if (numAuthorities > 0 || numAdditionals > 0)
+ {
+ throw new IllegalStateException("Answers must be added before authorities and additionals");
+ }
+ if (!rec.suppressedBy(in))
+ {
+ addAnswer(rec, 0);
+ }
+ }
+
+ /**
+ * Add an additional answer to the record. Omit if there is no room.
+ */
+ void addAdditionalAnswer(DNSIncoming in, DNSRecord rec) throws IOException
+ {
+ if ((off < DNSConstants.MAX_MSG_TYPICAL - 200) && !rec.suppressedBy(in))
+ {
+ writeRecord(rec, 0);
+ numAdditionals++;
+ }
+ }
+
+ /**
+ * Add an answer to the message.
+ */
+ public void addAnswer(DNSRecord rec, long now) throws IOException
+ {
+ if (numAuthorities > 0 || numAdditionals > 0)
+ {
+ throw new IllegalStateException("Questions must be added before answers");
+ }
+ if (rec != null)
+ {
+ if ((now == 0) || !rec.isExpired(now))
+ {
+ writeRecord(rec, now);
+ numAnswers++;
+ }
+ }
+ }
+
+ private LinkedList authorativeAnswers = new LinkedList();
+
+ /**
+ * Add an authorative answer to the message.
+ */
+ public void addAuthorativeAnswer(DNSRecord rec) throws IOException
+ {
+ if (numAdditionals > 0)
+ {
+ throw new IllegalStateException("Authorative answers must be added before additional answers");
+ }
+ authorativeAnswers.add(rec);
+ writeRecord(rec, 0);
+ numAuthorities++;
+
+ // VERIFY:
+
+ }
+
+ void writeByte(int value) throws IOException
+ {
+ if (off >= data.length)
+ {
+ throw new IOException("buffer full");
+ }
+ data[off++] = (byte) value;
+ }
+
+ void writeBytes(String str, int off, int len) throws IOException
+ {
+ for (int i = 0; i < len; i++)
+ {
+ writeByte(str.charAt(off + i));
+ }
+ }
+
+ void writeBytes(byte data[]) throws IOException
+ {
+ if (data != null)
+ {
+ writeBytes(data, 0, data.length);
+ }
+ }
+
+ void writeBytes(byte data[], int off, int len) throws IOException
+ {
+ for (int i = 0; i < len; i++)
+ {
+ writeByte(data[off + i]);
+ }
+ }
+
+ void writeShort(int value) throws IOException
+ {
+ writeByte(value >> 8);
+ writeByte(value);
+ }
+
+ void writeInt(int value) throws IOException
+ {
+ writeShort(value >> 16);
+ writeShort(value);
+ }
+
+ void writeUTF(String str, int off, int len) throws IOException
+ {
+ // compute utf length
+ int utflen = 0;
+ for (int i = 0; i < len; i++)
+ {
+ int ch = str.charAt(off + i);
+ if ((ch >= 0x0001) && (ch <= 0x007F))
+ {
+ utflen += 1;
+ }
+ else
+ {
+ if (ch > 0x07FF)
+ {
+ utflen += 3;
+ }
+ else
+ {
+ utflen += 2;
+ }
+ }
+ }
+ // write utf length
+ writeByte(utflen);
+ // write utf data
+ for (int i = 0; i < len; i++)
+ {
+ int ch = str.charAt(off + i);
+ if ((ch >= 0x0001) && (ch <= 0x007F))
+ {
+ writeByte(ch);
+ }
+ else
+ {
+ if (ch > 0x07FF)
+ {
+ writeByte(0xE0 | ((ch >> 12) & 0x0F));
+ writeByte(0x80 | ((ch >> 6) & 0x3F));
+ writeByte(0x80 | ((ch >> 0) & 0x3F));
+ }
+ else
+ {
+ writeByte(0xC0 | ((ch >> 6) & 0x1F));
+ writeByte(0x80 | ((ch >> 0) & 0x3F));
+ }
+ }
+ }
+ }
+
+ void writeName(String name) throws IOException
+ {
+ writeName(name, true);
+ }
+
+ void writeName(String name, boolean useCompression) throws IOException
+ {
+ while (true)
+ {
+ int n = name.indexOf('.');
+ if (n < 0)
+ {
+ n = name.length();
+ }
+ if (n <= 0)
+ {
+ writeByte(0);
+ return;
+ }
+ if(useCompression && USE_DOMAIN_NAME_COMPRESSION){
+ Integer offset = (Integer) names.get(name);
+ if (offset != null)
+ {
+ int val = offset.intValue();
+
+ if (val > off)
+ {
+ logger.log(Level.WARNING, "DNSOutgoing writeName failed val=" + val + " name=" + name);
+ }
+
+ writeByte((val >> 8) | 0xC0);
+ writeByte(val & 0xFF);
+ return;
+ }
+ names.put(name, Integer.valueOf(off));
+ }
+ writeUTF(name, 0, n);
+ name = name.substring(n);
+ if (name.startsWith("."))
+ {
+ name = name.substring(1);
+ }
+ }
+ }
+
+ void writeQuestion(DNSQuestion question) throws IOException
+ {
+ writeName(question.name);
+ writeShort(question.type);
+ writeShort(question.clazz);
+ }
+
+ void writeRecord(DNSRecord rec, long now) throws IOException
+ {
+ int save = off;
+ try
+ {
+ writeName(rec.name);
+ writeShort(rec.type);
+ writeShort(rec.clazz | ((rec.unique && multicast) ? DNSConstants.CLASS_UNIQUE : 0));
+ writeInt((now == 0) ? rec.getTtl() : rec.getRemainingTTL(now));
+ writeShort(0);
+ int start = off;
+ rec.write(this);
+ int len = off - start;
+ data[start - 2] = (byte) (len >> 8);
+ data[start - 1] = (byte) (len & 0xFF);
+ }
+ catch (IOException e)
+ {
+ off = save;
+ throw e;
+ }
+ }
+
+ /**
+ * Finish the message before sending it off.
+ */
+ void finish() throws IOException
+ {
+ int save = off;
+ off = 0;
+
+ writeShort(multicast ? 0 : id);
+ writeShort(flags);
+ writeShort(numQuestions);
+ writeShort(numAnswers);
+ writeShort(numAuthorities);
+ writeShort(numAdditionals);
+ off = save;
+ }
+
+ boolean isQuery()
+ {
+ return (flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_QUERY;
+ }
+
+ public boolean isEmpty()
+ {
+ return numQuestions == 0 && numAuthorities == 0
+ && numAdditionals == 0 && numAnswers == 0;
+ }
+
+
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append(isQuery() ? "dns[query," : "dns[response,");
+ //buf.append(packet.getAddress().getHostAddress());
+ buf.append(':');
+ //buf.append(packet.getPort());
+ //buf.append(",len=");
+ //buf.append(packet.getLength());
+ buf.append(",id=0x");
+ buf.append(Integer.toHexString(id));
+ if (flags != 0)
+ {
+ buf.append(",flags=0x");
+ buf.append(Integer.toHexString(flags));
+ if ((flags & DNSConstants.FLAGS_QR_RESPONSE) != 0)
+ {
+ buf.append(":r");
+ }
+ if ((flags & DNSConstants.FLAGS_AA) != 0)
+ {
+ buf.append(":aa");
+ }
+ if ((flags & DNSConstants.FLAGS_TC) != 0)
+ {
+ buf.append(":tc");
+ }
+ }
+ if (numQuestions > 0)
+ {
+ buf.append(",questions=");
+ buf.append(numQuestions);
+ }
+ if (numAnswers > 0)
+ {
+ buf.append(",answers=");
+ buf.append(numAnswers);
+ }
+ if (numAuthorities > 0)
+ {
+ buf.append(",authorities=");
+ buf.append(numAuthorities);
+ }
+ if (numAdditionals > 0)
+ {
+ buf.append(",additionals=");
+ buf.append(numAdditionals);
+ }
+ buf.append(",\nnames=" + names);
+ buf.append(",\nauthorativeAnswers=" + authorativeAnswers);
+
+ buf.append("]");
+ return buf.toString();
+ }
+
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSQuestion.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSQuestion.java
new file mode 100644
index 000000000..b37b2d361
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSQuestion.java
@@ -0,0 +1,44 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+
+package javax.jmdns.impl;
+
+import java.util.logging.Logger;
+
+/**
+ * A DNS question.
+ *
+ * @version %I%, %G%
+ * @author Arthur van Hoff
+ */
+public final class DNSQuestion extends DNSEntry
+{
+ private static Logger logger = Logger.getLogger(DNSQuestion.class.getName());
+
+ /**
+ * Create a question.
+ */
+ public DNSQuestion(String name, int type, int clazz)
+ {
+ super(name, type, clazz);
+ }
+
+ /**
+ * Check if this question is answered by a given DNS record.
+ */
+ boolean answeredBy(DNSRecord rec)
+ {
+ return (clazz == rec.clazz) && ((type == rec.type) || (type == DNSConstants.TYPE_ANY)) &&
+ name.equals(rec.name);
+ }
+
+ /**
+ * For debugging only.
+ */
+ public String toString()
+ {
+ return toString("question", null);
+ }
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSRecord.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSRecord.java
new file mode 100644
index 000000000..6fa92bb63
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSRecord.java
@@ -0,0 +1,742 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+
+package javax.jmdns.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Iterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * DNS record
+ *
+ * @version %I%, %G%
+ * @author Arthur van Hoff, Rick Blair, Werner Randelshofer, Pierre Frisch
+ */
+public abstract class DNSRecord extends DNSEntry
+{
+ private static Logger logger = Logger.getLogger(DNSRecord.class.getName());
+ private int ttl;
+ private long created;
+
+ /**
+ * This source is mainly for debugging purposes, should be the address that
+ * sent this record.
+ */
+ private InetAddress source;
+
+ /**
+ * Create a DNSRecord with a name, type, clazz, and ttl.
+ */
+ DNSRecord(String name, int type, int clazz, int ttl)
+ {
+ super(name, type, clazz);
+ this.ttl = ttl;
+ this.created = System.currentTimeMillis();
+ }
+
+ /**
+ * True if this record is the same as some other record.
+ */
+ public boolean equals(Object other)
+ {
+ return (other instanceof DNSRecord) && sameAs((DNSRecord) other);
+ }
+
+ /**
+ * True if this record is the same as some other record.
+ */
+ boolean sameAs(DNSRecord other)
+ {
+ return super.equals(other) && sameValue((DNSRecord) other);
+ }
+
+ /**
+ * True if this record has the same value as some other record.
+ */
+ abstract boolean sameValue(DNSRecord other);
+
+ /**
+ * True if this record has the same type as some other record.
+ */
+ boolean sameType(DNSRecord other)
+ {
+ return type == other.type;
+ }
+
+ /**
+ * Handles a query represented by this record.
+ *
+ * @return Returns true if a conflict with one of the services registered
+ * with JmDNS or with the hostname occured.
+ */
+ abstract boolean handleQuery(JmDNSImpl dns, long expirationTime);
+
+ /**
+ * Handles a responserepresented by this record.
+ *
+ * @return Returns true if a conflict with one of the services registered
+ * with JmDNS or with the hostname occured.
+ */
+ abstract boolean handleResponse(JmDNSImpl dns);
+
+ /**
+ * Adds this as an answer to the provided outgoing datagram.
+ */
+ abstract DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException;
+
+ /**
+ * True if this record is suppressed by the answers in a message.
+ */
+ boolean suppressedBy(DNSIncoming msg)
+ {
+ try
+ {
+ for (int i = msg.numAnswers; i-- > 0;)
+ {
+ if (suppressedBy((DNSRecord) msg.answers.get(i)))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ catch (ArrayIndexOutOfBoundsException e)
+ {
+ logger.log(Level.WARNING, "suppressedBy() message " + msg + " exception ", e);
+ // msg.print(true);
+ return false;
+ }
+ }
+
+ /**
+ * True if this record would be supressed by an answer.
+ * This is the case if this record would not have a
+ * significantly longer TTL.
+ */
+ boolean suppressedBy(DNSRecord other)
+ {
+ if (sameAs(other) && (other.ttl > ttl / 2))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the expiration time of this record.
+ */
+ long getExpirationTime(int percent)
+ {
+ return created + (percent * ttl * 10L);
+ }
+
+ /**
+ * Get the remaining TTL for this record.
+ */
+ int getRemainingTTL(long now)
+ {
+ return (int) Math.max(0, (getExpirationTime(100) - now) / 1000);
+ }
+
+ /**
+ * Check if the record is expired.
+ */
+ public boolean isExpired(long now)
+ {
+ return getExpirationTime(100) <= now;
+ }
+
+ /**
+ * Check if the record is stale, ie it has outlived
+ * more than half of its TTL.
+ */
+ boolean isStale(long now)
+ {
+ return getExpirationTime(50) <= now;
+ }
+
+ /**
+ * Reset the TTL of a record. This avoids having to
+ * update the entire record in the cache.
+ */
+ void resetTTL(DNSRecord other)
+ {
+ created = other.created;
+ ttl = other.ttl;
+ }
+
+ /**
+ * Write this record into an outgoing message.
+ */
+ abstract void write(DNSOutgoing out) throws IOException;
+
+ /**
+ * Address record.
+ */
+ static class Address extends DNSRecord
+ {
+ private static Logger logger = Logger.getLogger(Address.class.getName());
+ InetAddress addr;
+
+ Address(String name, int type, int clazz, int ttl, InetAddress addr)
+ {
+ super(name, type, clazz, ttl);
+ this.addr = addr;
+ }
+
+ Address(String name, int type, int clazz, int ttl, byte[] rawAddress)
+ {
+ super(name, type, clazz, ttl);
+ try
+ {
+ this.addr = InetAddress.getByAddress(rawAddress);
+ }
+ catch (UnknownHostException exception)
+ {
+ logger.log(Level.WARNING, "Address() exception ", exception);
+ }
+ }
+
+ void write(DNSOutgoing out) throws IOException
+ {
+ if (addr != null)
+ {
+ byte[] buffer = addr.getAddress();
+ if (DNSConstants.TYPE_A == type)
+ {
+ // If we have a type A records we should answer with a IPv4 address
+ if (addr instanceof Inet4Address)
+ {
+ // All is good
+ }
+ else
+ {
+ // Get the last four bytes
+ byte[] tempbuffer = buffer;
+ buffer = new byte[4];
+ System.arraycopy(tempbuffer, 12, buffer, 0, 4);
+ }
+ }
+ else
+ {
+ // If we have a type AAAA records we should answer with a IPv6 address
+ if (addr instanceof Inet4Address)
+ {
+ byte[] tempbuffer = buffer;
+ buffer = new byte[16];
+ for (int i = 0; i < 16; i++)
+ {
+ if (i < 11)
+ {
+ buffer[i] = tempbuffer[i - 12];
+ }
+ else
+ {
+ buffer[i] = 0;
+ }
+ }
+ }
+ }
+ int length = buffer.length;
+ out.writeBytes(buffer, 0, length);
+ }
+ }
+
+ boolean same(DNSRecord other)
+ {
+ return ((sameName(other)) && ((sameValue(other))));
+ }
+
+ boolean sameName(DNSRecord other)
+ {
+ return name.equalsIgnoreCase(((Address) other).name);
+ }
+
+ boolean sameValue(DNSRecord other)
+ {
+ return addr.equals(((Address) other).getAddress());
+ }
+
+ InetAddress getAddress()
+ {
+ return addr;
+ }
+
+ /**
+ * Creates a byte array representation of this record.
+ * This is needed for tie-break tests according to
+ * draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2.
+ */
+ private byte[] toByteArray()
+ {
+ try
+ {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ DataOutputStream dout = new DataOutputStream(bout);
+ dout.write(name.getBytes("UTF8"));
+ dout.writeShort(type);
+ dout.writeShort(clazz);
+ //dout.writeInt(len);
+ byte[] buffer = addr.getAddress();
+ for (int i = 0; i < buffer.length; i++)
+ {
+ dout.writeByte(buffer[i]);
+ }
+ dout.close();
+ return bout.toByteArray();
+ }
+ catch (IOException e)
+ {
+ throw new InternalError();
+ }
+ }
+
+ /**
+ * Does a lexicographic comparison of the byte array representation
+ * of this record and that record.
+ * This is needed for tie-break tests according to
+ * draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2.
+ */
+ private int lexCompare(DNSRecord.Address that)
+ {
+ byte[] thisBytes = this.toByteArray();
+ byte[] thatBytes = that.toByteArray();
+ for (int i = 0, n = Math.min(thisBytes.length, thatBytes.length); i < n; i++)
+ {
+ if (thisBytes[i] > thatBytes[i])
+ {
+ return 1;
+ }
+ else
+ {
+ if (thisBytes[i] < thatBytes[i])
+ {
+ return -1;
+ }
+ }
+ }
+ return thisBytes.length - thatBytes.length;
+ }
+
+ /**
+ * Does the necessary actions, when this as a query.
+ */
+ boolean handleQuery(JmDNSImpl dns, long expirationTime)
+ {
+ DNSRecord.Address dnsAddress = dns.getLocalHost().getDNSAddressRecord(this);
+ if (dnsAddress != null)
+ {
+ if (dnsAddress.sameType(this) && dnsAddress.sameName(this) && (!dnsAddress.sameValue(this)))
+ {
+ logger.finer("handleQuery() Conflicting probe detected. dns state " + dns.getState() + " lex compare " + lexCompare(dnsAddress));
+ // Tie-breaker test
+ if (dns.getState().isProbing() && lexCompare(dnsAddress) >= 0)
+ {
+ // We lost the tie-break. We have to choose a different name.
+ dns.getLocalHost().incrementHostName();
+ dns.getCache().clear();
+ for (Iterator i = dns.getServices().values().iterator(); i.hasNext();)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) i.next();
+ info.revertState();
+ }
+ }
+ dns.revertState();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Does the necessary actions, when this as a response.
+ */
+ boolean handleResponse(JmDNSImpl dns)
+ {
+ DNSRecord.Address dnsAddress = dns.getLocalHost().getDNSAddressRecord(this);
+ if (dnsAddress != null)
+ {
+ if (dnsAddress.sameType(this) && dnsAddress.sameName(this) && (!dnsAddress.sameValue(this)))
+ {
+ logger.finer("handleResponse() Denial detected");
+
+ if (dns.getState().isProbing())
+ {
+ dns.getLocalHost().incrementHostName();
+ dns.getCache().clear();
+ for (Iterator i = dns.getServices().values().iterator(); i.hasNext();)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) i.next();
+ info.revertState();
+ }
+ }
+ dns.revertState();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException
+ {
+ return out;
+ }
+
+ public String toString()
+ {
+ return toString(" address '" + (addr != null ? addr.getHostAddress() : "null") + "'");
+ }
+
+ }
+
+ /**
+ * Pointer record.
+ */
+ public static class Pointer extends DNSRecord
+ {
+ private static Logger logger = Logger.getLogger(Pointer.class.getName());
+ String alias;
+
+ public Pointer(String name, int type, int clazz, int ttl, String alias)
+ {
+ super(name, type, clazz, ttl);
+ this.alias = alias;
+ }
+
+ void write(DNSOutgoing out) throws IOException
+ {
+ out.writeName(alias);
+ }
+
+ boolean sameValue(DNSRecord other)
+ {
+ return alias.equals(((Pointer) other).alias);
+ }
+
+ boolean handleQuery(JmDNSImpl dns, long expirationTime)
+ {
+ // Nothing to do (?)
+ // I think there is no possibility for conflicts for this record type?
+ return false;
+ }
+
+ boolean handleResponse(JmDNSImpl dns)
+ {
+ // Nothing to do (?)
+ // I think there is no possibility for conflicts for this record type?
+ return false;
+ }
+
+ String getAlias()
+ {
+ return alias;
+ }
+
+ DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException
+ {
+ return out;
+ }
+
+ public String toString()
+ {
+ return toString(alias);
+ }
+ }
+
+ public static class Text extends DNSRecord
+ {
+ private static Logger logger = Logger.getLogger(Text.class.getName());
+ byte text[];
+
+ public Text(String name, int type, int clazz, int ttl, byte text[])
+ {
+ super(name, type, clazz, ttl);
+ this.text = text;
+ }
+
+ void write(DNSOutgoing out) throws IOException
+ {
+ out.writeBytes(text, 0, text.length);
+ }
+
+ boolean sameValue(DNSRecord other)
+ {
+ Text txt = (Text) other;
+ if (txt.text.length != text.length)
+ {
+ return false;
+ }
+ for (int i = text.length; i-- > 0;)
+ {
+ if (txt.text[i] != text[i])
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ boolean handleQuery(JmDNSImpl dns, long expirationTime)
+ {
+ // Nothing to do (?)
+ // I think there is no possibility for conflicts for this record type?
+ return false;
+ }
+
+ boolean handleResponse(JmDNSImpl dns)
+ {
+ // Nothing to do (?)
+ // Shouldn't we care if we get a conflict at this level?
+ /*
+ ServiceInfo info = (ServiceInfo) dns.services.get(name.toLowerCase());
+ if (info != null) {
+ if (! Arrays.equals(text,info.text)) {
+ info.revertState();
+ return true;
+ }
+ }*/
+ return false;
+ }
+
+ DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException
+ {
+ return out;
+ }
+
+ public String toString()
+ {
+ return toString((text.length > 10) ? new String(text, 0, 7) + "..." : new String(text));
+ }
+ }
+
+ /**
+ * Service record.
+ */
+ public static class Service extends DNSRecord
+ {
+ private static Logger logger = Logger.getLogger(Service.class.getName());
+ int priority;
+ int weight;
+ int port;
+ String server;
+
+ public Service(String name, int type, int clazz, int ttl, int priority, int weight, int port, String server)
+ {
+ super(name, type, clazz, ttl);
+ this.priority = priority;
+ this.weight = weight;
+ this.port = port;
+ this.server = server;
+ }
+
+ void write(DNSOutgoing out) throws IOException
+ {
+ out.writeShort(priority);
+ out.writeShort(weight);
+ out.writeShort(port);
+ if(DNSIncoming.USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET){
+ out.writeName(server, false);
+ } else {
+ out.writeUTF(server, 0, server.length());
+
+ // add a zero byte to the end just to be safe, this is the strange form
+ // used by the BonjourConformanceTest
+ out.writeByte(0);
+ }
+ }
+
+ private byte[] toByteArray()
+ {
+ try
+ {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ DataOutputStream dout = new DataOutputStream(bout);
+ dout.write(name.getBytes("UTF8"));
+ dout.writeShort(type);
+ dout.writeShort(clazz);
+ //dout.writeInt(len);
+ dout.writeShort(priority);
+ dout.writeShort(weight);
+ dout.writeShort(port);
+ dout.write(server.getBytes("UTF8"));
+ dout.close();
+ return bout.toByteArray();
+ }
+ catch (IOException e)
+ {
+ throw new InternalError();
+ }
+ }
+
+ private int lexCompare(DNSRecord.Service that)
+ {
+ byte[] thisBytes = this.toByteArray();
+ byte[] thatBytes = that.toByteArray();
+ for (int i = 0, n = Math.min(thisBytes.length, thatBytes.length); i < n; i++)
+ {
+ if (thisBytes[i] > thatBytes[i])
+ {
+ return 1;
+ }
+ else
+ {
+ if (thisBytes[i] < thatBytes[i])
+ {
+ return -1;
+ }
+ }
+ }
+ return thisBytes.length - thatBytes.length;
+ }
+
+ boolean sameValue(DNSRecord other)
+ {
+ Service s = (Service) other;
+ return (priority == s.priority) && (weight == s.weight) && (port == s.port) && server.equals(s.server);
+ }
+
+ boolean handleQuery(JmDNSImpl dns, long expirationTime)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(name.toLowerCase());
+ if (info != null
+ && (port != info.port || !server.equalsIgnoreCase(dns.getLocalHost().getName())))
+ {
+ logger.finer("handleQuery() Conflicting probe detected from: " + getRecordSource());
+ DNSRecord.Service localService = new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV,
+ DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE,
+ DNSConstants.DNS_TTL, info.priority,
+ info.weight, info.port, dns.getLocalHost().getName());
+
+ // This block is useful for debugging race conditions when jmdns is respoding to
+ // itself.
+ try
+ {
+ if(dns.getInterface().equals(getRecordSource())){
+ logger.warning("Got conflicting probe from ourselves\n" +
+ "incoming: " +this.toString() + "\n" +
+ "local : " + localService.toString());
+ }
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+
+ int comparison = lexCompare(localService);
+
+ if(comparison == 0){
+ // the 2 records are identical this probably means we are seeing our own record.
+ // With mutliple interfaces on a single computer it is possible to see our
+ // own records come in on different interfaces than the ones they were sent on.
+ // see section "10. Conflict Resolution" of mdns draft spec.
+ logger.finer("handleQuery() Ignoring a identical service query");
+ return false;
+ }
+
+ // Tie breaker test
+ if (info.getState().isProbing() && comparison > 0)
+ {
+ // We lost the tie break
+ String oldName = info.getQualifiedName().toLowerCase();
+ info.setName(dns.incrementName(info.getName()));
+ dns.getServices().remove(oldName);
+ dns.getServices().put(info.getQualifiedName().toLowerCase(), info);
+ logger.finer("handleQuery() Lost tie break: new unique name chosen:" + info.getName());
+
+ // We revert the state to start probing again with the new name
+ info.revertState();
+ }
+ else
+ {
+ // We won the tie break, so this conflicting probe should be ignored
+ // See paragraph 3 of section 9.2 in mdns draft spec
+ return false;
+ }
+
+ return true;
+
+ }
+ return false;
+ }
+
+ boolean handleResponse(JmDNSImpl dns)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(name.toLowerCase());
+ if (info != null
+ && (port != info.port || !server.equalsIgnoreCase(dns.getLocalHost().getName())))
+ {
+ logger.finer("handleResponse() Denial detected");
+
+ if (info.getState().isProbing())
+ {
+ String oldName = info.getQualifiedName().toLowerCase();
+ info.setName(dns.incrementName(info.getName()));
+ dns.getServices().remove(oldName);
+ dns.getServices().put(info.getQualifiedName().toLowerCase(), info);
+ logger.finer("handleResponse() New unique name chose:" + info.getName());
+
+ }
+ info.revertState();
+ return true;
+ }
+ return false;
+ }
+
+ DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(name.toLowerCase());
+ if (info != null)
+ {
+ if (this.port == info.port != server.equals(dns.getLocalHost().getName()))
+ {
+ return dns.addAnswer(in, addr, port, out,
+ new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV,
+ DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE,
+ DNSConstants.DNS_TTL, info.priority,
+ info.weight, info.port, dns.getLocalHost().getName()));
+ }
+ }
+ return out;
+ }
+
+ public String toString()
+ {
+ return toString(server + ":" + port);
+ }
+ }
+
+ public void setRecordSource(InetAddress source)
+ {
+ this.source = source;
+ }
+
+ public InetAddress getRecordSource()
+ {
+ return source;
+ }
+
+ public String toString(String other)
+ {
+ return toString("record", ttl + "/" + getRemainingTTL(System.currentTimeMillis()) + "," + other);
+ }
+
+ public void setTtl(int ttl)
+ {
+ this.ttl = ttl;
+ }
+
+ public int getTtl()
+ {
+ return ttl;
+ }
+}
+
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSState.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSState.java
new file mode 100644
index 000000000..d7256f230
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/DNSState.java
@@ -0,0 +1,111 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl;
+
+import java.util.ArrayList;
+import java.util.logging.Logger;
+
+/**
+ * DNSState defines the possible states for services registered with JmDNS.
+ *
+ * @author Werner Randelshofer, Rick Blair
+ * @version 1.0 May 23, 2004 Created.
+ */
+public class DNSState implements Comparable
+{
+ private static Logger logger = Logger.getLogger(DNSState.class.getName());
+
+ private final String name;
+
+ /**
+ * Ordinal of next state to be created.
+ */
+ private static int nextOrdinal = 0;
+ /**
+ * Assign an ordinal to this state.
+ */
+ private final int ordinal = nextOrdinal++;
+ /**
+ * Logical sequence of states.
+ * The sequence is consistent with the ordinal of a state.
+ * This is used for advancing through states.
+ */
+ private final static ArrayList sequence = new ArrayList();
+
+ private DNSState(String name)
+ {
+ this.name = name;
+ sequence.add(this);
+ }
+
+ public final String toString()
+ {
+ return name;
+ }
+
+ public static final DNSState PROBING_1 = new DNSState("probing 1");
+ public static final DNSState PROBING_2 = new DNSState("probing 2");
+ public static final DNSState PROBING_3 = new DNSState("probing 3");
+ public static final DNSState ANNOUNCING_1 = new DNSState("announcing 1");
+ public static final DNSState ANNOUNCING_2 = new DNSState("announcing 2");
+ public static final DNSState ANNOUNCED = new DNSState("announced");
+ public static final DNSState CANCELED = new DNSState("canceled");
+
+ /**
+ * Returns the next advanced state.
+ * In general, this advances one step in the following sequence: PROBING_1,
+ * PROBING_2, PROBING_3, ANNOUNCING_1, ANNOUNCING_2, ANNOUNCED.
+ * Does not advance for ANNOUNCED and CANCELED state.
+ */
+ public final DNSState advance()
+ {
+ return (isProbing() || isAnnouncing()) ? (DNSState) sequence.get(ordinal + 1) : this;
+ }
+
+ /**
+ * Returns to the next reverted state.
+ * All states except CANCELED revert to PROBING_1.
+ * Status CANCELED does not revert.
+ */
+ public final DNSState revert()
+ {
+ return (this == CANCELED) ? this : PROBING_1;
+ }
+
+ /**
+ * Returns true, if this is a probing state.
+ */
+ public boolean isProbing()
+ {
+ return compareTo(PROBING_1) >= 0 && compareTo(PROBING_3) <= 0;
+ }
+
+ /**
+ * Returns true, if this is an announcing state.
+ */
+ public boolean isAnnouncing()
+ {
+ return compareTo(ANNOUNCING_1) >= 0 && compareTo(ANNOUNCING_2) <= 0;
+ }
+
+ /**
+ * Returns true, if this is an announced state.
+ */
+ public boolean isAnnounced()
+ {
+ return compareTo(ANNOUNCED) == 0;
+ }
+
+ /**
+ * Compares two states.
+ * The states compare as follows:
+ * PROBING_1 &lt; PROBING_2 &lt; PROBING_3 &lt; ANNOUNCING_1 &lt;
+ * ANNOUNCING_2 &lt; RESPONDING &lt; ANNOUNCED &lt; CANCELED.
+ */
+ public int compareTo(Object o)
+ {
+ return ordinal - ((DNSState) o).ordinal;
+ }
+} \ No newline at end of file
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/HostInfo.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/HostInfo.java
new file mode 100644
index 000000000..839feb333
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/HostInfo.java
@@ -0,0 +1,172 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+
+
+package javax.jmdns.impl;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * HostInfo information on the local host to be able to cope with change of addresses.
+ *
+ * @version %I%, %G%
+ * @author Pierre Frisch, Werner Randelshofer
+ */
+public class HostInfo
+{
+ private static Logger logger = Logger.getLogger(HostInfo.class.getName());
+ protected String name;
+ protected InetAddress address;
+ protected NetworkInterface interfaze;
+ /**
+ * This is used to create a unique name for the host name.
+ */
+ private int hostNameCount;
+
+ public HostInfo(InetAddress address, String name)
+ {
+ super();
+ this.address = address;
+ this.name = name;
+ if (address != null)
+ {
+ try
+ {
+ interfaze = NetworkInterface.getByInetAddress(address);
+ }
+ catch (Exception exception)
+ {
+ // FIXME Shouldn't we take an action here?
+ logger.log(Level.WARNING, "LocalHostInfo() exception ", exception);
+ }
+ }
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public InetAddress getAddress()
+ {
+ return address;
+ }
+
+ public NetworkInterface getInterface()
+ {
+ return interfaze;
+ }
+
+ synchronized String incrementHostName()
+ {
+ hostNameCount++;
+ int plocal = name.indexOf(".local.");
+ int punder = name.lastIndexOf("-");
+ name = name.substring(0, (punder == -1 ? plocal : punder)) + "-" + hostNameCount + ".local.";
+ return name;
+ }
+
+ boolean shouldIgnorePacket(DatagramPacket packet)
+ {
+ boolean result = false;
+ if (getAddress() != null)
+ {
+ InetAddress from = packet.getAddress();
+ if (from != null)
+ {
+ if (from.isLinkLocalAddress() && (!getAddress().isLinkLocalAddress()))
+ {
+ // Ignore linklocal packets on regular interfaces, unless this is
+ // also a linklocal interface. This is to avoid duplicates. This is
+ // a terrible hack caused by the lack of an API to get the address
+ // of the interface on which the packet was received.
+ result = true;
+ }
+ if (from.isLoopbackAddress() && (!getAddress().isLoopbackAddress()))
+ {
+ // Ignore loopback packets on a regular interface unless this is
+ // also a loopback interface.
+ result = true;
+ }
+ }
+ }
+ return result;
+ }
+
+ DNSRecord.Address getDNSAddressRecord(DNSRecord.Address address)
+ {
+ return (DNSConstants.TYPE_AAAA == address.type ? getDNS6AddressRecord() : getDNS4AddressRecord());
+ }
+
+ public DNSRecord.Address getDNS4AddressRecord()
+ {
+ if ((getAddress() != null) &&
+ ((getAddress() instanceof Inet4Address) ||
+ ((getAddress() instanceof Inet6Address) && (((Inet6Address) getAddress()).isIPv4CompatibleAddress()))))
+ {
+ return new DNSRecord.Address(getName(), DNSConstants.TYPE_A, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, getAddress());
+ }
+ return null;
+ }
+
+ public DNSRecord.Address getDNS6AddressRecord()
+ {
+ if ((getAddress() != null) && (getAddress() instanceof Inet6Address))
+ {
+ return new DNSRecord.Address(getName(), DNSConstants.TYPE_AAAA, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, getAddress());
+ }
+ return null;
+ }
+
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append("local host info[");
+ buf.append(getName() != null ? getName() : "no name");
+ buf.append(", ");
+ buf.append(getInterface() != null ? getInterface().getDisplayName() : "???");
+ buf.append(":");
+ buf.append(getAddress() != null ? getAddress().getHostAddress() : "no address");
+ buf.append("]");
+ return buf.toString();
+ }
+
+ public void addAddressRecords(DNSOutgoing out, boolean authoritative) throws IOException
+ {
+ DNSRecord answer = getDNS4AddressRecord();
+ if (answer != null)
+ {
+ if (authoritative)
+ {
+ out.addAuthorativeAnswer(answer);
+ }
+ else
+ {
+ out.addAnswer(answer, 0);
+ }
+ }
+
+ answer = getDNS6AddressRecord();
+ if (answer != null)
+ {
+ if (authoritative)
+ {
+ out.addAuthorativeAnswer(answer);
+ }
+ else
+ {
+ out.addAnswer(answer, 0);
+ }
+ }
+ }
+
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/JmDNSImpl.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/JmDNSImpl.java
new file mode 100644
index 000000000..b637d30f6
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/JmDNSImpl.java
@@ -0,0 +1,1563 @@
+///Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.jmdns.JmDNS;
+import javax.jmdns.ServiceEvent;
+import javax.jmdns.ServiceInfo;
+import javax.jmdns.ServiceListener;
+import javax.jmdns.ServiceTypeListener;
+import javax.jmdns.impl.tasks.Announcer;
+import javax.jmdns.impl.tasks.Canceler;
+import javax.jmdns.impl.tasks.Prober;
+import javax.jmdns.impl.tasks.RecordReaper;
+import javax.jmdns.impl.tasks.Renewer;
+import javax.jmdns.impl.tasks.Responder;
+import javax.jmdns.impl.tasks.ServiceInfoResolver;
+import javax.jmdns.impl.tasks.ServiceResolver;
+import javax.jmdns.impl.tasks.TypeResolver;
+
+// REMIND: multiple IP addresses
+
+/**
+ * mDNS implementation in Java.
+ *
+ * @version %I%, %G%
+ * @author Arthur van Hoff, Rick Blair, Jeff Sonstein, Werner Randelshofer,
+ * Pierre Frisch, Scott Lewis
+ */
+public class JmDNSImpl extends JmDNS
+{
+ private static Logger logger = Logger.getLogger(JmDNSImpl.class.getName());
+
+ /**
+ * This is the multicast group, we are listening to for multicast DNS
+ * messages.
+ */
+ private InetAddress group;
+ /**
+ * This is our multicast socket.
+ */
+ private MulticastSocket socket;
+
+ /**
+ * Used to fix live lock problem on unregester.
+ */
+
+ private boolean closed = false;
+
+ /**
+ * Holds instances of JmDNS.DNSListener. Must by a synchronized collection,
+ * because it is updated from concurrent threads.
+ */
+ private List listeners;
+ /**
+ * Holds instances of ServiceListener's. Keys are Strings holding a fully
+ * qualified service type. Values are LinkedList's of ServiceListener's.
+ */
+ private Map serviceListeners;
+ /**
+ * Holds instances of ServiceTypeListener's.
+ */
+ private List typeListeners;
+
+ /**
+ * Cache for DNSEntry's.
+ */
+ private DNSCache cache;
+
+ /**
+ * This hashtable holds the services that have been registered. Keys are
+ * instances of String which hold an all lower-case version of the fully
+ * qualified service name. Values are instances of ServiceInfo.
+ */
+ Map services;
+
+ /**
+ * This hashtable holds the service types that have been registered or that
+ * have been received in an incoming datagram. Keys are instances of String
+ * which hold an all lower-case version of the fully qualified service type.
+ * Values hold the fully qualified service type.
+ */
+ Map serviceTypes;
+ /**
+ * This is the shutdown hook, we registered with the java runtime.
+ */
+ private Thread shutdown;
+
+ /**
+ * Handle on the local host
+ */
+ private HostInfo localHost;
+
+ private Thread incomingListener = null;
+
+ /**
+ * Throttle count. This is used to count the overall number of probes sent
+ * by JmDNS. When the last throttle increment happened .
+ */
+ private int throttle;
+ /**
+ * Last throttle increment.
+ */
+ private long lastThrottleIncrement;
+
+ /**
+ * The timer is used to dispatch all outgoing messages of JmDNS. It is also
+ * used to dispatch maintenance tasks for the DNS cache.
+ */
+ Timer timer;
+
+ /**
+ * The source for random values. This is used to introduce random delays in
+ * responses. This reduces the potential for collisions on the network.
+ */
+ private final static Random random = new Random();
+
+ /**
+ * This lock is used to coordinate processing of incoming and outgoing
+ * messages. This is needed, because the Rendezvous Conformance Test does
+ * not forgive race conditions.
+ */
+ private Object ioLock = new Object();
+
+ /**
+ * If an incoming package which needs an answer is truncated, we store it
+ * here. We add more incoming DNSRecords to it, until the JmDNS.Responder
+ * timer picks it up. Remind: This does not work well with multiple planned
+ * answers for packages that came in from different clients.
+ */
+ private DNSIncoming plannedAnswer;
+
+ // State machine
+ /**
+ * The state of JmDNS. <p/> For proper handling of concurrency, this
+ * variable must be changed only using methods advanceState(), revertState()
+ * and cancel().
+ */
+ private DNSState state = DNSState.PROBING_1;
+
+ /**
+ * Timer task associated to the host name. This is used to prevent from
+ * having multiple tasks associated to the host name at the same time.
+ */
+ private TimerTask task;
+
+ /**
+ * This hashtable is used to maintain a list of service types being
+ * collected by this JmDNS instance. The key of the hashtable is a service
+ * type name, the value is an instance of JmDNS.ServiceCollector.
+ *
+ * @see #list
+ */
+ private final HashMap serviceCollectors = new HashMap();
+
+ /**
+ * Create an instance of JmDNS.
+ */
+ public JmDNSImpl() throws IOException
+ {
+ logger.finer("JmDNS instance created");
+ try
+ {
+ final InetAddress addr = InetAddress.getLocalHost();
+ init(addr.isLoopbackAddress() ? null : addr, addr.getHostName()); // [
+ // PJYF
+ // Oct
+ // 14
+ // 2004
+ // ]
+ // Why
+ // do
+ // we
+ // disallow
+ // the
+ // loopback
+ // address
+ // ?
+ }
+ catch (final IOException e)
+ {
+ init(null, "computer");
+ }
+ }
+
+ /**
+ * Create an instance of JmDNS and bind it to a specific network interface
+ * given its IP-address.
+ */
+ public JmDNSImpl(InetAddress addr) throws IOException
+ {
+ try
+ {
+ init(addr, addr.getHostName());
+ }
+ catch (final IOException e)
+ {
+ init(null, "computer");
+ }
+ }
+
+ /**
+ * Initialize everything.
+ *
+ * @param address
+ * The interface to which JmDNS binds to.
+ * @param name
+ * The host name of the interface.
+ */
+ private void init(InetAddress address, String name) throws IOException
+ {
+ // A host name with "." is illegal. so strip off everything and append .
+ // local.
+ final int idx = name.indexOf(".");
+ if (idx > 0)
+ {
+ name = name.substring(0, idx);
+ }
+ name += ".local.";
+ // localHost to IP address binding
+ localHost = new HostInfo(address, name);
+
+ cache = new DNSCache(100);
+
+ listeners = Collections.synchronizedList(new ArrayList());
+ serviceListeners = new HashMap();
+ typeListeners = new ArrayList();
+
+ services = new Hashtable(20);
+ serviceTypes = new Hashtable(20);
+
+ // REMIND: If I could pass in a name for the Timer thread,
+ // I would pass' JmDNS.Timer'.
+ timer = new Timer();
+ new RecordReaper(this).start(timer);
+ shutdown = new Thread(new Shutdown(), "JmDNS.Shutdown");
+ Runtime.getRuntime().addShutdownHook(shutdown);
+
+ incomingListener = new Thread(new SocketListener(this), "JmDNS.SocketListener");
+
+ // Bind to multicast socket
+ openMulticastSocket(getLocalHost());
+ start(getServices().values());
+ }
+
+ private void start(Collection serviceInfos)
+ {
+ setState(DNSState.PROBING_1);
+ incomingListener.start();
+ new Prober(this).start(timer);
+ for (final Iterator iterator = serviceInfos.iterator(); iterator.hasNext();)
+ {
+ try
+ {
+ registerService(new ServiceInfoImpl((ServiceInfoImpl) iterator.next()));
+ }
+ catch (final Exception exception)
+ {
+ logger.log(Level.WARNING, "start() Registration exception ", exception);
+ }
+ }
+ }
+
+ private void openMulticastSocket(HostInfo hostInfo) throws IOException
+ {
+ if (group == null)
+ {
+ group = InetAddress.getByName(DNSConstants.MDNS_GROUP);
+ }
+ if (socket != null)
+ {
+ this.closeMulticastSocket();
+ }
+ socket = new MulticastSocket(DNSConstants.MDNS_PORT);
+ if ((hostInfo != null) && (localHost.getInterface() != null))
+ {
+ socket.setNetworkInterface(hostInfo.getInterface());
+ }
+ socket.setTimeToLive(255);
+ socket.joinGroup(group);
+ }
+
+ private void closeMulticastSocket()
+ {
+ logger.finer("closeMulticastSocket()");
+ if (socket != null)
+ {
+ // close socket
+ try
+ {
+ socket.leaveGroup(group);
+ socket.close();
+ if (incomingListener != null)
+ {
+ incomingListener.join();
+ }
+ }
+ catch (final Exception exception)
+ {
+ logger.log(Level.WARNING, "closeMulticastSocket() Close socket exception ",
+ exception);
+ }
+ socket = null;
+ }
+ }
+
+ // State machine
+ /**
+ * Sets the state and notifies all objects that wait on JmDNS.
+ */
+ public synchronized void advanceState()
+ {
+ setState(getState().advance());
+ notifyAll();
+ }
+
+ /**
+ * Sets the state and notifies all objects that wait on JmDNS.
+ */
+ synchronized void revertState()
+ {
+ setState(getState().revert());
+ notifyAll();
+ }
+
+ /**
+ * Sets the state and notifies all objects that wait on JmDNS.
+ */
+ synchronized void cancel()
+ {
+ setState(DNSState.CANCELED);
+ notifyAll();
+ }
+
+ /**
+ * Returns the current state of this info.
+ */
+ public DNSState getState()
+ {
+ return state;
+ }
+
+ /**
+ * Return the DNSCache associated with the cache variable
+ */
+ public DNSCache getCache()
+ {
+ return cache;
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#getHostName()
+ */
+ public String getHostName()
+ {
+ return localHost.getName();
+ }
+
+ public HostInfo getLocalHost()
+ {
+ return localHost;
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#getInterface()
+ */
+ public InetAddress getInterface() throws IOException
+ {
+ return socket.getInterface();
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#getServiceInfo(java.lang.String, java.lang.String)
+ */
+ public ServiceInfo getServiceInfo(String type, String name)
+ {
+ return getServiceInfo(type, name, 3 * 1000);
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#getServiceInfo(java.lang.String, java.lang.String,
+ * int)
+ */
+ public ServiceInfo getServiceInfo(String type, String name, int timeout)
+ {
+ final ServiceInfoImpl info = new ServiceInfoImpl(type, name);
+ new ServiceInfoResolver(this, info).start(timer);
+
+ try
+ {
+ final long end = System.currentTimeMillis() + timeout;
+ long delay;
+ synchronized (info)
+ {
+ while (!info.hasData() && (delay = end - System.currentTimeMillis()) > 0)
+ {
+ info.wait(delay);
+ }
+ }
+ }
+ catch (final InterruptedException e)
+ {
+ // empty
+ }
+
+ return (info.hasData()) ? info : null;
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#requestServiceInfo(java.lang.String,
+ * java.lang.String)
+ */
+ public void requestServiceInfo(String type, String name)
+ {
+ requestServiceInfo(type, name, 3 * 1000);
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#requestServiceInfo(java.lang.String,
+ * java.lang.String, int)
+ */
+ public void requestServiceInfo(String type, String name, int timeout)
+ {
+ registerServiceType(type);
+ final ServiceInfoImpl info = new ServiceInfoImpl(type, name);
+ new ServiceInfoResolver(this, info).start(timer);
+
+ try
+ {
+ final long end = System.currentTimeMillis() + timeout;
+ long delay;
+ synchronized (info)
+ {
+ while (!info.hasData() && (delay = end - System.currentTimeMillis()) > 0)
+ {
+ info.wait(delay);
+ }
+ }
+ }
+ catch (final InterruptedException e)
+ {
+ // empty
+ }
+ }
+
+ void handleServiceResolved(ServiceInfoImpl info)
+ {
+ List list = null;
+ ArrayList listCopy = null;
+ synchronized (serviceListeners)
+ {
+ list = (List) serviceListeners.get(info.type.toLowerCase());
+
+ if (list != null)
+ {
+ listCopy = new ArrayList(list);
+ }
+ }
+ if (listCopy != null)
+ {
+ final ServiceEvent event = new ServiceEventImpl(this, info.type, info.getName(), info);
+ for (final Iterator iterator = listCopy.iterator(); iterator.hasNext();)
+ {
+ ((ServiceListener) iterator.next()).serviceResolved(event);
+ }
+ }
+ }
+
+ /**
+ * @see
+ * javax.jmdns.JmDNS#addServiceTypeListener(javax.jmdns.ServiceTypeListener
+ * )
+ */
+ public void addServiceTypeListener(ServiceTypeListener listener) throws IOException
+ {
+ synchronized (this)
+ {
+ typeListeners.remove(listener);
+ typeListeners.add(listener);
+ }
+
+ // report cached service types
+ for (final Iterator iterator = serviceTypes.values().iterator(); iterator.hasNext();)
+ {
+ listener.serviceTypeAdded(new ServiceEventImpl(this, (String) iterator.next(), null,
+ null));
+ }
+
+ new TypeResolver(this).start(timer);
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#removeServiceTypeListener(javax.jmdns.
+ * ServiceTypeListener)
+ */
+ public void removeServiceTypeListener(ServiceTypeListener listener)
+ {
+ synchronized (this)
+ {
+ typeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#addServiceListener(java.lang.String,
+ * javax.jmdns.ServiceListener)
+ */
+ public void addServiceListener(String type, ServiceListener listener)
+ {
+ final String lotype = type.toLowerCase();
+ removeServiceListener(lotype, listener);
+ List list = null;
+
+ synchronized (serviceListeners)
+ {
+ list = (List) serviceListeners.get(lotype);
+ if (list == null)
+ {
+ list = Collections.synchronizedList(new LinkedList());
+ serviceListeners.put(lotype, list);
+ }
+ list.add(listener);
+ }
+
+ // report cached service types
+ final List serviceEvents = new ArrayList();
+ synchronized (cache)
+ {
+ for (final Iterator i = cache.iterator(); i.hasNext();)
+ {
+ for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n = n.next())
+ {
+ final DNSRecord rec = (DNSRecord) n.getValue();
+ if (rec.type == DNSConstants.TYPE_SRV)
+ {
+ if (rec.name.endsWith(type))
+ {
+ serviceEvents.add(new ServiceEventImpl(this, type, toUnqualifiedName(
+ type, rec.name), null));
+ }
+ }
+ }
+ }
+ }
+ // Actually call listener with all service events added above
+ for (final Iterator i = serviceEvents.iterator(); i.hasNext();)
+ {
+ listener.serviceAdded((ServiceEventImpl) i.next());
+ }
+ // Create/start ServiceResolver
+ new ServiceResolver(this, type).start(timer);
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#removeServiceListener(java.lang.String,
+ * javax.jmdns.ServiceListener)
+ */
+ public void removeServiceListener(String type, ServiceListener listener)
+ {
+ type = type.toLowerCase();
+ List list = null;
+ synchronized (serviceListeners)
+ {
+ list = (List) serviceListeners.get(type);
+ if (list != null)
+ {
+ list.remove(listener);
+ if (list.size() == 0)
+ {
+ serviceListeners.remove(type);
+ }
+ }
+ }
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#registerService(javax.jmdns.ServiceInfo)
+ */
+ public void registerService(ServiceInfo infoAbstract) throws IOException
+ {
+ final ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract;
+
+ registerServiceType(info.type);
+
+ // bind the service to this address
+ info.server = localHost.getName();
+ info.addr = localHost.getAddress();
+
+ synchronized (this)
+ {
+ makeServiceNameUnique(info);
+ services.put(info.getQualifiedName().toLowerCase(), info);
+ }
+
+ new /* Service */Prober(this).start(timer);
+ try
+ {
+ synchronized (info)
+ {
+ while (info.getState().compareTo(DNSState.ANNOUNCED) < 0)
+ {
+ info.wait();
+ }
+ }
+ }
+ catch (final InterruptedException e)
+ {
+ // empty
+ }
+ logger.fine("registerService() JmDNS registered service as " + info);
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#unregisterService(javax.jmdns.ServiceInfo)
+ */
+ public void unregisterService(ServiceInfo infoAbstract)
+ {
+ final ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract;
+ synchronized (this)
+ {
+ services.remove(info.getQualifiedName().toLowerCase());
+ }
+ info.cancel();
+
+ // Note: We use this lock object to synchronize on it.
+ // Synchronizing on another object (e.g. the ServiceInfo) does
+ // not make sense, because the sole purpose of the lock is to
+ // wait until the canceler has finished. If we synchronized on
+ // the ServiceInfo or on the Canceler, we would block all
+ // accesses to synchronized methods on that object. This is not
+ // what we want!
+ final Object lock = new Object();
+ new Canceler(this, info, lock).start(timer);
+
+ // Remind: We get a deadlock here, if the Canceler does not run!
+ try
+ {
+ synchronized (lock)
+ {
+ lock.wait();
+ }
+ }
+ catch (final InterruptedException e)
+ {
+ // empty
+ }
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#unregisterAllServices()
+ */
+ public void unregisterAllServices()
+ {
+ logger.finer("unregisterAllServices()");
+ if (services.size() == 0)
+ {
+ return;
+ }
+
+ Collection list;
+ synchronized (this)
+ {
+ list = new LinkedList(services.values());
+ services.clear();
+ }
+ for (final Iterator iterator = list.iterator(); iterator.hasNext();)
+ {
+ ((ServiceInfoImpl) iterator.next()).cancel();
+ }
+
+ final Object lock = new Object();
+ new Canceler(this, list, lock).start(timer);
+ // Remind: We get a livelock here, if the Canceler does not run!
+ try
+ {
+ synchronized (lock)
+ {
+ if (!closed)
+ {
+ lock.wait();
+ }
+ }
+ }
+ catch (final InterruptedException e)
+ {
+ // empty
+ }
+
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#registerServiceType(java.lang.String)
+ */
+ public void registerServiceType(String type)
+ {
+ final String name = type.toLowerCase();
+ if (serviceTypes.get(name) == null)
+ {
+ if ((type.indexOf("._mdns._udp.") < 0) && !type.endsWith(".in-addr.arpa."))
+ {
+ Collection list;
+ synchronized (this)
+ {
+ serviceTypes.put(name, type);
+ list = new LinkedList(typeListeners);
+ }
+ for (final Iterator iterator = list.iterator(); iterator.hasNext();)
+ {
+ ((ServiceTypeListener) iterator.next()).serviceTypeAdded(new ServiceEventImpl(
+ this, type, null, null));
+ }
+ }
+ }
+ }
+
+ /**
+ * Generate a possibly unique name for a host using the information we have
+ * in the cache.
+ *
+ * @return returns true, if the name of the host had to be changed.
+ */
+ private boolean makeHostNameUnique(DNSRecord.Address host)
+ {
+ final String originalName = host.getName();
+ System.currentTimeMillis();
+
+ boolean collision;
+ do
+ {
+ collision = false;
+
+ // Check for collision in cache
+ for (DNSCache.CacheNode j = cache.find(host.getName().toLowerCase()); j != null; j = j
+ .next())
+ {
+ if (false)
+ {
+ host.name = incrementName(host.getName());
+ collision = true;
+ break;
+ }
+ }
+ }
+ while (collision);
+
+ if (originalName.equals(host.getName()))
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ /**
+ * Generate a possibly unique name for a service using the information we
+ * have in the cache.
+ *
+ * @return returns true, if the name of the service info had to be changed.
+ */
+ private boolean makeServiceNameUnique(ServiceInfoImpl info)
+ {
+ final String originalQualifiedName = info.getQualifiedName();
+ final long now = System.currentTimeMillis();
+
+ boolean collision;
+ do
+ {
+ collision = false;
+
+ // Check for collision in cache
+ for (DNSCache.CacheNode j = cache.find(info.getQualifiedName().toLowerCase()); j != null; j = j
+ .next())
+ {
+ final DNSRecord a = (DNSRecord) j.getValue();
+ if ((a.type == DNSConstants.TYPE_SRV) && !a.isExpired(now))
+ {
+ final DNSRecord.Service s = (DNSRecord.Service) a;
+ if (s.port != info.port || !s.server.equals(localHost.getName()))
+ {
+ logger
+ .finer("makeServiceNameUnique() JmDNS.makeServiceNameUnique srv collision:"
+ + a
+ + " s.server="
+ + s.server
+ + " "
+ + localHost.getName()
+ + " equals:" + (s.server.equals(localHost.getName())));
+ info.setName(incrementName(info.getName()));
+ collision = true;
+ break;
+ }
+ }
+ }
+
+ // Check for collision with other service infos published by JmDNS
+ final Object selfService = services.get(info.getQualifiedName().toLowerCase());
+ if (selfService != null && selfService != info)
+ {
+ info.setName(incrementName(info.getName()));
+ collision = true;
+ }
+ }
+ while (collision);
+
+ return !(originalQualifiedName.equals(info.getQualifiedName()));
+ }
+
+ String incrementName(String name)
+ {
+ try
+ {
+ final int l = name.lastIndexOf('(');
+ final int r = name.lastIndexOf(')');
+ if ((l >= 0) && (l < r))
+ {
+ name = name.substring(0, l) + "("
+ + (Integer.parseInt(name.substring(l + 1, r)) + 1) + ")";
+ }
+ else
+ {
+ name += " (2)";
+ }
+ }
+ catch (final NumberFormatException e)
+ {
+ name += " (2)";
+ }
+ return name;
+ }
+
+ /**
+ * Add a listener for a question. The listener will receive updates of
+ * answers to the question as they arrive, or from the cache if they are
+ * already available.
+ */
+ public void addListener(DNSListener listener, DNSQuestion question)
+ {
+ final long now = System.currentTimeMillis();
+
+ // add the new listener
+ synchronized (this)
+ {
+ listeners.add(listener);
+ }
+
+ // report existing matched records
+ if (question != null)
+ {
+ for (DNSCache.CacheNode i = cache.find(question.name); i != null; i = i.next())
+ {
+ final DNSRecord c = (DNSRecord) i.getValue();
+ if (question.answeredBy(c) && !c.isExpired(now))
+ {
+ listener.updateRecord(this, now, c);
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove a listener from all outstanding questions. The listener will no
+ * longer receive any updates.
+ */
+ public void removeListener(DNSListener listener)
+ {
+ synchronized (this)
+ {
+ listeners.remove(listener);
+ }
+ }
+
+ // Remind: Method updateRecord should receive a better name.
+ /**
+ * Notify all listeners that a record was updated.
+ */
+ public void updateRecord(long now, DNSRecord rec)
+ {
+ // We do not want to block the entire DNS while we are updating the
+ // record for each listener (service info)
+ List listenerList = null;
+ synchronized (this)
+ {
+ listenerList = new ArrayList(listeners);
+ }
+ for (final Iterator iterator = listenerList.iterator(); iterator.hasNext();)
+ {
+ final DNSListener listener = (DNSListener) iterator.next();
+ listener.updateRecord(this, now, rec);
+ }
+ if (rec.type == DNSConstants.TYPE_PTR || rec.type == DNSConstants.TYPE_SRV)
+ {
+ List serviceListenerList = null;
+ synchronized (serviceListeners)
+ {
+ serviceListenerList = (List) serviceListeners.get(rec.name.toLowerCase());
+ // Iterate on a copy in case listeners will modify it
+ if (serviceListenerList != null)
+ {
+ serviceListenerList = new ArrayList(serviceListenerList);
+ }
+ }
+ if (serviceListenerList != null)
+ {
+ final boolean expired = rec.isExpired(now);
+ final String type = rec.getName();
+ final String name = ((DNSRecord.Pointer) rec).getAlias();
+ // DNSRecord old = (DNSRecord)services.get(name.toLowerCase());
+ if (!expired)
+ {
+ // new record
+ final ServiceEvent event = new ServiceEventImpl(this, type, toUnqualifiedName(
+ type, name), null);
+ for (final Iterator iterator = serviceListenerList.iterator(); iterator
+ .hasNext();)
+ {
+ ((ServiceListener) iterator.next()).serviceAdded(event);
+ }
+ }
+ else
+ {
+ // expire record
+ final ServiceEvent event = new ServiceEventImpl(this, type, toUnqualifiedName(
+ type, name), null);
+ for (final Iterator iterator = serviceListenerList.iterator(); iterator
+ .hasNext();)
+ {
+ ((ServiceListener) iterator.next()).serviceRemoved(event);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Handle an incoming response. Cache answers, and pass them on to the
+ * appropriate questions.
+ */
+ void handleResponse(DNSIncoming msg) throws IOException
+ {
+ final long now = System.currentTimeMillis();
+
+ boolean hostConflictDetected = false;
+ boolean serviceConflictDetected = false;
+
+ for (final Iterator i = msg.answers.iterator(); i.hasNext();)
+ {
+ boolean isInformative = false;
+ DNSRecord rec = (DNSRecord) i.next();
+ final boolean expired = rec.isExpired(now);
+
+ // update the cache
+ final DNSRecord c = (DNSRecord) cache.get(rec);
+ if (c != null)
+ {
+ if (expired)
+ {
+ isInformative = true;
+ cache.remove(c);
+ }
+ else
+ {
+ c.resetTTL(rec);
+ rec = c;
+ }
+ }
+ else
+ {
+ if (!expired)
+ {
+ isInformative = true;
+ cache.add(rec);
+ }
+ }
+ switch (rec.type)
+ {
+ case DNSConstants.TYPE_PTR:
+ // handle _mdns._udp records
+ if (rec.getName().indexOf("._mdns._udp.") >= 0)
+ {
+ if (!expired && rec.name.startsWith("_services._mdns._udp."))
+ {
+ isInformative = true;
+ registerServiceType(((DNSRecord.Pointer) rec).alias);
+ }
+ continue;
+ }
+ registerServiceType(rec.name);
+ break;
+ }
+
+ if ((rec.getType() == DNSConstants.TYPE_A) || (rec.getType() == DNSConstants.TYPE_AAAA))
+ {
+ hostConflictDetected |= rec.handleResponse(this);
+ }
+ else
+ {
+ serviceConflictDetected |= rec.handleResponse(this);
+ }
+
+ // notify the listeners
+ if (isInformative)
+ {
+ updateRecord(now, rec);
+ }
+ }
+
+ if (hostConflictDetected || serviceConflictDetected)
+ {
+ new Prober(this).start(timer);
+ }
+ }
+
+ /**
+ * Handle an incoming query. See if we can answer any part of it given our
+ * service infos.
+ */
+ void handleQuery(DNSIncoming in, InetAddress addr, int port) throws IOException
+ {
+ // Track known answers
+ boolean hostConflictDetected = false;
+ boolean serviceConflictDetected = false;
+ final long expirationTime = System.currentTimeMillis() + DNSConstants.KNOWN_ANSWER_TTL;
+ for (final Iterator i = in.answers.iterator(); i.hasNext();)
+ {
+ final DNSRecord answer = (DNSRecord) i.next();
+ if ((answer.getType() == DNSConstants.TYPE_A)
+ || (answer.getType() == DNSConstants.TYPE_AAAA))
+ {
+ hostConflictDetected |= answer.handleQuery(this, expirationTime);
+ }
+ else
+ {
+ serviceConflictDetected |= answer.handleQuery(this, expirationTime);
+ }
+ }
+
+ if (plannedAnswer != null)
+ {
+ plannedAnswer.append(in);
+ }
+ else
+ {
+ if (in.isTruncated())
+ {
+ plannedAnswer = in;
+ }
+
+ new Responder(this, in, addr, port).start();
+ }
+
+ if (hostConflictDetected || serviceConflictDetected)
+ {
+ new Prober(this).start(timer);
+ }
+ }
+
+ /**
+ * Add an answer to a question. Deal with the case when the outgoing packet
+ * overflows
+ */
+ public DNSOutgoing addAnswer(DNSIncoming in, InetAddress addr, int port, DNSOutgoing out,
+ DNSRecord rec) throws IOException
+ {
+ if (out == null)
+ {
+ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
+ }
+ try
+ {
+ out.addAnswer(in, rec);
+ }
+ catch (final IOException e)
+ {
+ out.flags |= DNSConstants.FLAGS_TC;
+ out.id = in.id;
+ out.finish();
+ send(out);
+
+ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
+ out.addAnswer(in, rec);
+ }
+ return out;
+ }
+
+ /**
+ * Send an outgoing multicast DNS message.
+ */
+ public void send(DNSOutgoing out) throws IOException
+ {
+ out.finish();
+ if (!out.isEmpty())
+ {
+ final DatagramPacket packet = new DatagramPacket(out.data, out.off, group,
+ DNSConstants.MDNS_PORT);
+
+ try
+ {
+ final DNSIncoming msg = new DNSIncoming(packet);
+ logger.finest("send() JmDNS out:" + msg.print(true));
+ }
+ catch (final IOException e)
+ {
+ logger.throwing(getClass().toString(),
+ "send(DNSOutgoing) - JmDNS can not parse what it sends!!!", e);
+ }
+ final MulticastSocket ms = socket;
+ if (ms != null && !ms.isClosed())
+ ms.send(packet);
+ }
+ }
+
+ public void startAnnouncer()
+ {
+ new Announcer(this).start(timer);
+ }
+
+ public void startRenewer()
+ {
+ new Renewer(this).start(timer);
+ }
+
+ public void schedule(TimerTask task, int delay)
+ {
+ timer.schedule(task, delay);
+ }
+
+ // REMIND: Why is this not an anonymous inner class?
+ /**
+ * Shutdown operations.
+ */
+ private class Shutdown implements Runnable
+ {
+ public void run()
+ {
+ shutdown = null;
+ close();
+ }
+ }
+
+ /**
+ * Recover jmdns when there is an error.
+ */
+ public void recover()
+ {
+ logger.finer("recover()");
+ // We have an IO error so lets try to recover if anything happens lets
+ // close it.
+ // This should cover the case of the IP address changing under our feet
+ if (DNSState.CANCELED != getState())
+ {
+ synchronized (this)
+ { // Synchronize only if we are not already in process to prevent
+ // dead locks
+ //
+ logger.finer("recover() Cleanning up");
+ // Stop JmDNS
+ setState(DNSState.CANCELED); // This protects against recursive
+ // calls
+
+ // We need to keep a copy for reregistration
+ final Collection oldServiceInfos = new ArrayList(getServices().values());
+
+ // Cancel all services
+ unregisterAllServices();
+ disposeServiceCollectors();
+ //
+ // close multicast socket
+ closeMulticastSocket();
+ //
+ cache.clear();
+ logger.finer("recover() All is clean");
+ //
+ // All is clear now start the services
+ //
+ try
+ {
+ openMulticastSocket(getLocalHost());
+ start(oldServiceInfos);
+ }
+ catch (final Exception exception)
+ {
+ logger.log(Level.WARNING, "recover() Start services exception ", exception);
+ }
+ logger.log(Level.WARNING, "recover() We are back!");
+ }
+ }
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#close()
+ */
+ public void close()
+ {
+ if (getState() != DNSState.CANCELED)
+ {
+ synchronized (this)
+ { // Synchronize only if we are not already in process to prevent
+ // dead locks
+ // Stop JmDNS
+ setState(DNSState.CANCELED); // This protects against recursive
+ // calls
+
+ unregisterAllServices();
+ disposeServiceCollectors();
+
+ // close socket
+ closeMulticastSocket();
+
+ // Stop the timer
+ timer.cancel();
+
+ // remove the shutdown hook
+ if (shutdown != null)
+ {
+ Runtime.getRuntime().removeShutdownHook(shutdown);
+ }
+
+ }
+ }
+ }
+
+ /**
+ * List cache entries, for debugging only.
+ */
+ void print()
+ {
+ System.out.println("---- cache ----");
+ cache.print();
+ System.out.println();
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#printServices()
+ */
+ public void printServices()
+ {
+ System.err.println(toString());
+ }
+
+ public String toString()
+ {
+ final StringBuffer aLog = new StringBuffer();
+ aLog.append("\t---- Services -----");
+ if (services != null)
+ {
+ for (final Iterator k = services.keySet().iterator(); k.hasNext();)
+ {
+ final Object key = k.next();
+ aLog.append("\n\t\tService: " + key + ": " + services.get(key));
+ }
+ }
+ aLog.append("\n");
+ aLog.append("\t---- Types ----");
+ if (serviceTypes != null)
+ {
+ for (final Iterator k = serviceTypes.keySet().iterator(); k.hasNext();)
+ {
+ final Object key = k.next();
+ aLog.append("\n\t\tType: " + key + ": " + serviceTypes.get(key));
+ }
+ }
+ aLog.append("\n");
+ aLog.append(cache.toString());
+ aLog.append("\n");
+ aLog.append("\t---- Service Collectors ----");
+ if (serviceCollectors != null)
+ {
+ synchronized (serviceCollectors)
+ {
+ for (final Iterator k = serviceCollectors.keySet().iterator(); k.hasNext();)
+ {
+ final Object key = k.next();
+ aLog.append("\n\t\tService Collector: " + key + ": "
+ + serviceCollectors.get(key));
+ }
+ serviceCollectors.clear();
+ }
+ }
+ return aLog.toString();
+ }
+
+ /**
+ * @see javax.jmdns.JmDNS#list(java.lang.String)
+ */
+ public ServiceInfo[] list(String type)
+ {
+ // Implementation note: The first time a list for a given type is
+ // requested, a ServiceCollector is created which collects service
+ // infos. This greatly speeds up the performance of subsequent calls
+ // to this method. The caveats are, that 1) the first call to this
+ // method
+ // for a given type is slow, and 2) we spawn a ServiceCollector
+ // instance for each service type which increases network traffic a
+ // little.
+
+ ServiceCollector collector;
+
+ boolean newCollectorCreated;
+ synchronized (serviceCollectors)
+ {
+ collector = (ServiceCollector) serviceCollectors.get(type);
+ if (collector == null)
+ {
+ collector = new ServiceCollector(type);
+ serviceCollectors.put(type, collector);
+ addServiceListener(type, collector);
+ newCollectorCreated = true;
+ }
+ else
+ {
+ newCollectorCreated = false;
+ }
+ }
+
+ // After creating a new ServiceCollector, we collect service infos for
+ // 200 milliseconds. This should be enough time, to get some service
+ // infos from the network.
+ if (newCollectorCreated)
+ {
+ try
+ {
+ Thread.sleep(200);
+ }
+ catch (final InterruptedException e)
+ {
+ }
+ }
+
+ return collector.list();
+ }
+
+ /**
+ * This method disposes all ServiceCollector instances which have been
+ * created by calls to method <code>list(type)</code>.
+ *
+ * @see #list
+ */
+ private void disposeServiceCollectors()
+ {
+ logger.finer("disposeServiceCollectors()");
+ synchronized (serviceCollectors)
+ {
+ for (final Iterator i = serviceCollectors.values().iterator(); i.hasNext();)
+ {
+ final ServiceCollector collector = (ServiceCollector) i.next();
+ removeServiceListener(collector.type, collector);
+ }
+ serviceCollectors.clear();
+ }
+ }
+
+ /**
+ * Instances of ServiceCollector are used internally to speed up the
+ * performance of method <code>list(type)</code>.
+ *
+ * @see #list
+ */
+ private static class ServiceCollector implements ServiceListener
+ {
+ private static Logger logger = Logger.getLogger(ServiceCollector.class.getName());
+ /**
+ * A set of collected service instance names.
+ */
+ private final Map infos = Collections.synchronizedMap(new HashMap());
+
+ public String type;
+
+ public ServiceCollector(String type)
+ {
+ this.type = type;
+ }
+
+ /**
+ * A service has been added.
+ */
+ public void serviceAdded(ServiceEvent event)
+ {
+ synchronized (infos)
+ {
+ event.getDNS().requestServiceInfo(event.getType(), event.getName(), 0);
+ }
+ }
+
+ /**
+ * A service has been removed.
+ */
+ public void serviceRemoved(ServiceEvent event)
+ {
+ synchronized (infos)
+ {
+ infos.remove(event.getName());
+ }
+ }
+
+ /**
+ * A service hase been resolved. Its details are now available in the
+ * ServiceInfo record.
+ */
+ public void serviceResolved(ServiceEvent event)
+ {
+ synchronized (infos)
+ {
+ infos.put(event.getName(), event.getInfo());
+ }
+ }
+
+ /**
+ * Returns an array of all service infos which have been collected by
+ * this ServiceCollector.
+ */
+ public ServiceInfoImpl[] list()
+ {
+ synchronized (infos)
+ {
+ return (ServiceInfoImpl[]) infos.values()
+ .toArray(new ServiceInfoImpl[infos.size()]);
+ }
+ }
+
+ public String toString()
+ {
+ final StringBuffer aLog = new StringBuffer();
+ synchronized (infos)
+ {
+ for (final Iterator k = infos.keySet().iterator(); k.hasNext();)
+ {
+ final Object key = k.next();
+ aLog.append("\n\t\tService: " + key + ": " + infos.get(key));
+ }
+ }
+ return aLog.toString();
+ }
+ };
+
+ private static String toUnqualifiedName(String type, String qualifiedName)
+ {
+ if (qualifiedName.endsWith(type))
+ {
+ return qualifiedName.substring(0, qualifiedName.length() - type.length() - 1);
+ }
+ else
+ {
+ return qualifiedName;
+ }
+ }
+
+ public void setState(DNSState state)
+ {
+ this.state = state;
+ }
+
+ public void setTask(TimerTask task)
+ {
+ this.task = task;
+ }
+
+ public TimerTask getTask()
+ {
+ return task;
+ }
+
+ public Map getServices()
+ {
+ return services;
+ }
+
+ public void setLastThrottleIncrement(long lastThrottleIncrement)
+ {
+ this.lastThrottleIncrement = lastThrottleIncrement;
+ }
+
+ public long getLastThrottleIncrement()
+ {
+ return lastThrottleIncrement;
+ }
+
+ public void setThrottle(int throttle)
+ {
+ this.throttle = throttle;
+ }
+
+ public int getThrottle()
+ {
+ return throttle;
+ }
+
+ public static Random getRandom()
+ {
+ return random;
+ }
+
+ public void setIoLock(Object ioLock)
+ {
+ this.ioLock = ioLock;
+ }
+
+ public Object getIoLock()
+ {
+ return ioLock;
+ }
+
+ public void setPlannedAnswer(DNSIncoming plannedAnswer)
+ {
+ this.plannedAnswer = plannedAnswer;
+ }
+
+ public DNSIncoming getPlannedAnswer()
+ {
+ return plannedAnswer;
+ }
+
+ void setLocalHost(HostInfo localHost)
+ {
+ this.localHost = localHost;
+ }
+
+ public Map getServiceTypes()
+ {
+ return serviceTypes;
+ }
+
+ public void setClosed(boolean closed)
+ {
+ this.closed = closed;
+ }
+
+ public boolean isClosed()
+ {
+ return closed;
+ }
+
+ public MulticastSocket getSocket()
+ {
+ return socket;
+ }
+
+ public InetAddress getGroup()
+ {
+ return group;
+ }
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/ServiceEventImpl.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/ServiceEventImpl.java
new file mode 100644
index 000000000..2d863d2c0
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/ServiceEventImpl.java
@@ -0,0 +1,99 @@
+///Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl;
+
+import java.util.logging.Logger;
+
+import javax.jmdns.JmDNS;
+import javax.jmdns.ServiceEvent;
+import javax.jmdns.ServiceInfo;
+
+/**
+ * ServiceEvent.
+ *
+ * @author Werner Randelshofer, Rick Blair
+ * @version %I%, %G%
+ */
+public class ServiceEventImpl extends ServiceEvent
+{
+ private static Logger logger = Logger.getLogger(ServiceEvent.class.getName());
+ /**
+ * The type name of the service.
+ */
+ private String type;
+ /**
+ * The instance name of the service. Or null, if the event was
+ * fired to a service type listener.
+ */
+ private String name;
+ /**
+ * The service info record, or null if the service could be be resolved.
+ * This is also null, if the event was fired to a service type listener.
+ */
+ private ServiceInfoImpl info;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param source the JmDNS instance which originated the event.
+ * @param type the type name of the service.
+ * @param name the instance name of the service.
+ * @param info the service info record, or null if the service could be be resolved.
+ */
+ public ServiceEventImpl(JmDNSImpl source, String type, String name, ServiceInfoImpl info)
+ {
+ super(source);
+ this.type = type;
+ this.name = name;
+ this.info = info;
+ }
+
+ /**
+ * @see javax.jmdns.ServiceEvent#getDNS()
+ */
+ public JmDNS getDNS()
+ {
+ return (JmDNS) getSource();
+ }
+
+ /**
+ * @see javax.jmdns.ServiceEvent#getType()
+ */
+ public String getType()
+ {
+ return type;
+ }
+
+ /**
+ * @see javax.jmdns.ServiceEvent#getName()
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * @see javax.jmdns.ServiceEvent#toString()
+ */
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append("<" + getClass().getName() + "> ");
+ buf.append(super.toString());
+ buf.append(" name ");
+ buf.append(getName());
+ buf.append(" type ");
+ buf.append(getType());
+ buf.append(" info ");
+ buf.append(getInfo());
+ return buf.toString();
+ }
+
+ public ServiceInfo getInfo()
+ {
+ return info;
+ }
+
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/ServiceInfoImpl.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/ServiceInfoImpl.java
new file mode 100644
index 000000000..e2c9064bd
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/ServiceInfoImpl.java
@@ -0,0 +1,668 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.TimerTask;
+import java.util.Vector;
+import java.util.logging.Logger;
+
+import javax.jmdns.ServiceInfo;
+import javax.jmdns.impl.DNSRecord.Pointer;
+import javax.jmdns.impl.DNSRecord.Service;
+import javax.jmdns.impl.DNSRecord.Text;
+
+/**
+ * JmDNS service information.
+ *
+ * @version %I%, %G%
+ * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer
+ */
+public class ServiceInfoImpl extends ServiceInfo implements DNSListener
+{
+ private static Logger logger = Logger.getLogger(ServiceInfoImpl.class.getName());
+ private JmDNSImpl dns;
+
+ // State machine
+ /**
+ * The state of this service info.
+ * This is used only for services announced by JmDNS.
+ * <p/>
+ * For proper handling of concurrency, this variable must be
+ * changed only using methods advanceState(), revertState() and cancel().
+ */
+ private DNSState state = DNSState.PROBING_1;
+
+ /**
+ * Task associated to this service info.
+ * Possible tasks are JmDNS.Prober, JmDNS.Announcer, JmDNS.Responder,
+ * JmDNS.Canceler.
+ */
+ private TimerTask task;
+
+ String type;
+ private String name;
+ String server;
+ int port;
+ int weight;
+ int priority;
+ private byte text[];
+ Hashtable props;
+ InetAddress addr;
+
+ /**
+ * @see javax.jmdns.ServiceInfo#create(String, String, int, String)
+ */
+ public ServiceInfoImpl(String type, String name, int port, String text)
+ {
+ this(type, name, port, 0, 0, text);
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, String)
+ */
+ public ServiceInfoImpl(String type, String name, int port, int weight, int priority, String text)
+ {
+ this(type, name, port, weight, priority, (byte[]) null);
+ try
+ {
+ ByteArrayOutputStream out = new ByteArrayOutputStream(text.length());
+ writeUTF(out, text);
+ byte [] data = out.toByteArray();
+ this.setText(new byte[data.length + 1]);
+ this.getText()[0] = (byte) data.length;
+ System.arraycopy(data, 0, this.getText(), 1, data.length);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, Hashtable)
+ */
+ public ServiceInfoImpl(String type, String name, int port, int weight, int priority, Hashtable props)
+ {
+ this(type, name, port, weight, priority, new byte[0]);
+ if (props != null)
+ {
+ try
+ {
+ ByteArrayOutputStream out = new ByteArrayOutputStream(256);
+ for (Enumeration e = props.keys(); e.hasMoreElements();)
+ {
+ String key = (String) e.nextElement();
+ Object val = props.get(key);
+ ByteArrayOutputStream out2 = new ByteArrayOutputStream(100);
+ writeUTF(out2, key);
+ if (val instanceof String)
+ {
+ out2.write('=');
+ writeUTF(out2, (String) val);
+ }
+ else
+ {
+ if (val instanceof byte[])
+ {
+ out2.write('=');
+ byte[] bval = (byte[]) val;
+ out2.write(bval, 0, bval.length);
+ }
+ else
+ {
+ if (val != NO_VALUE)
+ {
+ throw new IllegalArgumentException("invalid property value: " + val);
+ }
+ }
+ }
+ byte data[] = out2.toByteArray();
+ out.write(data.length);
+ out.write(data, 0, data.length);
+ }
+ this.setText(out.toByteArray());
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("unexpected exception: " + e);
+ }
+ }
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, byte[])
+ */
+ public ServiceInfoImpl(String type, String name, int port, int weight, int priority, byte text[])
+ {
+ this.type = type;
+ this.name = name;
+ this.port = port;
+ this.weight = weight;
+ this.priority = priority;
+ this.setText(text);
+ }
+
+ /**
+ * Construct a service record during service discovery.
+ */
+ ServiceInfoImpl(String type, String name)
+ {
+ if (!type.endsWith("."))
+ {
+ throw new IllegalArgumentException("type must be fully qualified DNS name ending in '.': " + type);
+ }
+
+ this.type = type;
+ this.name = name;
+ }
+
+ /**
+ * During recovery we need to duplicate service info to reregister them
+ */
+ ServiceInfoImpl(ServiceInfoImpl info)
+ {
+ if (info != null)
+ {
+ this.type = info.type;
+ this.name = info.name;
+ this.port = info.port;
+ this.weight = info.weight;
+ this.priority = info.priority;
+ this.setText(info.getText());
+ }
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getType()
+ */
+ public String getType()
+ {
+ return type;
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getName()
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Sets the service instance name.
+ *
+ * @param name unqualified service instance name, such as <code>foobar</code>
+ */
+ void setName(String name)
+ {
+ this.name = name;
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getQualifiedName()
+ */
+ public String getQualifiedName()
+ {
+ return name + "." + type;
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getServer()
+ */
+ public String getServer()
+ {
+ return server;
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getHostAddress()
+ */
+ public String getHostAddress()
+ {
+ return (addr != null ? addr.getHostAddress() : "");
+ }
+
+ public InetAddress getAddress()
+ {
+ return addr;
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getInetAddress()
+ */
+ public InetAddress getInetAddress()
+ {
+ return addr;
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getPort()
+ */
+ public int getPort()
+ {
+ return port;
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getPriority()
+ */
+ public int getPriority()
+ {
+ return priority;
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getWeight()
+ */
+ public int getWeight()
+ {
+ return weight;
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getTextBytes()
+ */
+ public byte[] getTextBytes()
+ {
+ return getText();
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getTextString()
+ */
+ public String getTextString()
+ {
+ if ((getText() == null) || (getText().length == 0) || ((getText().length == 1) && (getText()[0] == 0)))
+ {
+ return null;
+ }
+ return readUTF(getText(), 0, getText().length);
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getURL()
+ */
+ public String getURL()
+ {
+ return getURL("http");
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getURL(java.lang.String)
+ */
+ public String getURL(String protocol)
+ {
+ String url = protocol + "://" + getHostAddress() + ":" + getPort();
+ String path = getPropertyString("path");
+ if (path != null)
+ {
+ if (path.indexOf("://") >= 0)
+ {
+ url = path;
+ }
+ else
+ {
+ url += path.startsWith("/") ? path : "/" + path;
+ }
+ }
+ return url;
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getPropertyBytes(java.lang.String)
+ */
+ public synchronized byte[] getPropertyBytes(String name)
+ {
+ return (byte[]) getProperties().get(name);
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getPropertyString(java.lang.String)
+ */
+ public synchronized String getPropertyString(String name)
+ {
+ byte data[] = (byte[]) getProperties().get(name);
+ if (data == null)
+ {
+ return null;
+ }
+ if (data == NO_VALUE)
+ {
+ return "true";
+ }
+ return readUTF(data, 0, data.length);
+ }
+
+ /**
+ * @see javax.jmdns.ServiceInfo#getPropertyNames()
+ */
+ public Enumeration getPropertyNames()
+ {
+ Hashtable props = getProperties();
+ return (props != null) ? props.keys() : new Vector().elements();
+ }
+
+ /**
+ * Write a UTF string with a length to a stream.
+ */
+ void writeUTF(OutputStream out, String str) throws IOException
+ {
+ for (int i = 0, len = str.length(); i < len; i++)
+ {
+ int c = str.charAt(i);
+ if ((c >= 0x0001) && (c <= 0x007F))
+ {
+ out.write(c);
+ }
+ else
+ {
+ if (c > 0x07FF)
+ {
+ out.write(0xE0 | ((c >> 12) & 0x0F));
+ out.write(0x80 | ((c >> 6) & 0x3F));
+ out.write(0x80 | ((c >> 0) & 0x3F));
+ }
+ else
+ {
+ out.write(0xC0 | ((c >> 6) & 0x1F));
+ out.write(0x80 | ((c >> 0) & 0x3F));
+ }
+ }
+ }
+ }
+
+ /**
+ * Read data bytes as a UTF stream.
+ */
+ String readUTF(byte data[], int off, int len)
+ {
+ StringBuffer buf = new StringBuffer();
+ for (int end = off + len; off < end;)
+ {
+ int ch = data[off++] & 0xFF;
+ switch (ch >> 4)
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ // 0xxxxxxx
+ break;
+ case 12:
+ case 13:
+ if (off >= len)
+ {
+ return null;
+ }
+ // 110x xxxx 10xx xxxx
+ ch = ((ch & 0x1F) << 6) | (data[off++] & 0x3F);
+ break;
+ case 14:
+ if (off + 2 >= len)
+ {
+ return null;
+ }
+ // 1110 xxxx 10xx xxxx 10xx xxxx
+ ch = ((ch & 0x0f) << 12) | ((data[off++] & 0x3F) << 6) | (data[off++] & 0x3F);
+ break;
+ default:
+ if (off + 1 >= len)
+ {
+ return null;
+ }
+ // 10xx xxxx, 1111 xxxx
+ ch = ((ch & 0x3F) << 4) | (data[off++] & 0x0f);
+ break;
+ }
+ buf.append((char) ch);
+ }
+ return buf.toString();
+ }
+
+ synchronized Hashtable getProperties()
+ {
+ if ((props == null) && (getText() != null))
+ {
+ Hashtable props = new Hashtable();
+ int off = 0;
+ while (off < getText().length)
+ {
+ // length of the next key value pair
+ int len = getText()[off++] & 0xFF;
+ if ((len == 0) || (off + len > getText().length))
+ {
+ props.clear();
+ break;
+ }
+ // look for the '='
+ int i = 0;
+ for (; (i < len) && (getText()[off + i] != '='); i++)
+ {
+ ;
+ }
+
+ // get the property name
+ String name = readUTF(getText(), off, i);
+ if (name == null)
+ {
+ props.clear();
+ break;
+ }
+ if (i == len)
+ {
+ props.put(name, NO_VALUE);
+ }
+ else
+ {
+ byte value[] = new byte[len - ++i];
+ System.arraycopy(getText(), off + i, value, 0, len - i);
+ props.put(name, value);
+ off += len;
+ }
+ }
+ this.props = props;
+ }
+ return props;
+ }
+
+ /**
+ * JmDNS callback to update a DNS record.
+ */
+ public void updateRecord(JmDNSImpl jmdns, long now, DNSRecord rec)
+ {
+ if ((rec != null) && !rec.isExpired(now))
+ {
+ switch (rec.type)
+ {
+ case DNSConstants.TYPE_A: // IPv4
+ case DNSConstants.TYPE_AAAA: // IPv6 FIXME [PJYF Oct 14 2004] This has not been tested
+ if (rec.name.equals(server))
+ {
+ addr = ((DNSRecord.Address) rec).getAddress();
+
+ }
+ break;
+ case DNSConstants.TYPE_SRV:
+ if (rec.name.equals(getQualifiedName()))
+ {
+ DNSRecord.Service srv = (DNSRecord.Service) rec;
+ server = srv.server;
+ port = srv.port;
+ weight = srv.weight;
+ priority = srv.priority;
+ addr = null;
+ // changed to use getCache() instead - jeffs
+ // updateRecord(jmdns, now, (DNSRecord)jmdns.cache.get(server, TYPE_A, CLASS_IN));
+ updateRecord(jmdns, now, (DNSRecord) jmdns.getCache().get(server, DNSConstants.TYPE_A, DNSConstants.CLASS_IN));
+ }
+ break;
+ case DNSConstants.TYPE_TXT:
+ if (rec.name.equals(getQualifiedName()))
+ {
+ DNSRecord.Text txt = (DNSRecord.Text) rec;
+ setText(txt.text);
+ }
+ break;
+ }
+ // Future Design Pattern
+ // This is done, to notify the wait loop in method
+ // JmDNS.getServiceInfo(type, name, timeout);
+ if (hasData() && getDns() != null)
+ {
+ getDns().handleServiceResolved(this);
+ setDns(null);
+ }
+ synchronized (this)
+ {
+ notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Returns true if the service info is filled with data.
+ */
+ public boolean hasData()
+ {
+ return server != null && addr != null && getText() != null;
+ }
+
+
+ // State machine
+ /**
+ * Sets the state and notifies all objects that wait on the ServiceInfo.
+ */
+ public synchronized void advanceState()
+ {
+ state = state.advance();
+ notifyAll();
+ }
+
+ /**
+ * Sets the state and notifies all objects that wait on the ServiceInfo.
+ */
+ synchronized void revertState()
+ {
+ state = state.revert();
+ notifyAll();
+ }
+
+ /**
+ * Sets the state and notifies all objects that wait on the ServiceInfo.
+ */
+ synchronized void cancel()
+ {
+ state = DNSState.CANCELED;
+ notifyAll();
+ }
+
+ /**
+ * Returns the current state of this info.
+ */
+ public DNSState getState()
+ {
+ return state;
+ }
+
+
+ public int hashCode()
+ {
+ return getQualifiedName().hashCode();
+ }
+
+ public boolean equals(Object obj)
+ {
+ return (obj instanceof ServiceInfoImpl) && getQualifiedName().equals(((ServiceInfoImpl) obj).getQualifiedName());
+ }
+
+ public String getNiceTextString()
+ {
+ StringBuffer buf = new StringBuffer();
+ for (int i = 0, len = getText().length; i < len; i++)
+ {
+ if (i >= 20)
+ {
+ buf.append("...");
+ break;
+ }
+ int ch = getText()[i] & 0xFF;
+ if ((ch < ' ') || (ch > 127))
+ {
+ buf.append("\\0");
+ buf.append(Integer.toString(ch, 8));
+ }
+ else
+ {
+ buf.append((char) ch);
+ }
+ }
+ return buf.toString();
+ }
+
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append("service[");
+ buf.append(getQualifiedName());
+ buf.append(',');
+ buf.append(getAddress());
+ buf.append(':');
+ buf.append(port);
+ buf.append(',');
+ buf.append(getNiceTextString());
+ buf.append(']');
+ return buf.toString();
+ }
+
+ public void addAnswers(DNSOutgoing out, int ttl, HostInfo localHost) throws IOException
+ {
+ out.addAnswer(new Pointer(type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, ttl,
+ getQualifiedName()), 0);
+ out.addAnswer(new Service(getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN|DNSConstants.CLASS_UNIQUE,
+ ttl, priority, weight, port, localHost.getName()), 0);
+ out.addAnswer(new Text(getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN|DNSConstants.CLASS_UNIQUE,
+ ttl, getText()), 0);
+ }
+
+ public void setTask(TimerTask task)
+ {
+ this.task = task;
+ }
+
+ public TimerTask getTask()
+ {
+ return task;
+ }
+
+ public void setText(byte [] text)
+ {
+ this.text = text;
+ }
+
+ public byte [] getText()
+ {
+ return text;
+ }
+
+ public void setDns(JmDNSImpl dns)
+ {
+ this.dns = dns;
+ }
+
+ public JmDNSImpl getDns()
+ {
+ return dns;
+ }
+}
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/SocketListener.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/SocketListener.java
new file mode 100644
index 000000000..bfd161a7f
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/SocketListener.java
@@ -0,0 +1,87 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Listen for multicast packets.
+ */
+class SocketListener implements Runnable
+{
+ static Logger logger = Logger.getLogger(SocketListener.class.getName());
+
+ /**
+ *
+ */
+ private final JmDNSImpl jmDNSImpl;
+
+ /**
+ * @param jmDNSImpl
+ */
+ SocketListener(JmDNSImpl jmDNSImpl)
+ {
+ this.jmDNSImpl = jmDNSImpl;
+ }
+
+ public void run()
+ {
+ try
+ {
+ byte buf[] = new byte[DNSConstants.MAX_MSG_ABSOLUTE];
+ DatagramPacket packet = new DatagramPacket(buf, buf.length);
+ while (this.jmDNSImpl.getState() != DNSState.CANCELED)
+ {
+ packet.setLength(buf.length);
+ this.jmDNSImpl.getSocket().receive(packet);
+ if (this.jmDNSImpl.getState() == DNSState.CANCELED)
+ {
+ break;
+ }
+ try
+ {
+ if (this.jmDNSImpl.getLocalHost().shouldIgnorePacket(packet))
+ {
+ continue;
+ }
+
+ DNSIncoming msg = new DNSIncoming(packet);
+ logger.finest("SocketListener.run() JmDNS in:" + msg.print(true));
+
+ synchronized (this.jmDNSImpl.getIoLock())
+ {
+ if (msg.isQuery())
+ {
+ if (packet.getPort() != DNSConstants.MDNS_PORT)
+ {
+ this.jmDNSImpl.handleQuery(msg, packet.getAddress(), packet.getPort());
+ }
+ this.jmDNSImpl.handleQuery(msg, this.jmDNSImpl.getGroup(), DNSConstants.MDNS_PORT);
+ }
+ else
+ {
+ this.jmDNSImpl.handleResponse(msg);
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ logger.log(Level.WARNING, "run() exception ", e);
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ if (this.jmDNSImpl.getState() != DNSState.CANCELED)
+ {
+ logger.log(Level.WARNING, "run() exception ", e);
+ this.jmDNSImpl.recover();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Announcer.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Announcer.java
new file mode 100644
index 000000000..7cde56a91
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Announcer.java
@@ -0,0 +1,159 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl.tasks;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.jmdns.impl.DNSConstants;
+import javax.jmdns.impl.DNSOutgoing;
+import javax.jmdns.impl.DNSState;
+import javax.jmdns.impl.JmDNSImpl;
+import javax.jmdns.impl.ServiceInfoImpl;
+
+/**
+ * The Announcer sends an accumulated query of all announces, and advances
+ * the state of all serviceInfos, for which it has sent an announce.
+ * The Announcer also sends announcements and advances the state of JmDNS itself.
+ * <p/>
+ * When the announcer has run two times, it finishes.
+ */
+public class Announcer extends TimerTask
+{
+ static Logger logger = Logger.getLogger(Announcer.class.getName());
+
+ /**
+ *
+ */
+ private final JmDNSImpl jmDNSImpl;
+ /**
+ * The state of the announcer.
+ */
+ DNSState taskState = DNSState.ANNOUNCING_1;
+
+ public Announcer(JmDNSImpl jmDNSImpl)
+ {
+ this.jmDNSImpl = jmDNSImpl;
+ // Associate host to this, if it needs announcing
+ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCING_1)
+ {
+ this.jmDNSImpl.setTask(this);
+ }
+ // Associate services to this, if they need announcing
+ synchronized (this.jmDNSImpl)
+ {
+ for (Iterator s = this.jmDNSImpl.getServices().values().iterator(); s.hasNext();)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) s.next();
+ if (info.getState() == DNSState.ANNOUNCING_1)
+ {
+ info.setTask(this);
+ }
+ }
+ }
+ }
+
+ public void start(Timer timer)
+ {
+ timer.schedule(this, DNSConstants.ANNOUNCE_WAIT_INTERVAL, DNSConstants.ANNOUNCE_WAIT_INTERVAL);
+ }
+
+ public boolean cancel()
+ {
+ // Remove association from host to this
+ if (this.jmDNSImpl.getTask() == this)
+ {
+ this.jmDNSImpl.setTask(null);
+ }
+
+ // Remove associations from services to this
+ synchronized (this.jmDNSImpl)
+ {
+ for (Iterator i = this.jmDNSImpl.getServices().values().iterator(); i.hasNext();)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) i.next();
+ if (info.getTask() == this)
+ {
+ info.setTask(null);
+ }
+ }
+ }
+
+ return super.cancel();
+ }
+
+ public void run()
+ {
+ DNSOutgoing out = null;
+ try
+ {
+ // send probes for JmDNS itself
+ if (this.jmDNSImpl.getState() == taskState)
+ {
+ if (out == null)
+ {
+ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
+ }
+ this.jmDNSImpl.getLocalHost().addAddressRecords(out, false);
+ this.jmDNSImpl.advanceState();
+ }
+ // send announces for services
+ // Defensively copy the services into a local list,
+ // to prevent race conditions with methods registerService
+ // and unregisterService.
+ List list;
+ synchronized (this.jmDNSImpl)
+ {
+ list = new ArrayList(this.jmDNSImpl.getServices().values());
+ }
+ for (Iterator i = list.iterator(); i.hasNext();)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) i.next();
+ synchronized (info)
+ {
+ if (info.getState() == taskState && info.getTask() == this)
+ {
+ info.advanceState();
+ logger.finer("run() JmDNS announcing " + info.getQualifiedName() + " state " + info.getState());
+ if (out == null)
+ {
+ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
+ }
+ info.addAnswers(out, DNSConstants.DNS_TTL, this.jmDNSImpl.getLocalHost());
+ }
+ }
+ }
+ if (out != null)
+ {
+ logger.finer("run() JmDNS announcing #" + taskState);
+ this.jmDNSImpl.send(out);
+ }
+ else
+ {
+ // If we have nothing to send, another timer taskState ahead
+ // of us has done the job for us. We can cancel.
+ cancel();
+ }
+ }
+ catch (Throwable e)
+ {
+ logger.log(Level.WARNING, "run() exception ", e);
+ this.jmDNSImpl.recover();
+ }
+
+ taskState = taskState.advance();
+ if (!taskState.isAnnouncing())
+ {
+ cancel();
+
+ this.jmDNSImpl.startRenewer();
+ }
+ }
+} \ No newline at end of file
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Canceler.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Canceler.java
new file mode 100644
index 000000000..8501a870e
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Canceler.java
@@ -0,0 +1,117 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl.tasks;
+
+import java.util.Collection;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.jmdns.impl.DNSConstants;
+import javax.jmdns.impl.DNSOutgoing;
+import javax.jmdns.impl.DNSQuestion;
+import javax.jmdns.impl.JmDNSImpl;
+import javax.jmdns.impl.ServiceInfoImpl;
+
+/**
+ * The Canceler sends two announces with TTL=0 for the specified services.
+ */
+public class Canceler extends TimerTask
+{
+ static Logger logger = Logger.getLogger(Canceler.class.getName());
+
+ /**
+ *
+ */
+ private final JmDNSImpl jmDNSImpl;
+ /**
+ * Counts the number of announces being sent.
+ */
+ int count = 0;
+ /**
+ * The services that need cancelling.
+ * Note: We have to use a local variable here, because the services
+ * that are canceled, are removed immediately from variable JmDNS.services.
+ */
+ private ServiceInfoImpl[] infos;
+ /**
+ * We call notifyAll() on the lock object, when we have canceled the
+ * service infos.
+ * This is used by method JmDNS.unregisterService() and
+ * JmDNS.unregisterAllServices, to ensure that the JmDNS
+ * socket stays open until the Canceler has canceled all services.
+ * <p/>
+ * Note: We need this lock, because ServiceInfos do the transition from
+ * state ANNOUNCED to state CANCELED before we get here. We could get
+ * rid of this lock, if we added a state named CANCELLING to DNSState.
+ */
+ private Object lock;
+ int ttl = 0;
+
+ public Canceler(JmDNSImpl jmDNSImpl, ServiceInfoImpl info, Object lock)
+ {
+ this.jmDNSImpl = jmDNSImpl;
+ this.infos = new ServiceInfoImpl[]{info};
+ this.lock = lock;
+ this.jmDNSImpl.addListener(info, new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
+ }
+
+ public Canceler(JmDNSImpl jmDNSImpl, ServiceInfoImpl[] infos, Object lock)
+ {
+ this.jmDNSImpl = jmDNSImpl;
+ this.infos = infos;
+ this.lock = lock;
+ }
+
+ public Canceler(JmDNSImpl jmDNSImpl, Collection infos, Object lock)
+ {
+ this.jmDNSImpl = jmDNSImpl;
+ this.infos = (ServiceInfoImpl[]) infos.toArray(new ServiceInfoImpl[infos.size()]);
+ this.lock = lock;
+ }
+
+ public void start(Timer timer)
+ {
+ timer.schedule(this, 0, DNSConstants.ANNOUNCE_WAIT_INTERVAL);
+ }
+
+ public void run()
+ {
+ try
+ {
+ if (++count < 3)
+ {
+ logger.finer("run() JmDNS canceling service");
+ // announce the service
+ //long now = System.currentTimeMillis();
+ DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
+ for (int i = 0; i < infos.length; i++)
+ {
+ ServiceInfoImpl info = infos[i];
+ info.addAnswers(out, ttl, this.jmDNSImpl.getLocalHost());
+
+ this.jmDNSImpl.getLocalHost().addAddressRecords(out, false);
+ }
+ this.jmDNSImpl.send(out);
+ }
+ else
+ {
+ // After three successful announcements, we are finished.
+ synchronized (lock)
+ {
+ this.jmDNSImpl.setClosed(true);
+ lock.notifyAll();
+ }
+ this.cancel();
+ }
+ }
+ catch (Throwable e)
+ {
+ logger.log(Level.WARNING, "run() exception ", e);
+ this.jmDNSImpl.recover();
+ }
+ }
+} \ No newline at end of file
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Prober.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Prober.java
new file mode 100644
index 000000000..52365444e
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Prober.java
@@ -0,0 +1,197 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl.tasks;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.jmdns.impl.DNSConstants;
+import javax.jmdns.impl.DNSOutgoing;
+import javax.jmdns.impl.DNSQuestion;
+import javax.jmdns.impl.DNSRecord;
+import javax.jmdns.impl.DNSState;
+import javax.jmdns.impl.JmDNSImpl;
+import javax.jmdns.impl.ServiceInfoImpl;
+
+/**
+ * The Prober sends three consecutive probes for all service infos
+ * that needs probing as well as for the host name.
+ * The state of each service info of the host name is advanced, when a probe has
+ * been sent for it.
+ * When the prober has run three times, it launches an Announcer.
+ * <p/>
+ * If a conflict during probes occurs, the affected service infos (and affected
+ * host name) are taken away from the prober. This eventually causes the prober
+ * tho cancel itself.
+ */
+public class Prober extends TimerTask
+{
+ static Logger logger = Logger.getLogger(Prober.class.getName());
+
+ /**
+ *
+ */
+ private final JmDNSImpl jmDNSImpl;
+ /**
+ * The state of the prober.
+ */
+ DNSState taskState = DNSState.PROBING_1;
+
+ public Prober(JmDNSImpl jmDNSImpl)
+ {
+ this.jmDNSImpl = jmDNSImpl;
+ // Associate the host name to this, if it needs probing
+ if (this.jmDNSImpl.getState() == DNSState.PROBING_1)
+ {
+ this.jmDNSImpl.setTask(this);
+ }
+ // Associate services to this, if they need probing
+ synchronized (this.jmDNSImpl)
+ {
+ for (Iterator iterator = this.jmDNSImpl.getServices().values().iterator(); iterator.hasNext();)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) iterator.next();
+ if (info.getState() == DNSState.PROBING_1)
+ {
+ info.setTask(this);
+ }
+ }
+ }
+ }
+
+
+ public void start(Timer timer)
+ {
+ long now = System.currentTimeMillis();
+ if (now - this.jmDNSImpl.getLastThrottleIncrement() < DNSConstants.PROBE_THROTTLE_COUNT_INTERVAL)
+ {
+ this.jmDNSImpl.setThrottle(this.jmDNSImpl.getThrottle() + 1);
+ }
+ else
+ {
+ this.jmDNSImpl.setThrottle(1);
+ }
+ this.jmDNSImpl.setLastThrottleIncrement(now);
+
+ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED && this.jmDNSImpl.getThrottle() < DNSConstants.PROBE_THROTTLE_COUNT)
+ {
+ timer.schedule(this, JmDNSImpl.getRandom().nextInt(1 + DNSConstants.PROBE_WAIT_INTERVAL), DNSConstants.PROBE_WAIT_INTERVAL);
+ }
+ else
+ {
+ timer.schedule(this, DNSConstants.PROBE_CONFLICT_INTERVAL, DNSConstants.PROBE_CONFLICT_INTERVAL);
+ }
+ }
+
+ public boolean cancel()
+ {
+ // Remove association from host name to this
+ if (this.jmDNSImpl.getTask() == this)
+ {
+ this.jmDNSImpl.setTask(null);
+ }
+
+ // Remove associations from services to this
+ synchronized (this.jmDNSImpl)
+ {
+ for (Iterator i = this.jmDNSImpl.getServices().values().iterator(); i.hasNext();)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) i.next();
+ if (info.getTask() == this)
+ {
+ info.setTask(null);
+ }
+ }
+ }
+
+ return super.cancel();
+ }
+
+ public void run()
+ {
+ synchronized (this.jmDNSImpl.getIoLock())
+ {
+ DNSOutgoing out = null;
+ try
+ {
+ // send probes for JmDNS itself
+ if (this.jmDNSImpl.getState() == taskState && this.jmDNSImpl.getTask() == this)
+ {
+ if (out == null)
+ {
+ out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
+ }
+ out.addQuestion(new DNSQuestion(this.jmDNSImpl.getLocalHost().getName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
+
+ this.jmDNSImpl.getLocalHost().addAddressRecords(out, true);
+ this.jmDNSImpl.advanceState();
+ }
+ // send probes for services
+ // Defensively copy the services into a local list,
+ // to prevent race conditions with methods registerService
+ // and unregisterService.
+ List list;
+ synchronized (this.jmDNSImpl)
+ {
+ list = new LinkedList(this.jmDNSImpl.getServices().values());
+ }
+ for (Iterator i = list.iterator(); i.hasNext();)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) i.next();
+
+ synchronized (info)
+ {
+ if (info.getState() == taskState && info.getTask() == this)
+ {
+ info.advanceState();
+ logger.fine("run() JmDNS probing " + info.getQualifiedName() + " state " + info.getState());
+ if (out == null)
+ {
+ out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
+ out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
+ }
+ // the "unique" flag should be not set here because these answers haven't been proven unique yet
+ // this means the record will not exactly match the announcement record
+ out.addAuthorativeAnswer(new DNSRecord.Service(info.getQualifiedName(),
+ DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL,
+ info.getPriority(), info.getWeight(), info.getPort(), this.jmDNSImpl.getLocalHost().getName()));
+ }
+ }
+ }
+ if (out != null)
+ {
+ logger.finer("run() JmDNS probing #" + taskState);
+ this.jmDNSImpl.send(out);
+ }
+ else
+ {
+ // If we have nothing to send, another timer taskState ahead
+ // of us has done the job for us. We can cancel.
+ cancel();
+ return;
+ }
+ }
+ catch (Throwable e)
+ {
+ logger.log(Level.WARNING, "run() exception ", e);
+ this.jmDNSImpl.recover();
+ }
+
+ taskState = taskState.advance();
+ if (!taskState.isProbing())
+ {
+ cancel();
+
+ this.jmDNSImpl.startAnnouncer();
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/RecordReaper.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/RecordReaper.java
new file mode 100644
index 000000000..cc1d69f0d
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/RecordReaper.java
@@ -0,0 +1,83 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl.tasks;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Logger;
+
+import javax.jmdns.impl.DNSCache;
+import javax.jmdns.impl.DNSConstants;
+import javax.jmdns.impl.DNSRecord;
+import javax.jmdns.impl.DNSState;
+import javax.jmdns.impl.JmDNSImpl;
+
+/**
+ * Periodicaly removes expired entries from the cache.
+ */
+public class RecordReaper extends TimerTask
+{
+ static Logger logger = Logger.getLogger(RecordReaper.class.getName());
+
+ /**
+ *
+ */
+ private final JmDNSImpl jmDNSImpl;
+
+ /**
+ * @param jmDNSImpl
+ */
+ public RecordReaper(JmDNSImpl jmDNSImpl)
+ {
+ this.jmDNSImpl = jmDNSImpl;
+ }
+
+ public void start(Timer timer)
+ {
+ timer.schedule(this, DNSConstants.RECORD_REAPER_INTERVAL, DNSConstants.RECORD_REAPER_INTERVAL);
+ }
+
+ public void run()
+ {
+ synchronized (this.jmDNSImpl)
+ {
+ if (this.jmDNSImpl.getState() == DNSState.CANCELED)
+ {
+ return;
+ }
+ logger.finest("run() JmDNS reaping cache");
+
+ // Remove expired answers from the cache
+ // -------------------------------------
+ // To prevent race conditions, we defensively copy all cache
+ // entries into a list.
+ List list = new ArrayList();
+ synchronized (this.jmDNSImpl.getCache())
+ {
+ for (Iterator i = this.jmDNSImpl.getCache().iterator(); i.hasNext();)
+ {
+ for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n = n.next())
+ {
+ list.add(n.getValue());
+ }
+ }
+ }
+ // Now, we remove them.
+ long now = System.currentTimeMillis();
+ for (Iterator i = list.iterator(); i.hasNext();)
+ {
+ DNSRecord c = (DNSRecord) i.next();
+ if (c.isExpired(now))
+ {
+ this.jmDNSImpl.updateRecord(now, c);
+ this.jmDNSImpl.getCache().remove(c);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Renewer.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Renewer.java
new file mode 100644
index 000000000..9b2ed3d62
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Renewer.java
@@ -0,0 +1,154 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl.tasks;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.jmdns.impl.DNSConstants;
+import javax.jmdns.impl.DNSOutgoing;
+import javax.jmdns.impl.DNSState;
+import javax.jmdns.impl.JmDNSImpl;
+import javax.jmdns.impl.ServiceInfoImpl;
+
+/**
+ * The Renewer is there to send renewal announcment when the record expire for ours infos.
+ */
+public class Renewer extends TimerTask
+{
+ static Logger logger = Logger.getLogger(Renewer.class.getName());
+
+ /**
+ *
+ */
+ private final JmDNSImpl jmDNSImpl;
+ /**
+ * The state of the announcer.
+ */
+ DNSState taskState = DNSState.ANNOUNCED;
+
+ public Renewer(JmDNSImpl jmDNSImpl)
+ {
+ this.jmDNSImpl = jmDNSImpl;
+ // Associate host to this, if it needs renewal
+ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED)
+ {
+ this.jmDNSImpl.setTask(this);
+ }
+ // Associate services to this, if they need renewal
+ synchronized (this.jmDNSImpl)
+ {
+ for (Iterator s = this.jmDNSImpl.getServices().values().iterator(); s.hasNext();)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) s.next();
+ if (info.getState() == DNSState.ANNOUNCED)
+ {
+ info.setTask(this);
+ }
+ }
+ }
+ }
+
+ public void start(Timer timer)
+ {
+ timer.schedule(this, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL);
+ }
+
+ public boolean cancel()
+ {
+ // Remove association from host to this
+ if (this.jmDNSImpl.getTask() == this)
+ {
+ this.jmDNSImpl.setTask(null);
+ }
+
+ // Remove associations from services to this
+ synchronized (this.jmDNSImpl)
+ {
+ for (Iterator i = this.jmDNSImpl.getServices().values().iterator(); i.hasNext();)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) i.next();
+ if (info.getTask() == this)
+ {
+ info.setTask(null);
+ }
+ }
+ }
+
+ return super.cancel();
+ }
+
+ public void run()
+ {
+ DNSOutgoing out = null;
+ try
+ {
+ // send probes for JmDNS itself
+ if (this.jmDNSImpl.getState() == taskState)
+ {
+ if (out == null)
+ {
+ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
+ }
+ this.jmDNSImpl.getLocalHost().addAddressRecords(out, false);
+ this.jmDNSImpl.advanceState();
+ }
+ // send announces for services
+ // Defensively copy the services into a local list,
+ // to prevent race conditions with methods registerService
+ // and unregisterService.
+ List list;
+ synchronized (this.jmDNSImpl)
+ {
+ list = new ArrayList(this.jmDNSImpl.getServices().values());
+ }
+ for (Iterator i = list.iterator(); i.hasNext();)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) i.next();
+ synchronized (info)
+ {
+ if (info.getState() == taskState && info.getTask() == this)
+ {
+ info.advanceState();
+ logger.finer("run() JmDNS announced " + info.getQualifiedName() + " state " + info.getState());
+ if (out == null)
+ {
+ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
+ }
+ info.addAnswers(out, DNSConstants.DNS_TTL, this.jmDNSImpl.getLocalHost());
+ }
+ }
+ }
+ if (out != null)
+ {
+ logger.finer("run() JmDNS announced");
+ this.jmDNSImpl.send(out);
+ }
+ else
+ {
+ // If we have nothing to send, another timer taskState ahead
+ // of us has done the job for us. We can cancel.
+ cancel();
+ }
+ }
+ catch (Throwable e)
+ {
+ logger.log(Level.WARNING, "run() exception ", e);
+ this.jmDNSImpl.recover();
+ }
+
+ taskState = taskState.advance();
+ if (!taskState.isAnnounced())
+ {
+ cancel();
+
+ }
+ }
+} \ No newline at end of file
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Responder.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Responder.java
new file mode 100644
index 000000000..c63bdb361
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/Responder.java
@@ -0,0 +1,291 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl.tasks;
+
+import java.net.InetAddress;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.TimerTask;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.jmdns.impl.DNSConstants;
+import javax.jmdns.impl.DNSEntry;
+import javax.jmdns.impl.DNSIncoming;
+import javax.jmdns.impl.DNSOutgoing;
+import javax.jmdns.impl.DNSQuestion;
+import javax.jmdns.impl.DNSRecord;
+import javax.jmdns.impl.DNSState;
+import javax.jmdns.impl.JmDNSImpl;
+import javax.jmdns.impl.ServiceInfoImpl;
+
+/**
+ * The Responder sends a single answer for the specified service infos
+ * and for the host name.
+ */
+public class Responder extends TimerTask
+{
+ static Logger logger = Logger.getLogger(Responder.class.getName());
+
+ /**
+ *
+ */
+ private final JmDNSImpl jmDNSImpl;
+ private DNSIncoming in;
+ private InetAddress addr;
+ private int port;
+
+ public Responder(JmDNSImpl jmDNSImpl, DNSIncoming in, InetAddress addr, int port)
+ {
+ this.jmDNSImpl = jmDNSImpl;
+ this.in = in;
+ this.addr = addr;
+ this.port = port;
+ }
+
+ public void start()
+ {
+ // According to draft-cheshire-dnsext-multicastdns.txt
+ // chapter "8 Responding":
+ // We respond immediately if we know for sure, that we are
+ // the only one who can respond to the query.
+ // In all other cases, we respond within 20-120 ms.
+ //
+ // According to draft-cheshire-dnsext-multicastdns.txt
+ // chapter "7.2 Multi-Packet Known Answer Suppression":
+ // We respond after 20-120 ms if the query is truncated.
+
+ boolean iAmTheOnlyOne = true;
+ for (Iterator i = in.getQuestions().iterator(); i.hasNext();)
+ {
+ DNSEntry entry = (DNSEntry) i.next();
+ if (entry instanceof DNSQuestion)
+ {
+ DNSQuestion q = (DNSQuestion) entry;
+ logger.finest("start() question=" + q);
+ iAmTheOnlyOne &= (q.getType() == DNSConstants.TYPE_SRV
+ || q.getType() == DNSConstants.TYPE_TXT
+ || q.getType() == DNSConstants.TYPE_A
+ || q.getType() == DNSConstants.TYPE_AAAA
+ || this.jmDNSImpl.getLocalHost().getName().equalsIgnoreCase(q.getName())
+ || this.jmDNSImpl.getServices().containsKey(q.getName().toLowerCase()));
+ if (!iAmTheOnlyOne)
+ {
+ break;
+ }
+ }
+ }
+ int delay = (iAmTheOnlyOne && !in.isTruncated()) ? 0 : DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + JmDNSImpl.getRandom().nextInt(DNSConstants.RESPONSE_MAX_WAIT_INTERVAL - DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + 1) - in.elapseSinceArrival();
+ if (delay < 0)
+ {
+ delay = 0;
+ }
+ logger.finest("start() Responder chosen delay=" + delay);
+ this.jmDNSImpl.schedule(this, delay);
+ }
+
+ public void run()
+ {
+ synchronized (this.jmDNSImpl.getIoLock())
+ {
+ if (this.jmDNSImpl.getPlannedAnswer() == in)
+ {
+ this.jmDNSImpl.setPlannedAnswer(null);
+ }
+
+ // We use these sets to prevent duplicate records
+ // FIXME - This should be moved into DNSOutgoing
+ HashSet questions = new HashSet();
+ HashSet answers = new HashSet();
+
+
+ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED)
+ {
+ try
+ {
+ boolean isUnicast = (port != DNSConstants.MDNS_PORT);
+
+
+ // Answer questions
+ for (Iterator iterator = in.getQuestions().iterator(); iterator.hasNext();)
+ {
+ DNSEntry entry = (DNSEntry) iterator.next();
+ if (entry instanceof DNSQuestion)
+ {
+ DNSQuestion q = (DNSQuestion) entry;
+
+ // for unicast responses the question must be included
+ if (isUnicast)
+ {
+ //out.addQuestion(q);
+ questions.add(q);
+ }
+
+ int type = q.getType();
+ if (type == DNSConstants.TYPE_ANY || type == DNSConstants.TYPE_SRV)
+ { // I ama not sure of why there is a special case here [PJYF Oct 15 2004]
+ if (this.jmDNSImpl.getLocalHost().getName().equalsIgnoreCase(q.getName()))
+ {
+ // type = DNSConstants.TYPE_A;
+ DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord();
+ if (answer != null)
+ {
+ answers.add(answer);
+ }
+ answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord();
+ if (answer != null)
+ {
+ answers.add(answer);
+ }
+ type = DNSConstants.TYPE_IGNORE;
+ }
+ else
+ {
+ if (this.jmDNSImpl.getServiceTypes().containsKey(q.getName().toLowerCase()))
+ {
+ type = DNSConstants.TYPE_PTR;
+ }
+ }
+ }
+
+ switch (type)
+ {
+ case DNSConstants.TYPE_A:
+ {
+ // Answer a query for a domain name
+ //out = addAnswer( in, addr, port, out, host );
+ DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord();
+ if (answer != null)
+ {
+ answers.add(answer);
+ }
+ break;
+ }
+ case DNSConstants.TYPE_AAAA:
+ {
+ // Answer a query for a domain name
+ DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord();
+ if (answer != null)
+ {
+ answers.add(answer);
+ }
+ break;
+ }
+ case DNSConstants.TYPE_PTR:
+ {
+ // Answer a query for services of a given type
+
+ // find matching services
+ for (Iterator serviceIterator = this.jmDNSImpl.getServices().values().iterator(); serviceIterator.hasNext();)
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) serviceIterator.next();
+ if (info.getState() == DNSState.ANNOUNCED)
+ {
+ if (q.getName().equalsIgnoreCase(info.getType()))
+ {
+ DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord();
+ if (answer != null)
+ {
+ answers.add(answer);
+ }
+ answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord();
+ if (answer != null)
+ {
+ answers.add(answer);
+ }
+ answers.add(new DNSRecord.Pointer(info.getType(), DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()));
+ answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL,
+ info.getPriority(), info.getWeight(), info.getPort(), this.jmDNSImpl.getLocalHost().getName()));
+ answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL,
+ info.getText()));
+ }
+ }
+ }
+ if (q.getName().equalsIgnoreCase("_services._mdns._udp.local."))
+ {
+ for (Iterator serviceTypeIterator = this.jmDNSImpl.getServiceTypes().values().iterator(); serviceTypeIterator.hasNext();)
+ {
+ answers.add(new DNSRecord.Pointer("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, (String) serviceTypeIterator.next()));
+ }
+ }
+ break;
+ }
+ case DNSConstants.TYPE_SRV:
+ case DNSConstants.TYPE_ANY:
+ case DNSConstants.TYPE_TXT:
+ {
+ ServiceInfoImpl info = (ServiceInfoImpl) this.jmDNSImpl.getServices().get(q.getName().toLowerCase());
+ if (info != null && info.getState() == DNSState.ANNOUNCED)
+ {
+ DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord();
+ if (answer != null)
+ {
+ answers.add(answer);
+ }
+ answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord();
+ if (answer != null)
+ {
+ answers.add(answer);
+ }
+ answers.add(new DNSRecord.Pointer(info.getType(), DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()));
+ answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL,
+ info.getPriority(), info.getWeight(), info.getPort(), this.jmDNSImpl.getLocalHost().getName()));
+ answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.getText()));
+ }
+ break;
+ }
+ default :
+ {
+ //System.out.println("JmDNSResponder.unhandled query:"+q);
+ break;
+ }
+ }
+ }
+ }
+
+
+ // remove known answers, if the ttl is at least half of
+ // the correct value. (See Draft Cheshire chapter 7.1.).
+ for (Iterator i = in.getAnswers().iterator(); i.hasNext();)
+ {
+ DNSRecord knownAnswer = (DNSRecord) i.next();
+ if (knownAnswer.getTtl() > DNSConstants.DNS_TTL / 2 && answers.remove(knownAnswer))
+ {
+ logger.log(Level.FINER, "JmDNS Responder Known Answer Removed");
+ }
+ }
+
+
+ // responde if we have answers
+ if (answers.size() != 0)
+ {
+ logger.finer("run() JmDNS responding");
+ DNSOutgoing out = null;
+ if (isUnicast)
+ {
+ out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false);
+ }
+
+ for (Iterator i = questions.iterator(); i.hasNext();)
+ {
+ out.addQuestion((DNSQuestion) i.next());
+ }
+ for (Iterator i = answers.iterator(); i.hasNext();)
+ {
+ out = this.jmDNSImpl.addAnswer(in, addr, port, out, (DNSRecord) i.next());
+ }
+ this.jmDNSImpl.send(out);
+ }
+ this.cancel();
+ }
+ catch (Throwable e)
+ {
+ logger.log(Level.WARNING, "run() exception ", e);
+ this.jmDNSImpl.close();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/ServiceInfoResolver.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/ServiceInfoResolver.java
new file mode 100644
index 000000000..4041ae3b5
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/ServiceInfoResolver.java
@@ -0,0 +1,101 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl.tasks;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.jmdns.impl.DNSConstants;
+import javax.jmdns.impl.DNSOutgoing;
+import javax.jmdns.impl.DNSQuestion;
+import javax.jmdns.impl.DNSRecord;
+import javax.jmdns.impl.DNSState;
+import javax.jmdns.impl.JmDNSImpl;
+import javax.jmdns.impl.ServiceInfoImpl;
+
+/**
+ * The ServiceInfoResolver queries up to three times consecutively for
+ * a service info, and then removes itself from the timer.
+ * <p/>
+ * The ServiceInfoResolver will run only if JmDNS is in state ANNOUNCED.
+ * REMIND: Prevent having multiple service resolvers for the same info in the
+ * timer queue.
+ */
+public class ServiceInfoResolver extends TimerTask
+{
+ static Logger logger = Logger.getLogger(ServiceInfoResolver.class.getName());
+
+ /**
+ *
+ */
+ private final JmDNSImpl jmDNSImpl;
+ /**
+ * Counts the number of queries being sent.
+ */
+ int count = 0;
+ private ServiceInfoImpl info;
+
+ public ServiceInfoResolver(JmDNSImpl jmDNSImpl, ServiceInfoImpl info)
+ {
+ this.jmDNSImpl = jmDNSImpl;
+ this.info = info;
+ info.setDns(this.jmDNSImpl);
+ this.jmDNSImpl.addListener(info, new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
+ }
+
+ public void start(Timer timer)
+ {
+ timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL);
+ }
+
+ public void run()
+ {
+ try
+ {
+ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED)
+ {
+ if (count++ < 3 && !info.hasData())
+ {
+ long now = System.currentTimeMillis();
+ DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
+ out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN));
+ out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN));
+ if (info.getServer() != null)
+ {
+ out.addQuestion(new DNSQuestion(info.getServer(), DNSConstants.TYPE_A, DNSConstants.CLASS_IN));
+ }
+ out.addAnswer((DNSRecord) this.jmDNSImpl.getCache().get(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN), now);
+ out.addAnswer((DNSRecord) this.jmDNSImpl.getCache().get(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN), now);
+ if (info.getServer() != null)
+ {
+ out.addAnswer((DNSRecord) this.jmDNSImpl.getCache().get(info.getServer(), DNSConstants.TYPE_A, DNSConstants.CLASS_IN), now);
+ }
+ this.jmDNSImpl.send(out);
+ }
+ else
+ {
+ // After three queries, we can quit.
+ this.cancel();
+ this.jmDNSImpl.removeListener(info);
+ }
+ }
+ else
+ {
+ if (this.jmDNSImpl.getState() == DNSState.CANCELED)
+ {
+ this.cancel();
+ this.jmDNSImpl.removeListener(info);
+ }
+ }
+ }
+ catch (Throwable e)
+ {
+ logger.log(Level.WARNING, "run() exception ", e);
+ this.jmDNSImpl.recover();
+ }
+ }
+} \ No newline at end of file
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/ServiceResolver.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/ServiceResolver.java
new file mode 100644
index 000000000..09c60eb42
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/ServiceResolver.java
@@ -0,0 +1,101 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl.tasks;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.jmdns.impl.DNSConstants;
+import javax.jmdns.impl.DNSOutgoing;
+import javax.jmdns.impl.DNSQuestion;
+import javax.jmdns.impl.DNSRecord;
+import javax.jmdns.impl.DNSState;
+import javax.jmdns.impl.JmDNSImpl;
+import javax.jmdns.impl.ServiceInfoImpl;
+
+/**
+ * The ServiceResolver queries three times consecutively for services of
+ * a given type, and then removes itself from the timer.
+ * <p/>
+ * The ServiceResolver will run only if JmDNS is in state ANNOUNCED.
+ * REMIND: Prevent having multiple service resolvers for the same type in the
+ * timer queue.
+ */
+public class ServiceResolver extends TimerTask
+{
+ static Logger logger = Logger.getLogger(ServiceResolver.class.getName());
+
+ /**
+ *
+ */
+ private final JmDNSImpl jmDNSImpl;
+ /**
+ * Counts the number of queries being sent.
+ */
+ int count = 0;
+ private String type;
+
+ public ServiceResolver(JmDNSImpl jmDNSImpl, String type)
+ {
+ this.jmDNSImpl = jmDNSImpl;
+ this.type = type;
+ }
+
+ public void start(Timer timer)
+ {
+ timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL);
+ }
+
+ public void run()
+ {
+ try
+ {
+ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED)
+ {
+ if (count++ < 3)
+ {
+ logger.finer("run() JmDNS querying service");
+ long now = System.currentTimeMillis();
+ DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
+ out.addQuestion(new DNSQuestion(type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN));
+ for (Iterator s = this.jmDNSImpl.getServices().values().iterator(); s.hasNext();)
+ {
+ final ServiceInfoImpl info = (ServiceInfoImpl) s.next();
+ try
+ {
+ out.addAnswer(new DNSRecord.Pointer(info.getType(), DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()), now);
+ }
+ catch (IOException ee)
+ {
+ break;
+ }
+ }
+ this.jmDNSImpl.send(out);
+ }
+ else
+ {
+ // After three queries, we can quit.
+ this.cancel();
+ }
+ }
+ else
+ {
+ if (this.jmDNSImpl.getState() == DNSState.CANCELED)
+ {
+ this.cancel();
+ }
+ }
+ }
+ catch (Throwable e)
+ {
+ logger.log(Level.WARNING, "run() exception ", e);
+ this.jmDNSImpl.recover();
+ }
+ }
+} \ No newline at end of file
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/TypeResolver.java b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/TypeResolver.java
new file mode 100644
index 000000000..15303131a
--- /dev/null
+++ b/providers/bundles/org.eclipse.ecf.provider.jmdns/jmdns/javax/jmdns/impl/tasks/TypeResolver.java
@@ -0,0 +1,92 @@
+//Copyright 2003-2005 Arthur van Hoff, Rick Blair
+//Licensed under Apache License version 2.0
+//Original license LGPL
+
+package javax.jmdns.impl.tasks;
+
+import java.util.Iterator;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.jmdns.impl.DNSConstants;
+import javax.jmdns.impl.DNSOutgoing;
+import javax.jmdns.impl.DNSQuestion;
+import javax.jmdns.impl.DNSRecord;
+import javax.jmdns.impl.DNSState;
+import javax.jmdns.impl.JmDNSImpl;
+
+/**
+ * Helper class to resolve service types.
+ * <p/>
+ * The TypeResolver queries three times consecutively for service types, and then
+ * removes itself from the timer.
+ * <p/>
+ * The TypeResolver will run only if JmDNS is in state ANNOUNCED.
+ */
+public class TypeResolver extends TimerTask
+{
+ static Logger logger = Logger.getLogger(TypeResolver.class.getName());
+
+ /**
+ *
+ */
+ private final JmDNSImpl jmDNSImpl;
+
+ /**
+ * @param jmDNSImpl
+ */
+ public TypeResolver(JmDNSImpl jmDNSImpl)
+ {
+ this.jmDNSImpl = jmDNSImpl;
+ }
+
+ public void start(Timer timer)
+ {
+ timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL);
+ }
+
+ /**
+ * Counts the number of queries that were sent.
+ */
+ int count = 0;
+
+ public void run()
+ {
+ try
+ {
+ if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED)
+ {
+ if (count++ < 3)
+ {
+ logger.finer("run() JmDNS querying type");
+ DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
+ out.addQuestion(new DNSQuestion("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN));
+ for (Iterator iterator = this.jmDNSImpl.getServiceTypes().values().iterator(); iterator.hasNext();)
+ {
+ out.addAnswer(new DNSRecord.Pointer("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, (String) iterator.next()), 0);
+ }
+ this.jmDNSImpl.send(out);
+ }
+ else
+ {
+ // After three queries, we can quit.
+ this.cancel();
+ }
+ }
+ else
+ {
+ if (this.jmDNSImpl.getState() == DNSState.CANCELED)
+ {
+ this.cancel();
+ }
+ }
+ }
+ catch (Throwable e)
+ {
+ logger.log(Level.WARNING, "run() exception ", e);
+ this.jmDNSImpl.recover();
+ }
+ }
+} \ No newline at end of file
diff --git a/providers/bundles/org.eclipse.ecf.provider.jmdns/lib/jmdns.jar b/providers/bundles/org.eclipse.ecf.provider.jmdns/lib/jmdns.jar
deleted file mode 100644
index 5f83830d5..000000000
--- a/providers/bundles/org.eclipse.ecf.provider.jmdns/lib/jmdns.jar
+++ /dev/null
Binary files differ

Back to the top