jmdns-3.4.1/0000755000175000017500000000000011741065626012514 5ustar mathieumathieujmdns-3.4.1/CHANGELOG.txt0000644000175000017500000003712711625404056014551 0ustar mathieumathieuCHANGELOG 24 August 2011 Pierre Frisch Rev the source code to 3.4.1 unregisterService() prevents subsequent registerService() - ID: 3163605 Sometimes race condition causes to use a canceled timer - ID: 3183116 How should addServiceListener be expected to work? - ID: 3186552 ConcurrentModificationException is possible - ID: 3195594 Registering twice same ServiceInfo blocks - ID: 3195632 Error message on second register - ID: 3195656 ServiceInfoImpl returns invalid IPV6 addresses - ID: 3290692 Queries are sent over IPv4 even when binding to IPv6 address - ID: 3293105 Synchronization issue - ID: 3298376 Synchronization issue - ID: 3301269 NPE is possible - ID: 3390168 CHANGELOG 20 January 2011 Pierre Frisch Rev the source code to 3.4.0 Android does not support IOException(String, Throwable) - ID: 3110982 Replace dots with dashes in hostname instead of truncating - ID: 3111108 Moved the NetworkTopologyDiscovery into a stand alone class instead of JmmDNS inner class. OSGi Bundle-RequiredExecutionEnvironment broken - ID: 3111738 JmDNS does not preserve service type case Remove call to isEmpty (for Android) - ID: 3113114 Remove package.html - ID: 3112613 Announcer is causing a ServiceType cache flush - ID: 3113073 Can't resolve if txt field is empty - ID: 3123622 TXT Record not updating for getProperty call - ID: 3126952 ********* JmDNS is now case preserving, which was the intent all along. Check your code as we JmDNS may return mixed case in instances where it was returning lower case. ********* ServiceInfo doesn't allow more than one IPV4 address.- ID: 3150462 waitForCancelled and waitForAnnounced can cause bad behavior - ID: 3151408 Add support for MANET that need to override timer definition Reworked the waitForXXX method to make them event driven. Quite a bit more efficient. CHANGELOG 16 November 2010 Pierre Frisch Rev the source code to 3.2.2 serviceResolved not working properly - ID: 3032054 Only adds "first" server on Android - ID: 3065602 RecordReaper repeated stacktrace - ID: 3085234 JmDNS 3.2.1 on Android Application - ID: 3059323 Built-in support for multiple network interfaces - ID: 3079530 Create OSGi metadata in maven build - ID: 3085811 Path to prevent JmDNS.create() binding to loopback - ID: 3092443 java.net.InterfaceAddress not available on Android - ID: 3096650 CHANGELOG 31 August 2010 Pierre Frisch Rev the source code to 3.2.1 ServiceInfo.setText() reannounces updated service only once - ID: 3030016 ServiceListener doesn't see updated ServiceInfo - ID: 3010451 unregisterService - ID: 3040520 Maven depository update - ID: 1432672 Maven POM file - ID: 3047223 ********* Call backs are now executed in a separate executor. This should make JmDNS more responsive but may have consequences in the client code. ********* CHANGELOG 08 July 2010 Pierre Frisch Rev the source code to 3.2.0 Add subtypes - ID: 1582803 CHANGELOG 27 June 2010 Pierre Frisch Rev the source code to 3.1.8 JmDNS fails to get ServiceInfo from cache - ID: 3018122 Avahi DNS entries lost after an hour - ID: 3015920 No IPv6 address returned by getAddress - ID: 1952439 JmDNS.list() no longer provides results - ID: 3019756 CHANGELOG 10 June 2010 Pierre Frisch Rev the source code to 3.1.7 ********* Reorganization of the locking / timer strategy. Hopefully it should be less deadlock prone. ********* index out of range exception in javax.jmdns.JmDNS - ID: 1414816 Optimization in JmDNS.SocketListener - ID: 1623129 JMDNS reports IPv6 link local addr as service type - ID: 1791481 JmDNSImpl.openMulticastSocket() fails on AIX - ID: 2821919 Service discovery takes about 6 s - FIX - ID: 2910895 Update text field and receive text updates. - ID: 2349331 Make listening interface and port configurable - ID: 3000610 Add extra API to read protocol and domain - ID: 3000616 Replace outdated/inofficial "._mdns._udp." meta query with o - ID: 3000618 Build OSGi bundle as part of ANT build - ID: 3000972 CHANGELOG 5 April 2010 Pierre Frisch Rev the source code to 3.1.6 Fix an issue in the discovery where the values where not correctly retrieved from the cache. Bug in method JmDNSImpl.openMulticastSocket - ID: 2980409 JmDNSImpl DNS Cache Entry Removal - ID: 2981167 Bug in Pointer.getServiceInfo() - ID: 2980727 TXT String not set when ServiceInfo retrieved from the Cache - ID: 2982114 CHANGELOG 30 March 2010 Pierre Frisch Rev the source code to 3.1.5 Reorg or the Responser code to make more modular and easy to understand. Added support for variable response length from EDNS Added better usage of cache information that will make the request for service info more responsive. CHANGELOG 28 March 2010 Pierre Frisch Rev the source code to 3.1.4 Fixed a bad issue that would prevent proper response to queries. Adjusted the timeouts to make them work by default. CHANGELOG 22 March 2010 Pierre Frisch Rev the source code to 3.1.3 Fixed a NPE in ServiceInfoImpl.getText() Fixed a case sensitivity issue in the Service Info updateRecord() Fixed a number of problem with the service listener that did not get called at the right time. ****** Please note the changes in the JavaDoc for ServiceListener ****** This is not a change of behavior but a clarification of the contract. CHANGELOG 17 March 2010 Pierre Frisch Rev the source code to 3.1.2 Merged issue: JmDNS.create() with no args doesn't set address properly - ID: 2937206 Merged issue: IllegalStateException in Prober.start() - ID: 2942555 Fixed an NPE exception and tested publishing and discovering the service on the same machine CHANGELOG 9 March 2010 Pierre Frisch Rev the source code to 3.1.1 Merged issue: Service listener does not find any services - FIX - ID: 2910899 CHANGELOG 17 December 2009 Rick Blair 1. Changes by Pierre Frisch to compile clean under java 1.5 and java 1.6 2. Patch to close as submitted by Lachian Deck Thanks all. CHANGELOG 06 December 2009 Rick Blair 1. Merged in all outstanding patches. CHANGELOG 06 February 2008 Scott Cytacki 1. Added code to make it easier to debug communication with other mdns implementations. Including: - keeping track of the the address which sent a dns record - flag to turning off domain-name compression - flag to use non domain-name format for targets of SRV records - checks for conflicts from our own source address. Added the UNIQUE flag which is referred to the "cache flush bit" by the spec, on SRV and TXT records sent to announce a Service. 2. Made TimerTasks inside of JmDNSImpl into toplevel classes in a new package: javax.jmdns.impl.tasks This required making several method publics and encapsulating several fields with public getters and setters. All these changes were done using Eclipse refactoring so there is a log chance of error. I will be testing it with the Conformance Test to make sure it hasn't regressed. CHANGELOG 05 February 2008 Scott Cytacki 1. Moved implementation classes into a impl package. This paves the way for further refactoring. By putting all the jmdns api classes and interfaces in one package, the impl classes can be split up more without exposing public classes and methods that aren't part of the jmdns api. This change breaks the old api, because the JmDNS and ServiceInfo classes cannot be instanciated directly. Instead there are factory methods to create them. CHANGELOG 01 February 2008 Scott Cytacki 1. Fixed an error in the way ServiceInfo was constructing the text array for the TXT record. The length byte was being omitted if the ServiceInfo constructor was called with the last argument of a String. Added better handling of unsupported dns label types. The Apple Bonjour conformance test sends SRV records with target strings that have lengths longer than 63, so the top 2 bits get used. CHANGELOG 25 January 2008 Scott Cytacki 1. Consolidated most places where the ServiceInfo object was turned into a set of dns records. And most places where the localHost (HostInfo) object was turned into dns records. There is still one place that is duplicated (JmDNS.Responder) because it adds the dns records to a list first, removes known answers, and finally adds them to DNSOutgoing. CHANGELOG 17 January 2008 Scott Cytacki 1. Added eclipse project files. Forced the eclipse project to use java 1.4 2. Changed the eclipse project to store built files in eclipse-lib because lib was already being used. 3. Added eclipse code formating configuration files. They should match follow the turbine coding standards which have been used in this project: http://turbine.apache.org/common/code-standards.html Also this includes a minor formating clean up of JmDNS class. 4. Changed Logger.getLogger parameter to use class.getName instead of class.toString. This is because class.toString returns for example "class javax.jmdns.JmDNS" while class.getName returns "javax.jmdns.JmDNS". The "class " messes up the logging hierarchical structure. This javadoc specifices the string should be dot separated: http://java.sun.com/j2se/1.5.0/docs/api/java/util/logging/Logger.html#getLogger(java.lang.String). Also this post to the mailing list talks about this issue: http://sourceforge.net/mailarchive/forum.php?thread_name=1196195175.2703.4.camel%40moya.kivasystems.com&forum_name=jmdns-discuss In that case getCanonicalName was used but that is only in Java 1.5 and basically does the same thing for normal classes. CHANGELOG 16 July 2005 Rick Blair 1. Added Apache License Version 2.0 2. Added shutdown fix as identifed by Scott Lewis 3. Added copyright notice. 4. General code cleanup. CHANGELOG 30 November 2004 rawcoder 1. IPv6 support - RFE 890432: IPv6 support implemented 2. Logging - Logging is done using the Java Logger API now 3. Bug fixes - JmDNS reannounces services after their TTL has timed out. 4. Plattform dependency - Due to the changes done for IPv6 support and the logging, JmDNS requires now a JVM supporting a J2SE 1.4 API. CHANGELOG 6 June 2004 rawcoder 1. General API Changes - RFE 868432: Changed listener API to comply with the commonly used EventListener/EventObject idiom - RFE 868433: Added a list method to class JmDNS which can be used to retrieve a list of available services without having to implement a ServiceListener. - RFE 892855: All API's use now unqualified service instance names 2. General implementation changes - JmDNS now runs through the Rendezvous Conformance Test, when the options "-M hn" are used. - Threads are now handled differently. ServiceListeners and ServiceTypeListeners can not assume anymore that they are invoked from the AWT event dispatcher thread. - States are now handled explicitly using class DNSState. 3. DNSCache - Removed the hash table code and use now a java.util.Hashtable. - This reduces the overall size of the class. 4. DNSConstants - Added several new time interval constants. 5. DNSEntry - Now overrides method hashCode() to return a value, that is consistent with method equals(). 6. DNSIncoming - Added a warning to constant EMPTY. Using this constant is dangerous, because a Vector is mutable. Thus it can not be used as a constant this way. - Made many instance variables and methods private. - Added more output to method print(), to help me debug JmDNS. - Optimized StringBuffer handling in method toString(). - Added methods isTruncated() and append(). - DNSIncoming must adjust variables numAnswers, numAuthorities, numAdditionals when skipping records. 7. DNSOutgoing - Made as many instance variables private as I could. - Added assertions (using if-Statements) to all add...() methods to prevent construction of illegal data structures. 8. DNSRecord - Made as many instance variables private as I could. - Moved code from class JmDNS into class DNSRecord. The new operations are named: handleQuery() handleResponse() and addAnswer(). This is to get rid of the big switch-statements that were in method handleQuery() and handleResponse() of class JmDNS. This does somehow make the relationship between these two classes a little bit more complex though. 9. JmDNS - Added comments to some of the instance variables. - Added an instance variable named 'state' to track the state of the host name published by JmDNS, and of JmDNS itself. - Replaced instance variable 'Vector browsers' by a hash map named 'serviceListeners'. - Got rid of the query cache. All caching is done now by a single cache. - Added instance variables for counting probe throttles. - Added an instance variable named 'hostNameCount' to create a new host name, when a conflict was detected. - Added a java.util.Timer. All outgoing messages and maintenance of the cache is now coordinated using the timer. This greatly reduces the number of concurrent threads. - Added an instance variable named 'ioLock'. This lock is used to coordinate the incoming listener thread with the timer tasks. - Added a static variable named 'random'. It is used to generate random delays for responses. - All outgoing tasks such as probing, announcing, responding and canceling are now handled by instances of TimerTask. - Added an instance variable named 'task', to keep track of timer tasks associated with the host name. - Transferred code from JmDNS to DNSRecord to get rid of some ugly switch statements. 10. ServiceInfo - Added an instance variable named 'state' to track the state of the service. This is used only, if the service is published by JmDNS. - Added an instance variable named 'task', to keep track of timer tasks associated with the service info. 11. Sample code - Added a "samples" package to JmDNS. This package contains sample programs for JmDNS. 12. JavaDoc - Added a "package.html" file for each package. This file holds package documentation used by JavaDoc. 13. Build - Added a "samples" task to build.xml. - The "javadoc" task puts now a version number into the header of each generated page. CHANGELOG 21 APRIL 2004 RickBlair 1. JmDNS -Fixed broken additionals in the Query Function. -Added additionals to the SVC query. CHANGELOG 28-29 MAR 2004 jeffs 1. JmDNS - Added probing thread to handle address record probing and conflicts - Fixed wait times to be in line with the draft mDNS spec. - Added query cache so can check if another machine is also probing for our address. - Added support for slowing down the request rate during probing as per the draft mDNS spec. - Added conflict checks. - General code clean up. - changed visibility of static debug variable to public so apps can check it and decide to do their own logging and so on - changed visbility of all non-static variables to private - added package protected method getCache() - broke up handleQuery() into: private DNSOutgoing typeA() private DNSOutgoing typePTR() private DNSOutgoing findService() - reduced handleQuery() synchronized block to just cover Socket.send() call [need to figure out what else needs synchronization] 2. ServiceInfo - changed all calls to JmDNS cache variable to use JmDNS getCache() accessor instead - replaced javadoc comment _http._tcp.local. with _http._tcp.local. so the javadoc tool would quit whining 3. DNSConstants - Added constants for wait times and probe times. - changed to be a final class - changed all references to it's fields in all other classes to read DNSConstants.whatever Know Problems. -- The additional section is broken in handleQuery. This will be fixed in the next go round. -- Service probing is still not to spec. No conflict resolution. -- Still can blow address space on very busy networks. jmdns-3.4.1/fulllogging.properties0000644000175000017500000000104111625404056017132 0ustar mathieumathieu# Global logging properties. # ------------------------------------------ # The set of handlers to be loaded upon startup. # Comma-separated list of class names. # (? LogManager docs say no comma here, but JDK example has comma.) handlers=java.util.logging.ConsoleHandler # Default global logging level. # Loggers and Handlers may override this level .level=ALL # --- ConsoleHandler --- # Override of global logging level java.util.logging.ConsoleHandler.level=ALL java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter jmdns-3.4.1/src/0000755000175000017500000000000011625404056013276 5ustar mathieumathieujmdns-3.4.1/src/samples/0000755000175000017500000000000011625404056014742 5ustar mathieumathieujmdns-3.4.1/src/samples/DiscoverServices.java0000644000175000017500000000555611625404056021102 0ustar mathieumathieu// Licensed under Apache License version 2.0 // Original license LGPL // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package samples; import java.io.IOException; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.JmDNS; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceListener; /** * Sample Code for Service Discovery using JmDNS and a ServiceListener. *

* Run the main method of this class. It listens for HTTP services and lists all changes on System.out. * * @author Werner Randelshofer */ public class DiscoverServices { static class SampleListener implements ServiceListener { @Override public void serviceAdded(ServiceEvent event) { System.out.println("Service added : " + event.getName() + "." + event.getType()); } @Override public void serviceRemoved(ServiceEvent event) { System.out.println("Service removed : " + event.getName() + "." + event.getType()); } @Override public void serviceResolved(ServiceEvent event) { System.out.println("Service resolved: " + event.getInfo()); } } /** * @param args * the command line arguments */ public static void main(String[] args) { try { // Activate these lines to see log messages of JmDNS boolean log = false; if (log) { Logger logger = Logger.getLogger(JmDNS.class.getName()); ConsoleHandler handler = new ConsoleHandler(); logger.addHandler(handler); logger.setLevel(Level.FINER); handler.setLevel(Level.FINER); } final JmDNS jmdns = JmDNS.create(); jmdns.addServiceListener("_http._tcp.local.", new SampleListener()); System.out.println("Press q and Enter, to quit"); int b; while ((b = System.in.read()) != -1 && (char) b != 'q') { /* Stub */ } jmdns.close(); System.out.println("Done"); } catch (IOException e) { e.printStackTrace(); } } } jmdns-3.4.1/src/samples/TestShutdownHook.java0000644000175000017500000000141411625404056021101 0ustar mathieumathieu// Licensed under Apache License version 2.0 // Original license LGPL package samples; import java.io.*; /** * TestShutdownHook. * * @author Werner Randelshofer * @version 1.0 May 24, 2004 Created. */ public class TestShutdownHook { /** * Creates a new instance. * * @param args */ public static void main(String[] args) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { System.out.println("Shutdown Hook"); } }); try { int b; while ((b = System.in.read()) != -1) { System.out.print("\"" + (char) b); } } catch (IOException e) { /* Stub */ } } } jmdns-3.4.1/src/samples/RegisterService.java0000644000175000017500000001144611625404056020720 0ustar mathieumathieu// Licensed under Apache License version 2.0 // Original license LGPL // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package samples; import java.io.IOException; import java.util.Enumeration; import java.util.HashMap; import java.util.Random; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; import javax.jmdns.JmDNS; import javax.jmdns.ServiceInfo; /** * Sample Code for Service Registration using JmDNS. *

* To see what happens, launch the TTY browser of JmDNS using the following command: * *

 * java -jar lib/jmdns.jar -bs _http._tcp local.
 * 
* * Then run the main method of this class. When you press 'r' and enter, you should see the following output on the TTY browser: * *
 * ADD: service[foo._http._tcp.local.,192.168.2.5:1234,path=index.html]
 * 
* * Press 'r' and enter, you should see the following output on the TTY browser: * *
 * ADD: service[foo._http._tcp.local.,192.168.2.5:1234,path=index.html]
 * 
* * REMOVE: foo * * @author Werner Randelshofer */ public class RegisterService { public final static String REMOTE_TYPE = "_touch-remote._tcp.local."; /** * @param args * the command line arguments */ public static void main(String[] args) { // Activate these lines to see log messages of JmDNS boolean log = true; if (log) { ConsoleHandler handler = new ConsoleHandler(); handler.setLevel(Level.FINEST); for (Enumeration enumerator = LogManager.getLogManager().getLoggerNames(); enumerator.hasMoreElements();) { String loggerName = enumerator.nextElement(); Logger logger = Logger.getLogger(loggerName); logger.addHandler(handler); logger.setLevel(Level.FINEST); } } try { System.out.println("Opening JmDNS..."); JmDNS jmdns = JmDNS.create(); System.out.println("Opened JmDNS!"); Random random = new Random(); int id = random.nextInt(100000); System.out.println("\nPress r and Enter, to register Itunes Remote service 'Android-'" + id); int b; while ((b = System.in.read()) != -1 && (char) b != 'r') { /* Stub */ } final HashMap values = new HashMap(); values.put("DvNm", "Android-" + id); values.put("RemV", "10000"); values.put("DvTy", "iPod"); values.put("RemN", "Remote"); values.put("txtvers", "1"); byte[] pair = new byte[8]; random.nextBytes(pair); values.put("Pair", toHex(pair)); byte[] name = new byte[20]; random.nextBytes(name); System.out.println("Requesting pairing for " + toHex(name)); ServiceInfo pairservice = ServiceInfo.create(REMOTE_TYPE, toHex(name), 1025, 0, 0, values); jmdns.registerService(pairservice); System.out.println("\nRegistered Service as " + pairservice); System.out.println("Press q and Enter, to quit"); // int b; while ((b = System.in.read()) != -1 && (char) b != 'q') { /* Stub */ } System.out.println("Closing JmDNS..."); jmdns.unregisterService(pairservice); jmdns.unregisterAllServices(); jmdns.close(); System.out.println("Done!"); System.exit(0); } catch (IOException e) { e.printStackTrace(); } } private static final char[] _nibbleToHex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private static String toHex(byte[] code) { StringBuilder result = new StringBuilder(2 * code.length); for (int i = 0; i < code.length; i++) { int b = code[i] & 0xFF; result.append(_nibbleToHex[b / 16]); result.append(_nibbleToHex[b % 16]); } return result.toString(); } } jmdns-3.4.1/src/samples/OpenJmDNS.java0000644000175000017500000000443211625404056017345 0ustar mathieumathieu// Licensed under Apache License version 2.0 // Original license LGPL // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package samples; import java.io.IOException; import java.util.Enumeration; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; import javax.jmdns.JmDNS; /** * Sample Code that opens JmDNS in debug mode. *

* Run the main method of this class. * * @author Werner Randelshofer */ public class OpenJmDNS { /** * @param args * the command line arguments */ public static void main(String[] args) { try { /* Activate these lines to see log messages of JmDNS */ boolean log = true; if (log) { ConsoleHandler handler = new ConsoleHandler(); handler.setLevel(Level.FINEST); for (Enumeration enumerator = LogManager.getLogManager().getLoggerNames(); enumerator.hasMoreElements();) { String loggerName = enumerator.nextElement(); Logger logger = Logger.getLogger(loggerName); logger.addHandler(handler); logger.setLevel(Level.FINEST); } } JmDNS jmdns = JmDNS.create(); System.out.println("Press q and Enter, to quit"); int b; while ((b = System.in.read()) != -1 && (char) b != 'q') { /* Stub */ } jmdns.close(); } catch (IOException e) { e.printStackTrace(); } } } jmdns-3.4.1/src/samples/ListServices.java0000644000175000017500000000545211625404056020232 0ustar mathieumathieu// Licensed under Apache License version 2.0 // Original license LGPL // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package samples; import java.io.IOException; import java.util.Enumeration; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; import javax.jmdns.JmDNS; import javax.jmdns.ServiceInfo; /** * Sample Code for Listing Services using JmDNS. *

* Run the main method of this class. This class prints a list of available HTTP services every 5 seconds. * * @author Werner Randelshofer */ public class ListServices { /** * @param args * the command line arguments */ public static void main(String[] args) { /* Activate these lines to see log messages of JmDNS */ boolean log = false; if (log) { ConsoleHandler handler = new ConsoleHandler(); handler.setLevel(Level.FINEST); for (Enumeration enumerator = LogManager.getLogManager().getLoggerNames(); enumerator.hasMoreElements();) { String loggerName = enumerator.nextElement(); Logger logger = Logger.getLogger(loggerName); logger.addHandler(handler); logger.setLevel(Level.FINEST); } } JmDNS jmdns = null; try { jmdns = JmDNS.create(); while (true) { ServiceInfo[] infos = jmdns.list("_airport._tcp.local."); System.out.println("List _airport._tcp.local."); for (int i = 0; i < infos.length; i++) { System.out.println(infos[i]); } System.out.println(); try { Thread.sleep(5000); } catch (InterruptedException e) { break; } } } catch (IOException e) { e.printStackTrace(); } finally { if (jmdns != null) try { jmdns.close(); } catch (IOException exception) { // } } } } jmdns-3.4.1/src/samples/ITunesRemotePairing.java0000644000175000017500000001523511625404056021510 0ustar mathieumathieu/** * */ package samples; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Enumeration; import java.util.HashMap; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; import javax.jmdns.JmDNS; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceListener; /** * */ public class ITunesRemotePairing implements Runnable, ServiceListener { public final static String TOUCH_ABLE_TYPE = "_touch-able._tcp.local."; public final static String DACP_TYPE = "_dacp._tcp.local."; public final static String REMOTE_TYPE = "_touch-remote._tcp.local."; public volatile static boolean _running = true; protected final Random random = new Random(); public static byte[] PAIRING_RAW = new byte[] { 0x63, 0x6d, 0x70, 0x61, 0x00, 0x00, 0x00, 0x3a, 0x63, 0x6d, 0x70, 0x67, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x63, 0x6d, 0x6e, 0x6d, 0x00, 0x00, 0x00, 0x16, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, (byte) 0xe2, (byte) 0x80, (byte) 0x99, 0x73, 0x20, 0x69, 0x50, 0x6f, 0x64, 0x63, 0x6d, 0x74, 0x79, 0x00, 0x00, 0x00, 0x04, 0x69, 0x50, 0x6f, 0x64 }; /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { // Activate these lines to see log messages of JmDNS boolean log = false; if (log) { ConsoleHandler handler = new ConsoleHandler(); handler.setLevel(Level.FINEST); for (Enumeration enumerator = LogManager.getLogManager().getLoggerNames(); enumerator.hasMoreElements();) { String loggerName = enumerator.nextElement(); Logger logger = Logger.getLogger(loggerName); logger.addHandler(handler); logger.setLevel(Level.FINEST); } } ExecutorService executor = Executors.newSingleThreadExecutor(); executor.submit(new ITunesRemotePairing()); executor.shutdown(); } /** * */ public ITunesRemotePairing() { super(); } @Override public void run() { try { final JmDNS jmdns = JmDNS.create(); jmdns.addServiceListener(TOUCH_ABLE_TYPE, this); jmdns.addServiceListener(DACP_TYPE, this); final HashMap values = new HashMap(); byte[] number = new byte[4]; random.nextBytes(number); values.put("DvNm", "Android-" + toHex(number)); values.put("RemV", "10000"); values.put("DvTy", "iPod"); values.put("RemN", "Remote"); values.put("txtvers", "1"); byte[] pair = new byte[8]; random.nextBytes(pair); values.put("Pair", toHex(pair)); while (_running) { ServerSocket server = new ServerSocket(0); byte[] name = new byte[20]; random.nextBytes(name); System.out.println("Requesting pairing for " + toHex(name)); ServiceInfo pairservice = ServiceInfo.create(REMOTE_TYPE, toHex(name), server.getLocalPort(), 0, 0, values); jmdns.registerService(pairservice); System.out.println("Waiting for pass code"); final Socket socket = server.accept(); OutputStream output = null; try { output = socket.getOutputStream(); // output the contents for debugging final BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); while (br.ready()) { String line = br.readLine(); System.out.println(line); } // edit our local PAIRING_RAW to return the correct guid byte[] code = new byte[8]; random.nextBytes(code); System.out.println("Device guid: " + toHex(code)); System.arraycopy(code, 0, PAIRING_RAW, 16, 8); byte[] header = String.format("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n", new Integer(PAIRING_RAW.length)).getBytes(); byte[] reply = new byte[header.length + PAIRING_RAW.length]; System.arraycopy(header, 0, reply, 0, header.length); System.arraycopy(PAIRING_RAW, 0, reply, header.length, PAIRING_RAW.length); System.out.println("Response: " + new String(reply)); output.write(reply); output.flush(); System.out.println("someone paired with me!"); jmdns.unregisterService(pairservice); } finally { if (output != null) { output.close(); } System.out.println("Closing Socket"); if (!server.isClosed()) { server.close(); } _running = false; } } Thread.sleep(6000); System.out.println("Closing JmDNS"); jmdns.close(); } catch (Exception e) { e.printStackTrace(); } } @Override public void serviceAdded(ServiceEvent event) { System.out.println("Service added : " + event.getName() + "." + event.getType()); } @Override public void serviceRemoved(ServiceEvent event) { System.out.println("Service removed : " + event.getName() + "." + event.getType()); } @Override public void serviceResolved(ServiceEvent event) { System.out.println("Service resolved: " + event.getName() + "." + event.getType() + "\n" + event.getInfo()); } private static final char[] _nibbleToHex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; private static String toHex(byte[] code) { StringBuilder result = new StringBuilder(2 * code.length); for (int i = 0; i < code.length; i++) { int b = code[i] & 0xFF; result.append(_nibbleToHex[b / 16]); result.append(_nibbleToHex[b % 16]); } return result.toString(); } } jmdns-3.4.1/src/samples/DiscoverServiceTypes.java0000644000175000017500000000516311625404056021736 0ustar mathieumathieu// Licensed under Apache License version 2.0 // Original license LGPL // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package samples; import java.io.IOException; import javax.jmdns.JmDNS; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceTypeListener; /** * Sample Code for Service Type Discovery using JmDNS and a ServiceTypeListener. *

* Run the main method of this class. It lists all service types known on the local network on System.out. * * @author Werner Randelshofer */ public class DiscoverServiceTypes { static class SampleListener implements ServiceTypeListener { @Override public void serviceTypeAdded(ServiceEvent event) { System.out.println("Service type added: " + event.getType()); } /* * (non-Javadoc) * @see javax.jmdns.ServiceTypeListener#subTypeForServiceTypeAdded(javax.jmdns.ServiceEvent) */ @Override public void subTypeForServiceTypeAdded(ServiceEvent event) { System.out.println("SubType for service type added: " + event.getType()); } } /** * @param args * the command line arguments */ public static void main(String[] args) { /* * Activate these lines to see log messages of JmDNS Logger logger = Logger.getLogger(JmDNS.class.getName()); ConsoleHandler handler = new ConsoleHandler(); logger.addHandler(handler); logger.setLevel(Level.FINER); * handler.setLevel(Level.FINER); */ try { JmDNS jmdns = JmDNS.create(); jmdns.addServiceTypeListener(new SampleListener()); System.out.println("Press q and Enter, to quit"); int b; while ((b = System.in.read()) != -1 && (char) b != 'q') { /* Stub */ } jmdns.close(); System.out.println("Done"); } catch (IOException e) { e.printStackTrace(); } } } jmdns-3.4.1/src/com/0000755000175000017500000000000011625404056014054 5ustar mathieumathieujmdns-3.4.1/src/com/strangeberry/0000755000175000017500000000000011625404056016563 5ustar mathieumathieujmdns-3.4.1/src/com/strangeberry/jmdns/0000755000175000017500000000000011625404056017676 5ustar mathieumathieujmdns-3.4.1/src/com/strangeberry/jmdns/tools/0000755000175000017500000000000011625404056021036 5ustar mathieumathieujmdns-3.4.1/src/com/strangeberry/jmdns/tools/Browser.java0000644000175000017500000002730411625404056023332 0ustar mathieumathieu// Licensed under Apache License version 2.0 // Original license LGPL // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package com.strangeberry.jmdns.tools; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.GridLayout; import java.io.IOException; import java.net.InetAddress; import java.util.Enumeration; import javax.jmdns.JmmDNS; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceListener; import javax.jmdns.ServiceTypeListener; import javax.swing.DefaultListModel; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; /** * User Interface for browsing JmDNS services. * * @author Arthur van Hoff, Werner Randelshofer */ public class Browser extends JFrame implements ServiceListener, ServiceTypeListener, ListSelectionListener { /** * */ private static final long serialVersionUID = 5750114542524415107L; JmmDNS jmmdns; // Vector headers; String type; DefaultListModel types; JList typeList; DefaultListModel services; JList serviceList; JTextArea info; /** * @param mmDNS * @throws IOException */ Browser(JmmDNS mmDNS) throws IOException { super("JmDNS Browser"); this.jmmdns = mmDNS; Color bg = new Color(230, 230, 230); EmptyBorder border = new EmptyBorder(5, 5, 5, 5); Container content = getContentPane(); content.setLayout(new GridLayout(1, 3)); types = new DefaultListModel(); typeList = new JList(types); typeList.setBorder(border); typeList.setBackground(bg); typeList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); typeList.addListSelectionListener(this); JPanel typePanel = new JPanel(); typePanel.setLayout(new BorderLayout()); typePanel.add("North", new JLabel("Types")); typePanel.add("Center", new JScrollPane(typeList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); content.add(typePanel); services = new DefaultListModel(); serviceList = new JList(services); serviceList.setBorder(border); serviceList.setBackground(bg); serviceList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); serviceList.addListSelectionListener(this); JPanel servicePanel = new JPanel(); servicePanel.setLayout(new BorderLayout()); servicePanel.add("North", new JLabel("Services")); servicePanel.add("Center", new JScrollPane(serviceList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); content.add(servicePanel); info = new JTextArea(); info.setBorder(border); info.setBackground(bg); info.setEditable(false); info.setLineWrap(true); JPanel infoPanel = new JPanel(); infoPanel.setLayout(new BorderLayout()); infoPanel.add("North", new JLabel("Details")); infoPanel.add("Center", new JScrollPane(info, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); content.add(infoPanel); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocation(100, 100); setSize(600, 400); this.jmmdns.addServiceTypeListener(this); // register some well known types // String list[] = new String[] { "_http._tcp.local.", "_ftp._tcp.local.", "_tftp._tcp.local.", "_ssh._tcp.local.", "_smb._tcp.local.", "_printer._tcp.local.", "_airport._tcp.local.", "_afpovertcp._tcp.local.", "_ichat._tcp.local.", // "_eppc._tcp.local.", "_presence._tcp.local.", "_rfb._tcp.local.", "_daap._tcp.local.", "_touchcs._tcp.local." }; String[] list = new String[] {}; for (int i = 0; i < list.length; i++) { this.jmmdns.registerServiceType(list[i]); } this.setVisible(true); } /* * (non-Javadoc) * @see javax.jmdns.ServiceListener#serviceAdded(javax.jmdns.ServiceEvent) */ @Override public void serviceAdded(ServiceEvent event) { final String name = event.getName(); System.out.println("ADD: " + name); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { insertSorted(services, name); } }); } /* * (non-Javadoc) * @see javax.jmdns.ServiceListener#serviceRemoved(javax.jmdns.ServiceEvent) */ @Override public void serviceRemoved(ServiceEvent event) { final String name = event.getName(); System.out.println("REMOVE: " + name); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { services.removeElement(name); } }); } @Override public void serviceResolved(ServiceEvent event) { final String name = event.getName(); System.out.println("RESOLVED: " + name); if (name.equals(serviceList.getSelectedValue())) { ServiceInfo[] serviceInfos = this.jmmdns.getServiceInfos(type, name); this.dislayInfo(serviceInfos); // this.dislayInfo(new ServiceInfo[] { event.getInfo() }); } } /* * (non-Javadoc) * @see javax.jmdns.ServiceTypeListener#serviceTypeAdded(javax.jmdns.ServiceEvent) */ @Override public void serviceTypeAdded(ServiceEvent event) { final String aType = event.getType(); System.out.println("TYPE: " + aType); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { insertSorted(types, aType); } }); } /* * (non-Javadoc) * @see javax.jmdns.ServiceTypeListener#subTypeForServiceTypeAdded(javax.jmdns.ServiceEvent) */ @Override public void subTypeForServiceTypeAdded(ServiceEvent event) { System.out.println("SUBTYPE: " + event.getType()); } void insertSorted(DefaultListModel model, String value) { for (int i = 0, n = model.getSize(); i < n; i++) { int result = value.compareToIgnoreCase((String) model.elementAt(i)); if (result == 0) { return; } if (result < 0) { model.insertElementAt(value, i); return; } } model.addElement(value); } /** * List selection changed. * * @param e */ @Override public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()) { if (e.getSource() == typeList) { type = (String) typeList.getSelectedValue(); System.out.println("VALUE CHANGED: type: " + type); this.jmmdns.removeServiceListener(type, this); services.setSize(0); info.setText(""); if (type != null) { this.jmmdns.addServiceListener(type, this); } } else if (e.getSource() == serviceList) { String name = (String) serviceList.getSelectedValue(); System.out.println("VALUE CHANGED: type: " + type + " service: " + name); if (name == null) { info.setText(""); } else { ServiceInfo[] serviceInfos = this.jmmdns.getServiceInfos(type, name); // This is actually redundant. getServiceInfo will force the resolution of the service and call serviceResolved this.dislayInfo(serviceInfos); } } } } private void dislayInfo(ServiceInfo[] serviceInfos) { if (serviceInfos.length == 0) { System.out.println("INFO: null"); info.setText("service not found\n"); } else { StringBuilder buf = new StringBuilder(2048); System.out.println("INFO: " + serviceInfos.length); for (ServiceInfo service : serviceInfos) { System.out.println("INFO: " + service); buf.append(service.getName()); buf.append('.'); buf.append(service.getTypeWithSubtype()); buf.append('\n'); buf.append(service.getServer()); buf.append(':'); buf.append(service.getPort()); buf.append('\n'); for (InetAddress address : service.getInetAddresses()) { buf.append(address); buf.append(':'); buf.append(service.getPort()); buf.append('\n'); } for (Enumeration names = service.getPropertyNames(); names.hasMoreElements();) { String prop = names.nextElement(); buf.append(prop); buf.append('='); buf.append(service.getPropertyString(prop)); buf.append('\n'); } buf.append("------------------------\n"); } this.info.setText(buf.toString()); } } /** * Table data. */ class ServiceTableModel extends AbstractTableModel { /** * */ private static final long serialVersionUID = 5607994569609827570L; @Override public String getColumnName(int column) { switch (column) { case 0: return "service"; case 1: return "address"; case 2: return "port"; case 3: return "text"; } return null; } @Override public int getColumnCount() { return 1; } @Override public int getRowCount() { return services.size(); } @Override public Object getValueAt(int row, int col) { return services.elementAt(row); } } @Override public String toString() { return "RVBROWSER"; } /** * Main program. * * @param argv * @throws IOException */ public static void main(String argv[]) throws IOException { new Browser(JmmDNS.Factory.getInstance()); } } jmdns-3.4.1/src/com/strangeberry/jmdns/tools/Responder.java0000644000175000017500000000553711625404056023654 0ustar mathieumathieu// Licensed under Apache License version 2.0 // Original license LGPL // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package com.strangeberry.jmdns.tools; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import javax.jmdns.JmDNS; import javax.jmdns.ServiceInfo; /** * A sample JmDNS responder that reads a set of rendezvous service definitions from a file and registers them with rendezvous. It uses the same file format as Apple's responder. Each record consists of 4 lines: name, type, text, port. Empty lines and * lines starting with # between records are ignored. * * @author Arthur van Hoff */ public class Responder { /** * Constructor. * * @param jmdns * @param file * @throws IOException */ public Responder(JmDNS jmdns, String file) throws IOException { BufferedReader in = new BufferedReader(new FileReader(file)); try { while (true) { String ln = in.readLine(); while ((ln != null) && (ln.startsWith("#") || ln.trim().length() == 0)) { ln = in.readLine(); } if (ln != null) { String name = ln; String type = in.readLine(); String text = in.readLine(); int port = Integer.parseInt(in.readLine()); // make sure the type is fully qualified and in the local. domain if (type != null) { if (!type.endsWith(".")) { type += "."; } if (!type.endsWith(".local.")) { type += "local."; } jmdns.registerService(ServiceInfo.create(type, name, port, text)); } } } } finally { in.close(); } } /** * Create a responder. * * @param argv * @throws IOException */ public static void main(String argv[]) throws IOException { new Responder(JmDNS.create(), (argv.length > 0) ? argv[0] : "services.txt"); } } jmdns-3.4.1/src/com/strangeberry/jmdns/tools/Main.java0000644000175000017500000001527411625404056022576 0ustar mathieumathieu// Licensed under Apache License version 2.0 // Original license LGPL // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package com.strangeberry.jmdns.tools; import java.io.IOException; import java.net.InetAddress; import java.util.Enumeration; import java.util.Hashtable; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; import javax.jmdns.JmDNS; import javax.jmdns.JmmDNS; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceListener; import javax.jmdns.ServiceTypeListener; /** * Main sample program for JmDNS. * * @author Arthur van Hoff, Werner Randelshofer */ public class Main { static class SampleListener implements ServiceListener, ServiceTypeListener { /* * (non-Javadoc) * @see javax.jmdns.ServiceListener#serviceAdded(javax.jmdns.ServiceEvent) */ @Override public void serviceAdded(ServiceEvent event) { System.out.println("ADD: " + event.getDNS().getServiceInfo(event.getType(), event.getName())); } /* * (non-Javadoc) * @see javax.jmdns.ServiceListener#serviceRemoved(javax.jmdns.ServiceEvent) */ @Override public void serviceRemoved(ServiceEvent event) { System.out.println("REMOVE: " + event.getName()); } /* * (non-Javadoc) * @see javax.jmdns.ServiceListener#serviceResolved(javax.jmdns.ServiceEvent) */ @Override public void serviceResolved(ServiceEvent event) { System.out.println("RESOLVED: " + event.getInfo()); } /* * (non-Javadoc) * @see javax.jmdns.ServiceTypeListener#serviceTypeAdded(javax.jmdns.ServiceEvent) */ @Override public void serviceTypeAdded(ServiceEvent event) { System.out.println("TYPE: " + event.getType()); } /* * (non-Javadoc) * @see javax.jmdns.ServiceTypeListener#subTypeForServiceTypeAdded(javax.jmdns.ServiceEvent) */ @Override public void subTypeForServiceTypeAdded(ServiceEvent event) { System.out.println("SUBTYPE: " + event.getType()); } } public static void main(String argv[]) throws IOException { int argc = argv.length; boolean debug = false; InetAddress intf = null; if ((argc > 0) && "-d".equals(argv[0])) { System.arraycopy(argv, 1, argv, 0, --argc); { ConsoleHandler handler = new ConsoleHandler(); handler.setLevel(Level.FINEST); for (Enumeration enumerator = LogManager.getLogManager().getLoggerNames(); enumerator.hasMoreElements();) { String loggerName = enumerator.nextElement(); Logger logger = Logger.getLogger(loggerName); logger.addHandler(handler); logger.setLevel(Level.FINEST); } } debug = true; } if ((argc > 1) && "-i".equals(argv[0])) { intf = InetAddress.getByName(argv[1]); System.arraycopy(argv, 2, argv, 0, argc -= 2); } if (intf == null) { intf = InetAddress.getLocalHost(); } JmDNS jmdns = JmDNS.create(intf, "Browser"); if ((argc == 0) || ((argc >= 1) && "-browse".equals(argv[0]))) { new Browser(JmmDNS.Factory.getInstance()); for (int i = 2; i < argc; i++) { jmdns.registerServiceType(argv[i]); } } else if ((argc == 1) && "-bt".equals(argv[0])) { jmdns.addServiceTypeListener(new SampleListener()); } else if ((argc == 3) && "-bs".equals(argv[0])) { jmdns.addServiceListener(argv[1] + "." + argv[2], new SampleListener()); } else if ((argc > 4) && "-rs".equals(argv[0])) { String type = argv[2] + "." + argv[3]; String name = argv[1]; Hashtable props = null; for (int i = 5; i < argc; i++) { int j = argv[i].indexOf('='); if (j < 0) { throw new RuntimeException("not key=val: " + argv[i]); } if (props == null) { props = new Hashtable(); } props.put(argv[i].substring(0, j), argv[i].substring(j + 1)); } jmdns.registerService(ServiceInfo.create(type, name, Integer.parseInt(argv[4]), 0, 0, props)); // This while loop keeps the main thread alive while (true) { try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { break; } } } else if ((argc == 2) && "-f".equals(argv[0])) { new Responder(JmDNS.create(intf, "Responder"), argv[1]); } else if (!debug) { System.out.println(); System.out.println("jmdns:"); System.out.println(" -d - output debugging info"); System.out.println(" -i - specify the interface address"); System.out.println(" -browse [...] - GUI browser (default)"); System.out.println(" -bt - browse service types"); System.out.println(" -bs - browse services by type"); System.out.println(" -rs - register service"); System.out.println(" -f - rendezvous responder"); System.out.println(); System.exit(1); } } } jmdns-3.4.1/src/javax/0000755000175000017500000000000011625404056014407 5ustar mathieumathieujmdns-3.4.1/src/javax/jmdns/0000755000175000017500000000000011625404056015522 5ustar mathieumathieujmdns-3.4.1/src/javax/jmdns/ServiceListener.java0000644000175000017500000000336311625404056021500 0ustar mathieumathieu// 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. * * @author Arthur van Hoff, Werner Randelshofer, Pierre Frisch */ public interface ServiceListener extends EventListener { /** * A service has been added.
* Note:This event is only the service added event. The service info associated with this event does not include resolution information.
* To get the full resolved information you need to listen to {@link #serviceResolved(ServiceEvent)} or call {@link JmDNS#getServiceInfo(String, String, long)} * *

     *  ServiceInfo info = event.getDNS().getServiceInfo(event.getType(), event.getName())
     * 
*

* Please note that service resolution may take a few second to resolve. *

* * @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.
* Note:This call back will never be called if the service does not resolve.
* * @param event * The ServiceEvent providing the name, the fully qualified type of the service, and the service info record. */ void serviceResolved(ServiceEvent event); } jmdns-3.4.1/src/javax/jmdns/JmmDNS.java0000644000175000017500000003744211625404056017467 0ustar mathieumathieu/** * */ package javax.jmdns; import java.io.Closeable; import java.io.IOException; import java.net.InetAddress; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import javax.jmdns.impl.JmmDNSImpl; /** *

* Java Multihomed Multicast DNS *

* Uses an underlying {@link JmDNS} instance for each {@link InetAddress} found on this computer.
* This class will monitor network topology changes, and will create or destroy JmDNS instances as required. It is your responsibility to maintain services registration (hint: use a {@link NetworkTopologyListener}).
* Most of this class methods have no notion of transaction: if an Exception is raised in the middle of execution, you may be in an incoherent state. *

* Note: This API is experimental and may change in the future please let us know what work and what does not work in you application. *

* * @author Cédrik Lime, Pierre Frisch */ public interface JmmDNS extends Closeable { /** * JmmDNS.Factory enable the creation of new instance of JmmDNS. */ public static final class Factory { private static volatile JmmDNS _instance; /** * This interface defines a delegate to the EOClassDescriptionRegister class to enable subclassing. */ public static interface ClassDelegate { /** * Allows the delegate the opportunity to construct and return a different JmmDNS. * * @return Should return a new JmmDNS. * @see #classDelegate() * @see #setClassDelegate(ClassDelegate anObject) */ public JmmDNS newJmmDNS(); } private static final AtomicReference _databaseClassDelegate = new AtomicReference(); private Factory() { super(); } /** * Assigns delegate as JmmDNS's class delegate. The class delegate is optional. * * @param delegate * The object to set as JmmDNS's class delegate. * @see #classDelegate() * @see JmmDNS.Factory.ClassDelegate */ public static void setClassDelegate(ClassDelegate delegate) { _databaseClassDelegate.set(delegate); } /** * Returns JmmDNS's class delegate. * * @return JmmDNS's class delegate. * @see #setClassDelegate(ClassDelegate anObject) * @see JmmDNS.Factory.ClassDelegate */ public static ClassDelegate classDelegate() { return _databaseClassDelegate.get(); } /** * Returns a new instance of JmmDNS using the class delegate if it exists. * * @return new instance of JmmDNS */ protected static JmmDNS newJmmDNS() { JmmDNS dns = null; ClassDelegate delegate = _databaseClassDelegate.get(); if (delegate != null) { dns = delegate.newJmmDNS(); } return (dns != null ? dns : new JmmDNSImpl()); } /** * Return the instance of the Multihommed Multicast DNS. * * @return the JmmDNS */ public static JmmDNS getInstance() { if (_instance == null) { synchronized (Factory.class) { if (_instance == null) { _instance = JmmDNS.Factory.newJmmDNS(); } } } return _instance; } } /** * Return the names of the JmDNS instances. * * @return list of name of the JmDNS * @see javax.jmdns.JmDNS#getName() */ public abstract String[] getNames(); /** * Return the list HostName associated with this JmmDNS instance. * * @return list of host names * @see javax.jmdns.JmDNS#getHostName() */ public abstract String[] getHostNames(); /** * Return the list of addresses of the interface to which this instance of JmmDNS is bound. * * @return list of Internet Address * @exception IOException * @see javax.jmdns.JmDNS#getInterface() */ public abstract InetAddress[] getInterfaces() throws IOException; /** * Get service information. If the information is not cached, the method will block until updated information is received on all DNS. *

* 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @return list of service info. If no service info is found the list is empty. * @see javax.jmdns.JmDNS#getServiceInfo(java.lang.String, java.lang.String) */ public abstract ServiceInfo[] getServiceInfos(String type, String name); /** * Get service information. If the information is not cached, the method will block until updated information is received on all DNS. *

* 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @param timeout * timeout in milliseconds. Typical timeout should be 5s. * @return list of service info. If no service info is found the list is empty. * @see javax.jmdns.JmDNS#getServiceInfo(java.lang.String, java.lang.String, long) */ public abstract ServiceInfo[] getServiceInfos(String type, String name, long timeout); /** * Get service information. If the information is not cached, the method will block until updated information is received on all DNS. *

* 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @return list of service info. If no service info is found the list is empty. * @see javax.jmdns.JmDNS#getServiceInfo(java.lang.String, java.lang.String, boolean) */ public abstract ServiceInfo[] getServiceInfos(String type, String name, boolean persistent); /** * Get service information. If the information is not cached, the method will block until updated information is received on all DNS. *

* 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @param timeout * timeout in milliseconds. Typical timeout should be 5s. * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @return list of service info. If no service info is found the list is empty. * @see javax.jmdns.JmDNS#getServiceInfo(java.lang.String, java.lang.String, boolean, long) */ public abstract ServiceInfo[] getServiceInfos(String type, String name, boolean persistent, long timeout); /** * 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @see javax.jmdns.JmDNS#requestServiceInfo(java.lang.String, java.lang.String) */ 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @see javax.jmdns.JmDNS#requestServiceInfo(java.lang.String, java.lang.String, boolean) */ public abstract void requestServiceInfo(String type, String name, boolean persistent); /** * 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @param timeout * timeout in milliseconds * @see javax.jmdns.JmDNS#requestServiceInfo(java.lang.String, java.lang.String, long) */ public abstract void requestServiceInfo(String type, String name, long timeout); /** * 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @param timeout * timeout in milliseconds * @see javax.jmdns.JmDNS#requestServiceInfo(java.lang.String, java.lang.String, boolean, long) */ public abstract void requestServiceInfo(String type, String name, boolean persistent, long timeout); /** * Listen for service types. * * @param listener * listener for service types * @exception IOException * @see javax.jmdns.JmDNS#addServiceTypeListener(javax.jmdns.ServiceTypeListener) */ public abstract void addServiceTypeListener(ServiceTypeListener listener) throws IOException; /** * Remove listener for service types. * * @param listener * listener for service types * @see javax.jmdns.JmDNS#removeServiceTypeListener(javax.jmdns.ServiceTypeListener) */ 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 _http._tcp.local.. * * @param type * full qualified service type, such as _http._tcp.local.. * @param listener * listener for service updates * @see javax.jmdns.JmDNS#addServiceListener(java.lang.String, javax.jmdns.ServiceListener) */ public abstract void addServiceListener(String type, ServiceListener listener); /** * Remove listener for services of a given type. * * @param type * full qualified service type, such as _http._tcp.local.. * @param listener * listener for service updates * @see javax.jmdns.JmDNS#removeServiceListener(java.lang.String, javax.jmdns.ServiceListener) */ 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.
* Note the Service info is cloned for each network interface. * * @param info * service info to register * @exception IOException * @see javax.jmdns.JmDNS#registerService(javax.jmdns.ServiceInfo) */ public abstract void registerService(ServiceInfo info) throws IOException; /** * Unregister a service. The service should have been registered. * * @param info * service info to remove * @see javax.jmdns.JmDNS#unregisterService(javax.jmdns.ServiceInfo) */ public abstract void unregisterService(ServiceInfo info); /** * Unregister all services. * * @see javax.jmdns.JmDNS#unregisterAllServices() */ 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. * * @param type * full qualified service type, such as _http._tcp.local.. * @see javax.jmdns.JmDNS#registerServiceType(java.lang.String) */ public abstract void registerServiceType(String type); /** * Returns a list of service infos of the specified type. * * @param type * Service type name, such as _http._tcp.local.. * @return An array of service instance. * @see javax.jmdns.JmDNS#list(java.lang.String) */ public abstract ServiceInfo[] list(String type); /** * Returns a list of service infos of the specified type. * * @param type * Service type name, such as _http._tcp.local.. * @param timeout * timeout in milliseconds. Typical timeout should be 6s. * @return An array of service instance. * @see javax.jmdns.JmDNS#list(java.lang.String, long) */ public abstract ServiceInfo[] list(String type, long timeout); /** * Returns a list of service infos of the specified type sorted by subtype. Any service that do not register a subtype is listed in the empty subtype section. * * @param type * Service type name, such as _http._tcp.local.. * @return A dictionary of service info by subtypes. * @see javax.jmdns.JmDNS#listBySubtype(java.lang.String) */ public abstract Map listBySubtype(String type); /** * Returns a list of service infos of the specified type sorted by subtype. Any service that do not register a subtype is listed in the empty subtype section. * * @param type * Service type name, such as _http._tcp.local.. * @param timeout * timeout in milliseconds. Typical timeout should be 6s. * @return A dictionary of service info by subtypes. * @see javax.jmdns.JmDNS#listBySubtype(java.lang.String, long) */ public abstract Map listBySubtype(String type, long timeout); /** * Listen to network changes. * * @param listener * listener for network changes */ public abstract void addNetworkTopologyListener(NetworkTopologyListener listener); /** * Remove listener for network changes. * * @param listener * listener for network changes */ public abstract void removeNetworkTopologyListener(NetworkTopologyListener listener); /** * Returns list of network change listeners * * @return list of network change listeners */ public abstract NetworkTopologyListener[] networkListeners(); } jmdns-3.4.1/src/javax/jmdns/JmDNS.java0000644000175000017500000004215611625404056017310 0ustar mathieumathieu// /Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns; import java.io.Closeable; import java.io.IOException; import java.net.InetAddress; import java.util.Collection; import java.util.Map; import javax.jmdns.impl.JmDNSImpl; /** * mDNS implementation in Java. * * @author Arthur van Hoff, Rick Blair, Jeff Sonstein, Werner Randelshofer, Pierre Frisch, Scott Lewis, Scott Cytacki */ public abstract class JmDNS implements Closeable { /** * */ public static interface Delegate { /** * This method is called if JmDNS cannot recover from an I/O error. * * @param dns * target DNS * @param infos * service info registered with the DNS */ public void cannotRecoverFromIOError(JmDNS dns, Collection infos); } /** * The version of JmDNS. */ public static final String VERSION = "3.4.1"; /** *

* Create an instance of JmDNS. *

*

* Note: This is a convenience method. The preferred constructor is {@link #create(InetAddress, String)}.
* Check that your platform correctly handle the default localhost IP address and the local hostname. In doubt use the explicit constructor.
* This call is equivalent to create(null, null). *

* * @see #create(InetAddress, String) * @return jmDNS instance * @exception IOException * if an exception occurs during the socket creation */ public static JmDNS create() throws IOException { return new JmDNSImpl(null, null); } /** *

* Create an instance of JmDNS and bind it to a specific network interface given its IP-address. *

*

* Note: This is a convenience method. The preferred constructor is {@link #create(InetAddress, String)}.
* Check that your platform correctly handle the default localhost IP address and the local hostname. In doubt use the explicit constructor.
* This call is equivalent to create(addr, null). *

* * @see #create(InetAddress, String) * @param addr * IP address to bind to. * @return jmDNS instance * @exception IOException * if an exception occurs during the socket creation */ public static JmDNS create(final InetAddress addr) throws IOException { return new JmDNSImpl(addr, null); } /** *

* Create an instance of JmDNS. *

*

* Note: This is a convenience method. The preferred constructor is {@link #create(InetAddress, String)}.
* Check that your platform correctly handle the default localhost IP address and the local hostname. In doubt use the explicit constructor.
* This call is equivalent to create(null, name). *

* * @see #create(InetAddress, String) * @param name * name of the newly created JmDNS * @return jmDNS instance * @exception IOException * if an exception occurs during the socket creation */ public static JmDNS create(final String name) throws IOException { return new JmDNSImpl(null, name); } /** *

* Create an instance of JmDNS and bind it to a specific network interface given its IP-address. *

* If addr parameter is null this method will try to resolve to a local IP address of the machine using a network discovery: *
    *
  1. Check the system property net.mdns.interface
  2. *
  3. Check the JVM local host
  4. *
  5. Use the {@link NetworkTopologyDiscovery} to find a valid network interface and IP.
  6. *
  7. In the last resort bind to the loopback address. This is non functional in most cases.
  8. *
* If name parameter is null will use the hostname. The hostname is determined by the following algorithm: *
    *
  1. Get the hostname from the InetAdress obtained before.
  2. *
  3. If the hostname is a reverse lookup default to JmDNS name or computer if null.
  4. *
  5. If the name contains '.' replace them by '-'
  6. *
  7. Add .local. at the end of the name.
  8. *
*

* Note: If you need to use a custom {@link NetworkTopologyDiscovery} it must be setup before any call to this method. This is done by setting up a {@link NetworkTopologyDiscovery.Factory.ClassDelegate} and installing it using * {@link NetworkTopologyDiscovery.Factory#setClassDelegate(NetworkTopologyDiscovery.Factory.ClassDelegate)}. This must be done before creating a {@link JmDNS} or {@link JmmDNS} instance. *

* * @param addr * IP address to bind to. * @param name * name of the newly created JmDNS * @return jmDNS instance * @exception IOException * if an exception occurs during the socket creation */ public static JmDNS create(final InetAddress addr, final String name) throws IOException { return new JmDNSImpl(addr, name); } /** * Return the name of the JmDNS instance. This is an arbitrary string that is useful for distinguishing instances. * * @return name of the JmDNS */ public abstract String getName(); /** * Return the HostName associated with this JmDNS instance. Note: May not be the same as what started. The host name is subject to negotiation. * * @return Host name */ public abstract String getHostName(); /** * Return the address of the interface to which this instance of JmDNS is bound. * * @return Internet Address * @exception IOException * if there is an error in the underlying protocol, such as a TCP error. */ public abstract InetAddress getInterface() throws IOException; /** * Get service information. If the information is not cached, the method will block until updated information is received. *

* 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @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. *

* 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @param timeout * timeout in milliseconds. Typical timeout should be 5s. * @return null if the service information cannot be obtained */ public abstract ServiceInfo getServiceInfo(String type, String name, long timeout); /** * Get service information. If the information is not cached, the method will block until updated information is received. *

* 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @return null if the service information cannot be obtained */ public abstract ServiceInfo getServiceInfo(String type, String name, boolean persistent); /** * Get service information. If the information is not cached, the method will block for the given timeout until updated information is received. *

* 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @param timeout * timeout in milliseconds. Typical timeout should be 5s. * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @return null if the service information cannot be obtained */ public abstract ServiceInfo getServiceInfo(String type, String name, boolean persistent, long timeout); /** * Request service information. The information about the service is requested and the ServiceListener.resolveService method is called as soon as it is available. *

* 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . */ 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. *

* 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. */ public abstract void requestServiceInfo(String type, String name, boolean persistent); /** * 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @param timeout * timeout in milliseconds */ public abstract void requestServiceInfo(String type, String name, long timeout); /** * 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 _http._tcp.local. . * @param name * unqualified service name, such as foobar . * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @param timeout * timeout in milliseconds */ public abstract void requestServiceInfo(String type, String name, boolean persistent, long timeout); /** * Listen for service types. * * @param listener * listener for service types * @exception IOException * if there is an error in the underlying protocol, such as a TCP error. */ 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 _http._tcp.local.. * * @param type * full qualified service type, such as _http._tcp.local.. * @param listener * listener for service updates */ public abstract void addServiceListener(String type, ServiceListener listener); /** * Remove listener for services of a given type. * * @param type * full qualified service type, such as _http._tcp.local.. * @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.
* Note that the given {@code ServiceInfo} is bound to this {@code JmDNS} instance, and should not be reused for any other {@linkplain #registerService(ServiceInfo)}. * * @param info * service info to register * @exception IOException * if there is an error in the underlying protocol, such as a TCP error. */ public abstract void registerService(ServiceInfo info) throws IOException; /** * Unregister a service. The service should have been registered. *

* Note: Unregistered services will not disappear form the list of services immediately. According to the specification, when unregistering services we send goodbye packets and then wait 1s before purging the cache.
* This is support for shared records that can be rescued by some other cooperation DNS. * *

     * Clients receiving a Multicast DNS Response with a TTL of zero SHOULD NOT immediately delete the record from the cache, but instead record a TTL of 1 and then delete the record one second later.
     * 
* *

* * @param info * service info to remove */ 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. *

* * @param type * full qualified service type, such as _http._tcp.local.. * @return true if the type or subtype was added, false if the type was already registered. */ public abstract boolean registerServiceType(String type); /** * List Services and serviceTypes. Debugging Only * * @deprecated since 3.2.2 */ @Deprecated public abstract void printServices(); /** * Returns a list of service infos of the specified type. * * @param type * Service type name, such as _http._tcp.local.. * @return An array of service instance. */ public abstract ServiceInfo[] list(String type); /** * Returns a list of service infos of the specified type. * * @param type * Service type name, such as _http._tcp.local.. * @param timeout * timeout in milliseconds. Typical timeout should be 6s. * @return An array of service instance. */ public abstract ServiceInfo[] list(String type, long timeout); /** * Returns a list of service infos of the specified type sorted by subtype. Any service that do not register a subtype is listed in the empty subtype section. * * @param type * Service type name, such as _http._tcp.local.. * @return A dictionary of service info by subtypes. */ public abstract Map listBySubtype(String type); /** * Returns a list of service infos of the specified type sorted by subtype. Any service that do not register a subtype is listed in the empty subtype section. * * @param type * Service type name, such as _http._tcp.local.. * @param timeout * timeout in milliseconds. Typical timeout should be 6s. * @return A dictionary of service info by subtypes. */ public abstract Map listBySubtype(String type, long timeout); /** * Returns the instance delegate * * @return instance delegate */ public abstract Delegate getDelegate(); /** * Sets the instance delegate * * @param value * new instance delegate * @return previous instance delegate */ public abstract Delegate setDelegate(Delegate value); } jmdns-3.4.1/src/javax/jmdns/NetworkTopologyDiscovery.java0000644000175000017500000001245711625404056023454 0ustar mathieumathieupackage javax.jmdns; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.concurrent.atomic.AtomicReference; import javax.jmdns.impl.NetworkTopologyDiscoveryImpl; /** * This class is used to resolve the list of Internet address to use when attaching JmDNS to the network. *

* To create you own filtering class for Internet Addresses you will need to implement the class and the factory delegate. These must be called before any other call to JmDNS. * *

 * public static class MyNetworkTopologyDiscovery implements NetworkTopologyDiscovery {
 *
 *     @Override
 *     public InetAddress[] getInetAddresses() {
 *         // TODO Auto-generated method stub
 *         return null;
 *     }
 *
 *     @Override
 *     public boolean useInetAddress(NetworkInterface networkInterface, InetAddress interfaceAddress) {
 *         // TODO Auto-generated method stub
 *         return false;
 *     }
 *
 * }
 *
 * public static class MyClass implements NetworkTopologyDiscovery.Factory.ClassDelegate {
 *     public MyClass() {
 *         super();
 *         NetworkTopologyDiscovery.Factory.setClassDelegate(this);
 *
 *         // Access JmDNS or JmmDNS
 *     }
 *
 *     @Override
 *     public NetworkTopologyDiscovery newNetworkTopologyDiscovery() {
 *         return new MyNetworkTopologyDiscovery();
 *     }
 *
 * }
 * 
* *

* * @author Pierre Frisch */ public interface NetworkTopologyDiscovery { /** * NetworkTopologyDiscovery.Factory enable the creation of new instance of NetworkTopologyDiscovery. */ public static final class Factory { private static volatile NetworkTopologyDiscovery _instance; /** * This interface defines a delegate to the NetworkTopologyDiscovery.Factory class to enable subclassing. */ public static interface ClassDelegate { /** * Allows the delegate the opportunity to construct and return a different NetworkTopologyDiscovery. * * @return Should return a new NetworkTopologyDiscovery Object. * @see #classDelegate() * @see #setClassDelegate(ClassDelegate anObject) */ public NetworkTopologyDiscovery newNetworkTopologyDiscovery(); } private static final AtomicReference _databaseClassDelegate = new AtomicReference(); private Factory() { super(); } /** * Assigns delegate as NetworkTopologyDiscovery's class delegate. The class delegate is optional. * * @param delegate * The object to set as NetworkTopologyDiscovery's class delegate. * @see #classDelegate() * @see JmmDNS.Factory.ClassDelegate */ public static void setClassDelegate(Factory.ClassDelegate delegate) { _databaseClassDelegate.set(delegate); } /** * Returns NetworkTopologyDiscovery's class delegate. * * @return NetworkTopologyDiscovery's class delegate. * @see #setClassDelegate(ClassDelegate anObject) * @see JmmDNS.Factory.ClassDelegate */ public static Factory.ClassDelegate classDelegate() { return _databaseClassDelegate.get(); } /** * Returns a new instance of NetworkTopologyDiscovery using the class delegate if it exists. * * @return new instance of NetworkTopologyDiscovery */ protected static NetworkTopologyDiscovery newNetworkTopologyDiscovery() { NetworkTopologyDiscovery instance = null; Factory.ClassDelegate delegate = _databaseClassDelegate.get(); if (delegate != null) { instance = delegate.newNetworkTopologyDiscovery(); } return (instance != null ? instance : new NetworkTopologyDiscoveryImpl()); } /** * Return the instance of the Multihommed Multicast DNS. * * @return the JmmDNS */ public static NetworkTopologyDiscovery getInstance() { if (_instance == null) { synchronized (NetworkTopologyDiscovery.Factory.class) { if (_instance == null) { _instance = NetworkTopologyDiscovery.Factory.newNetworkTopologyDiscovery(); } } } return _instance; } } /** * Get all local Internet Addresses for the machine. * * @return Set of InetAddress */ public abstract InetAddress[] getInetAddresses(); /** * Check if a given InetAddress should be used for mDNS * * @param networkInterface * @param interfaceAddress * @return true is the address is to be used, false otherwise. */ public boolean useInetAddress(NetworkInterface networkInterface, InetAddress interfaceAddress); /** * Locks the given InetAddress if the device requires it. * * @param interfaceAddress */ public void lockInetAddress(InetAddress interfaceAddress); /** * Locks the given InetAddress if the device requires it. * * @param interfaceAddress */ public void unlockInetAddress(InetAddress interfaceAddress); }jmdns-3.4.1/src/javax/jmdns/ServiceInfo.java0000644000175000017500000006525011625404056020611 0ustar mathieumathieu// Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.Enumeration; import java.util.Map; import javax.jmdns.impl.ServiceInfoImpl; /** *

* The fully qualified service name is build using up to 5 components with the following structure: * *

 *            <app>.<protocol>.<servicedomain>.<parentdomain>.
* <Instance>.<app>.<protocol>.<servicedomain>.<parentdomain>.
* <sub>._sub.<app>.<protocol>.<servicedomain>.<parentdomain>. *
* *
    *
  1. <servicedomain>.<parentdomain>: This is the domain scope of the service typically "local.", but this can also be something similar to "in-addr.arpa." or "ip6.arpa."
  2. *
  3. <protocol>: This is either "_tcp" or "_udp"
  4. *
  5. <app>: This define the application protocol. Typical example are "_http", "_ftp", etc.
  6. *
  7. <Instance>: This is the service name
  8. *
  9. <sub>: This is the subtype for the application protocol
  10. *
*

*/ public abstract class ServiceInfo implements Cloneable { /** * This is the no value text byte. According top the specification it is one byte with 0 value. */ public static final byte[] NO_VALUE = new byte[0]; /** * Fields for the fully qualified map. */ public enum Fields { /** * Domain Field. */ Domain, /** * Protocol Field. */ Protocol, /** * Application Field. */ Application, /** * Instance Field. */ Instance, /** * Subtype Field. */ Subtype } /** * Construct a service description for registering with JmDNS. * * @param type * fully qualified service type name, such as _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @param port * the local port on which the service runs * @param text * string describing the service * @return new service info */ public static ServiceInfo create(final String type, final String name, final int port, final String text) { return new ServiceInfoImpl(type, name, "", port, 0, 0, false, text); } /** * Construct a service description for registering with JmDNS. * * @param type * fully qualified service type name, such as _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @param subtype * service subtype see draft-cheshire-dnsext-dns-sd-06.txt chapter 7.1 Selective Instance Enumeration * @param port * the local port on which the service runs * @param text * string describing the service * @return new service info */ public static ServiceInfo create(final String type, final String name, final String subtype, final int port, final String text) { return new ServiceInfoImpl(type, name, subtype, port, 0, 0, false, text); } /** * Construct a service description for registering with JmDNS. * * @param type * fully qualified service type name, such as _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @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 * @return new service info */ public static ServiceInfo create(final String type, final String name, final int port, final int weight, final int priority, final String text) { return new ServiceInfoImpl(type, name, "", port, weight, priority, false, text); } /** * Construct a service description for registering with JmDNS. * * @param type * fully qualified service type name, such as _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @param subtype * service subtype see draft-cheshire-dnsext-dns-sd-06.txt chapter 7.1 Selective Instance Enumeration * @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 * @return new service info */ public static ServiceInfo create(final String type, final String name, final String subtype, final int port, final int weight, final int priority, final String text) { return new ServiceInfoImpl(type, name, subtype, port, weight, priority, false, text); } /** * Construct a service description for registering 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 _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @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 * @return new service info */ public static ServiceInfo create(final String type, final String name, final int port, final int weight, final int priority, final Map props) { return new ServiceInfoImpl(type, name, "", port, weight, priority, false, props); } /** * Construct a service description for registering 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 _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @param subtype * service subtype see draft-cheshire-dnsext-dns-sd-06.txt chapter 7.1 Selective Instance Enumeration * @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 * @return new service info */ public static ServiceInfo create(final String type, final String name, final String subtype, final int port, final int weight, final int priority, final Map props) { return new ServiceInfoImpl(type, name, subtype, port, weight, priority, false, props); } /** * Construct a service description for registering with JmDNS. * * @param type * fully qualified service type name, such as _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @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 * @return new service info */ public static ServiceInfo create(final String type, final String name, final int port, final int weight, final int priority, final byte[] text) { return new ServiceInfoImpl(type, name, "", port, weight, priority, false, text); } /** * Construct a service description for registering with JmDNS. * * @param type * fully qualified service type name, such as _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @param subtype * service subtype see draft-cheshire-dnsext-dns-sd-06.txt chapter 7.1 Selective Instance Enumeration * @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 * @return new service info */ public static ServiceInfo create(final String type, final String name, final String subtype, final int port, final int weight, final int priority, final byte[] text) { return new ServiceInfoImpl(type, name, subtype, port, weight, priority, false, text); } /** * Construct a service description for registering with JmDNS. * * @param type * fully qualified service type name, such as _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @param port * the local port on which the service runs * @param weight * weight of the service * @param priority * priority of the service * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @param text * string describing the service * @return new service info */ public static ServiceInfo create(final String type, final String name, final int port, final int weight, final int priority, final boolean persistent, final String text) { return new ServiceInfoImpl(type, name, "", port, weight, priority, persistent, text); } /** * Construct a service description for registering with JmDNS. * * @param type * fully qualified service type name, such as _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @param subtype * service subtype see draft-cheshire-dnsext-dns-sd-06.txt chapter 7.1 Selective Instance Enumeration * @param port * the local port on which the service runs * @param weight * weight of the service * @param priority * priority of the service * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @param text * string describing the service * @return new service info */ public static ServiceInfo create(final String type, final String name, final String subtype, final int port, final int weight, final int priority, final boolean persistent, final String text) { return new ServiceInfoImpl(type, name, subtype, port, weight, priority, persistent, text); } /** * Construct a service description for registering 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 _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @param port * the local port on which the service runs * @param weight * weight of the service * @param priority * priority of the service * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @param props * properties describing the service * @return new service info */ public static ServiceInfo create(final String type, final String name, final int port, final int weight, final int priority, final boolean persistent, final Map props) { return new ServiceInfoImpl(type, name, "", port, weight, priority, persistent, props); } /** * Construct a service description for registering 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 _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @param subtype * service subtype see draft-cheshire-dnsext-dns-sd-06.txt chapter 7.1 Selective Instance Enumeration * @param port * the local port on which the service runs * @param weight * weight of the service * @param priority * priority of the service * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @param props * properties describing the service * @return new service info */ public static ServiceInfo create(final String type, final String name, final String subtype, final int port, final int weight, final int priority, final boolean persistent, final Map props) { return new ServiceInfoImpl(type, name, subtype, port, weight, priority, persistent, props); } /** * Construct a service description for registering with JmDNS. * * @param type * fully qualified service type name, such as _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @param port * the local port on which the service runs * @param weight * weight of the service * @param priority * priority of the service * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @param text * bytes describing the service * @return new service info */ public static ServiceInfo create(final String type, final String name, final int port, final int weight, final int priority, final boolean persistent, final byte[] text) { return new ServiceInfoImpl(type, name, "", port, weight, priority, persistent, text); } /** * Construct a service description for registering with JmDNS. * * @param type * fully qualified service type name, such as _http._tcp.local.. * @param name * unqualified service instance name, such as foobar * @param subtype * service subtype see draft-cheshire-dnsext-dns-sd-06.txt chapter 7.1 Selective Instance Enumeration * @param port * the local port on which the service runs * @param weight * weight of the service * @param priority * priority of the service * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @param text * bytes describing the service * @return new service info */ public static ServiceInfo create(final String type, final String name, final String subtype, final int port, final int weight, final int priority, final boolean persistent, final byte[] text) { return new ServiceInfoImpl(type, name, subtype, port, weight, priority, persistent, text); } /** * Construct a service description for registering with JmDNS. The properties hashtable must map property names to either Strings or byte arrays describing the property values. * * @param qualifiedNameMap * dictionary of values to build the fully qualified service name. Mandatory keys are Application and Instance. The Domain default is local, the Protocol default is tcp and the subtype default is none. * @param port * the local port on which the service runs * @param weight * weight of the service * @param priority * priority of the service * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @param props * properties describing the service * @return new service info */ public static ServiceInfo create(final Map qualifiedNameMap, final int port, final int weight, final int priority, final boolean persistent, final Map props) { return new ServiceInfoImpl(qualifiedNameMap, port, weight, priority, persistent, props); } /** * Returns true if the service info is filled with data. * * @return true if the service info has data, false otherwise. */ public abstract boolean hasData(); /** * Fully qualified service type name, such as _http._tcp.local. * * @return service type name */ public abstract String getType(); /** * Fully qualified service type name with the subtype if appropriate, such as _printer._sub._http._tcp.local. * * @return service type name */ public abstract String getTypeWithSubtype(); /** * Unqualified service instance name, such as foobar . * * @return service name */ public abstract String getName(); /** * The key is used to retrieve service info in hash tables.
* The key is the lower case qualified name. * * @return the key */ public abstract String getKey(); /** * Fully qualified service name, such as foobar._http._tcp.local. . * * @return qualified service name */ public abstract String getQualifiedName(); /** * Get the name of the server. * * @return server name */ public abstract String getServer(); /** * Returns the host IP address string in textual presentation.
* Note: This can be either an IPv4 or an IPv6 representation. * * @return the host raw IP address in a string format. * @deprecated since 3.2.3 * @see #getHostAddresses() */ @Deprecated public abstract String getHostAddress(); /** * Returns the host IP addresses string in textual presentation. * * @return list of host raw IP address in a string format. */ public abstract String[] getHostAddresses(); /** * Get the host address of the service.
* * @return host Internet address * @deprecated since 3.1.8 * @see #getInetAddresses() */ @Deprecated public abstract InetAddress getAddress(); /** * Get the InetAddress of the service. This will return the IPv4 if it exist, otherwise it return the IPv6 if set.
* Note: This return null if the service IP address cannot be resolved. * * @return Internet address * @deprecated since 3.2.3 * @see #getInetAddresses() */ @Deprecated public abstract InetAddress getInetAddress(); /** * Get the IPv4 InetAddress of the service.
* Note: This return null if the service IPv4 address cannot be resolved. * * @return Internet address * @deprecated since 3.2.3 * @see #getInet4Addresses() */ @Deprecated public abstract Inet4Address getInet4Address(); /** * Get the IPv6 InetAddress of the service.
* Note: This return null if the service IPv6 address cannot be resolved. * * @return Internet address * @deprecated since 3.2.3 * @see #getInet6Addresses() */ @Deprecated public abstract Inet6Address getInet6Address(); /** * Returns a list of all InetAddresses that can be used for this service. *

* In a multi-homed environment service info can be associated with more than one address. *

* * @return list of InetAddress objects */ public abstract InetAddress[] getInetAddresses(); /** * Returns a list of all IPv4 InetAddresses that can be used for this service. *

* In a multi-homed environment service info can be associated with more than one address. *

* * @return list of InetAddress objects */ public abstract Inet4Address[] getInet4Addresses(); /** * Returns a list of all IPv6 InetAddresses that can be used for this service. *

* In a multi-homed environment service info can be associated with more than one address. *

* * @return list of InetAddress objects */ public abstract Inet6Address[] getInet6Addresses(); /** * Get the port for the service. * * @return service port */ public abstract int getPort(); /** * Get the priority of the service. * * @return service priority */ public abstract int getPriority(); /** * Get the weight of the service. * * @return service weight */ public abstract int getWeight(); /** * Get the text for the service as raw bytes. * * @return raw service text */ 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.
* Note: Do not use. This method make the assumption that the TXT record is one string. This is false. The TXT record is a series of key value pairs. * * @return service text * @see #getPropertyNames() * @see #getPropertyBytes(String) * @see #getPropertyString(String) * @deprecated since 3.1.7 */ @Deprecated public abstract String getTextString(); /** * Get the URL for this service. An http URL is created by combining the address, port, and path properties. * * @return service URL * @deprecated since 3.2.3 * @see #getURLs() */ @Deprecated public abstract String getURL(); /** * Get the list of URL for this service. An http URL is created by combining the address, port, and path properties. * * @return list of service URL */ public abstract String[] getURLs(); /** * Get the URL for this service. An URL is created by combining the protocol, address, port, and path properties. * * @param protocol * requested protocol * @return service URL * @deprecated since 3.2.3 * @see #getURLs() */ @Deprecated public abstract String getURL(String protocol); /** * Get the list of URL for this service. An URL is created by combining the protocol, address, port, and path properties. * * @param protocol * requested protocol * @return list of service URL */ public abstract String[] getURLs(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. * * @param name * property name * @return raw property text */ public abstract byte[] getPropertyBytes(final 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. * * @param name * property name * @return property text */ public abstract String getPropertyString(final String name); /** * Enumeration of the property names. * * @return property name enumeration */ public abstract Enumeration getPropertyNames(); /** * Returns a description of the service info suitable for printing. * * @return service info description */ public abstract String getNiceTextString(); /** * Set the text for the service. Setting the text will fore a re-announce of the service. * * @param text * the raw byte representation of the text field. * @exception IllegalStateException * if attempting to set the text for a non persistent service info. */ public abstract void setText(final byte[] text) throws IllegalStateException; /** * Set the text for the service. Setting the text will fore a re-announce of the service. * * @param props * a key=value map that will be encoded into raw bytes. * @exception IllegalStateException * if attempting to set the text for a non persistent service info. */ public abstract void setText(final Map props) throws IllegalStateException; /** * Returns true if ServiceListener.resolveService will be called whenever new new information is received. * * @return the persistent */ public abstract boolean isPersistent(); /** * Returns the domain of the service info suitable for printing. * * @return service domain */ public abstract String getDomain(); /** * Returns the protocol of the service info suitable for printing. * * @return service protocol */ public abstract String getProtocol(); /** * Returns the application of the service info suitable for printing. * * @return service application */ public abstract String getApplication(); /** * Returns the sub type of the service info suitable for printing. * * @return service sub type */ public abstract String getSubtype(); /** * Returns a dictionary of the fully qualified name component of this service. * * @return dictionary of the fully qualified name components */ public abstract Map getQualifiedNameMap(); /* * (non-Javadoc) * @see java.lang.Object#clone() */ @Override public ServiceInfo clone() { try { return (ServiceInfo) super.clone(); } catch (CloneNotSupportedException exception) { // clone is supported return null; } } } jmdns-3.4.1/src/javax/jmdns/impl/0000755000175000017500000000000011625404056016463 5ustar mathieumathieujmdns-3.4.1/src/javax/jmdns/impl/SocketListener.java0000644000175000017500000000556111625404056022273 0ustar mathieumathieu// 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; import javax.jmdns.impl.constants.DNSConstants; /** * Listen for multicast packets. */ class SocketListener extends Thread { static Logger logger = Logger.getLogger(SocketListener.class.getName()); /** * */ private final JmDNSImpl _jmDNSImpl; /** * @param jmDNSImpl */ SocketListener(JmDNSImpl jmDNSImpl) { super("SocketListener(" + (jmDNSImpl != null ? jmDNSImpl.getName() : "") + ")"); this.setDaemon(true); this._jmDNSImpl = jmDNSImpl; } @Override public void run() { try { byte buf[] = new byte[DNSConstants.MAX_MSG_ABSOLUTE]; DatagramPacket packet = new DatagramPacket(buf, buf.length); while (!this._jmDNSImpl.isCanceling() && !this._jmDNSImpl.isCanceled()) { packet.setLength(buf.length); this._jmDNSImpl.getSocket().receive(packet); if (this._jmDNSImpl.isCanceling() || this._jmDNSImpl.isCanceled() || this._jmDNSImpl.isClosing() || this._jmDNSImpl.isClosed()) { break; } try { if (this._jmDNSImpl.getLocalHost().shouldIgnorePacket(packet)) { continue; } DNSIncoming msg = new DNSIncoming(packet); if (logger.isLoggable(Level.FINEST)) { logger.finest(this.getName() + ".run() JmDNS in:" + msg.print(true)); } 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, this.getName() + ".run() exception ", e); } } } catch (IOException e) { if (!this._jmDNSImpl.isCanceling() && !this._jmDNSImpl.isCanceled() && !this._jmDNSImpl.isClosing() && !this._jmDNSImpl.isClosed()) { logger.log(Level.WARNING, this.getName() + ".run() exception ", e); this._jmDNSImpl.recover(); } } if (logger.isLoggable(Level.FINEST)) { logger.finest(this.getName() + ".run() exiting."); } } public JmDNSImpl getDns() { return _jmDNSImpl; } } jmdns-3.4.1/src/javax/jmdns/impl/DNSMessage.java0000644000175000017500000001713711625404056021270 0ustar mathieumathieu/** * */ package javax.jmdns.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import javax.jmdns.impl.constants.DNSConstants; /** * DNSMessage define a DNS message either incoming or outgoing. * * @author Werner Randelshofer, Rick Blair, Pierre Frisch */ public abstract class DNSMessage { /** * */ public static final boolean MULTICAST = true; /** * */ public static final boolean UNICAST = false; // protected DatagramPacket _packet; // protected int _off; // protected int _len; // protected byte[] _data; private int _id; boolean _multicast; private int _flags; protected final List _questions; protected final List _answers; protected final List _authoritativeAnswers; protected final List _additionals; /** * @param flags * @param id * @param multicast */ protected DNSMessage(int flags, int id, boolean multicast) { super(); _flags = flags; _id = id; _multicast = multicast; _questions = Collections.synchronizedList(new LinkedList()); _answers = Collections.synchronizedList(new LinkedList()); _authoritativeAnswers = Collections.synchronizedList(new LinkedList()); _additionals = Collections.synchronizedList(new LinkedList()); } // public DatagramPacket getPacket() { // return _packet; // } // // public int getOffset() { // return _off; // } // // public int getLength() { // return _len; // } // // public byte[] getData() { // if ( _data == null ) _data = new byte[DNSConstants.MAX_MSG_TYPICAL]; // return _data; // } /** * @return message id */ public int getId() { return (_multicast ? 0 : _id); } /** * @param id * the id to set */ public void setId(int id) { this._id = id; } /** * @return message flags */ public int getFlags() { return _flags; } /** * @param flags * the flags to set */ public void setFlags(int flags) { this._flags = flags; } /** * @return true if multicast */ public boolean isMulticast() { return _multicast; } /** * @return list of questions */ public Collection getQuestions() { return _questions; } /** * @return number of questions in the message */ public int getNumberOfQuestions() { return this.getQuestions().size(); } public Collection getAllAnswers() { List aList = new ArrayList(_answers.size() + _authoritativeAnswers.size() + _additionals.size()); aList.addAll(_answers); aList.addAll(_authoritativeAnswers); aList.addAll(_additionals); return aList; } /** * @return list of answers */ public Collection getAnswers() { return _answers; } /** * @return number of answers in the message */ public int getNumberOfAnswers() { return this.getAnswers().size(); } /** * @return list of authorities */ public Collection getAuthorities() { return _authoritativeAnswers; } /** * @return number of authorities in the message */ public int getNumberOfAuthorities() { return this.getAuthorities().size(); } /** * @return list of additional answers */ public Collection getAdditionals() { return _additionals; } /** * @return number of additional in the message */ public int getNumberOfAdditionals() { return this.getAdditionals().size(); } /** * Check if the message is truncated. * * @return true if the message was truncated */ public boolean isTruncated() { return (_flags & DNSConstants.FLAGS_TC) != 0; } /** * Check if the message is a query. * * @return true is the message is a query */ public boolean isQuery() { return (_flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_QUERY; } /** * Check if the message is a response. * * @return true is the message is a response */ public boolean isResponse() { return (_flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_RESPONSE; } /** * Check if the message is empty * * @return true is the message is empty */ public boolean isEmpty() { return (this.getNumberOfQuestions() + this.getNumberOfAnswers() + this.getNumberOfAuthorities() + this.getNumberOfAdditionals()) == 0; } /** * Debugging. */ String print() { StringBuffer buf = new StringBuffer(200); buf.append(this.toString()); buf.append("\n"); for (DNSQuestion question : _questions) { buf.append("\tquestion: "); buf.append(question); buf.append("\n"); } for (DNSRecord answer : _answers) { buf.append("\tanswer: "); buf.append(answer); buf.append("\n"); } for (DNSRecord answer : _authoritativeAnswers) { buf.append("\tauthoritative: "); buf.append(answer); buf.append("\n"); } for (DNSRecord answer : _additionals) { buf.append("\tadditional: "); buf.append(answer); buf.append("\n"); } return buf.toString(); } /** * Debugging. * * @param data * @return data dump */ protected String print(byte[] data) { StringBuilder buf = new StringBuilder(4000); for (int off = 0, len = data.length; off < len; off += 32) { int n = Math.min(32, len - off); if (off < 0x10) { buf.append(' '); } if (off < 0x100) { buf.append(' '); } if (off < 0x1000) { buf.append(' '); } buf.append(Integer.toHexString(off)); buf.append(':'); int index = 0; for (index = 0; index < n; index++) { if ((index % 8) == 0) { buf.append(' '); } buf.append(Integer.toHexString((data[off + index] & 0xF0) >> 4)); buf.append(Integer.toHexString((data[off + index] & 0x0F) >> 0)); } // for incomplete lines if (index < 32) { for (int i = index; i < 32; i++) { if ((i % 8) == 0) { buf.append(' '); } buf.append(" "); } } buf.append(" "); for (index = 0; index < n; index++) { if ((index % 8) == 0) { buf.append(' '); } int ch = data[off + index] & 0xFF; buf.append(((ch > ' ') && (ch < 127)) ? (char) ch : '.'); } buf.append("\n"); // limit message size if (off + 32 >= 2048) { buf.append("....\n"); break; } } return buf.toString(); } } jmdns-3.4.1/src/javax/jmdns/impl/NameRegister.java0000644000175000017500000001155511625404056021722 0ustar mathieumathieu/** * */ package javax.jmdns.impl; import java.net.InetAddress; /** * */ public interface NameRegister { /** * */ public enum NameType { /** * This name represents a host name */ HOST, /** * This name represents a service name */ SERVICE, } public static class UniqueNamePerInterface implements NameRegister { /* * (non-Javadoc) * @see javax.jmdns.impl.NameRegister#register(java.net.InetAddress, java.lang.String, javax.jmdns.impl.NameRegister.NameType) */ @Override public void register(InetAddress networkInterface, String name, NameType type) { // TODO Auto-generated method stub } /* * (non-Javadoc) * @see javax.jmdns.impl.NameRegister#checkName(java.net.InetAddress, java.lang.String, javax.jmdns.impl.NameRegister.NameType) */ @Override public boolean checkName(InetAddress networkInterface, String name, NameType type) { // TODO Auto-generated method stub return false; } /* * (non-Javadoc) * @see javax.jmdns.impl.NameRegister#incrementHostName(java.net.InetAddress, java.lang.String, javax.jmdns.impl.NameRegister.NameType) */ @Override public String incrementHostName(InetAddress networkInterface, String name, NameType type) { // TODO Auto-generated method stub return null; } } public static class UniqueNameAcrossInterface implements NameRegister { /* * (non-Javadoc) * @see javax.jmdns.impl.NameRegister#register(java.net.InetAddress, java.lang.String, javax.jmdns.impl.NameRegister.NameType) */ @Override public void register(InetAddress networkInterface, String name, NameType type) { // TODO Auto-generated method stub } /* * (non-Javadoc) * @see javax.jmdns.impl.NameRegister#checkName(java.net.InetAddress, java.lang.String, javax.jmdns.impl.NameRegister.NameType) */ @Override public boolean checkName(InetAddress networkInterface, String name, NameType type) { // TODO Auto-generated method stub return false; } /* * (non-Javadoc) * @see javax.jmdns.impl.NameRegister#incrementHostName(java.net.InetAddress, java.lang.String, javax.jmdns.impl.NameRegister.NameType) */ @Override public String incrementHostName(InetAddress networkInterface, String name, NameType type) { // TODO Auto-generated method stub return null; } } public static class Factory { private static volatile NameRegister _register; /** * Register a Name register. * * @param register * new register * @throws IllegalStateException * the register can only be set once */ public static void setRegistry(NameRegister register) throws IllegalStateException { if (_register != null) { throw new IllegalStateException("The register can only be set once."); } if (register != null) { _register = register; } } /** * Returns the name register. * * @return name register */ public static NameRegister getRegistry() { if (_register == null) { _register = new UniqueNamePerInterface(); } return _register; } } /** * Registers a name that is defended by this group of mDNS. * * @param networkInterface * IP address to handle * @param name * name to register * @param type * name type to register */ public abstract void register(InetAddress networkInterface, String name, NameType type); /** * Checks a name that is defended by this group of mDNS. * * @param networkInterface * IP address to handle * @param name * name to check * @param type * name type to check * @return true if the name is not in conflict, flase otherwise. */ public abstract boolean checkName(InetAddress networkInterface, String name, NameType type); /** * Increments a name that is defended by this group of mDNS after it has been found in conflict. * * @param networkInterface * IP address to handle * @param name * name to increment * @param type * name type to increments * @return new name */ public abstract String incrementHostName(InetAddress networkInterface, String name, NameType type); } jmdns-3.4.1/src/javax/jmdns/impl/DNSIncoming.java0000644000175000017500000006070211625404056021443 0ustar mathieumathieu// /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.ByteArrayInputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSLabel; import javax.jmdns.impl.constants.DNSOptionCode; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; import javax.jmdns.impl.constants.DNSResultCode; /** * Parse an incoming DNS message into its components. * * @author Arthur van Hoff, Werner Randelshofer, Pierre Frisch, Daniel Bobbert */ public final class DNSIncoming extends DNSMessage { 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; public static class MessageInputStream extends ByteArrayInputStream { private static Logger logger1 = Logger.getLogger(MessageInputStream.class.getName()); final Map _names; public MessageInputStream(byte[] buffer, int length) { this(buffer, 0, length); } /** * @param buffer * @param offset * @param length */ public MessageInputStream(byte[] buffer, int offset, int length) { super(buffer, offset, length); _names = new HashMap(); } public int readByte() { return this.read(); } public int readUnsignedShort() { return (this.read() << 8) | this.read(); } public int readInt() { return (this.readUnsignedShort() << 16) | this.readUnsignedShort(); } public byte[] readBytes(int len) { byte bytes[] = new byte[len]; this.read(bytes, 0, len); return bytes; } public String readUTF(int len) { StringBuilder buffer = new StringBuilder(len); for (int index = 0; index < len; index++) { int ch = this.read(); 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) | (this.read() & 0x3F); index++; break; case 14: // 1110 xxxx 10xx xxxx 10xx xxxx ch = ((ch & 0x0f) << 12) | ((this.read() & 0x3F) << 6) | (this.read() & 0x3F); index++; index++; break; default: // 10xx xxxx, 1111 xxxx ch = ((ch & 0x3F) << 4) | (this.read() & 0x0f); index++; break; } buffer.append((char) ch); } return buffer.toString(); } protected synchronized int peek() { return (pos < count) ? (buf[pos] & 0xff) : -1; } public String readName() { Map names = new HashMap(); StringBuilder buffer = new StringBuilder(); boolean finished = false; while (!finished) { int len = this.read(); if (len == 0) { finished = true; break; } switch (DNSLabel.labelForByte(len)) { case Standard: int offset = pos - 1; String label = this.readUTF(len) + "."; buffer.append(label); for (StringBuilder previousLabel : names.values()) { previousLabel.append(label); } names.put(Integer.valueOf(offset), new StringBuilder(label)); break; case Compressed: int index = (DNSLabel.labelValue(len) << 8) | this.read(); String compressedLabel = _names.get(Integer.valueOf(index)); if (compressedLabel == null) { logger1.severe("bad domain name: possible circular name detected. Bad offset: 0x" + Integer.toHexString(index) + " at 0x" + Integer.toHexString(pos - 2)); compressedLabel = ""; } buffer.append(compressedLabel); for (StringBuilder previousLabel : names.values()) { previousLabel.append(compressedLabel); } finished = true; break; case Extended: // int extendedLabelClass = DNSLabel.labelValue(len); logger1.severe("Extended label are not currently supported."); break; case Unknown: default: logger1.severe("unsupported dns label type: '" + Integer.toHexString(len & 0xC0) + "'"); } } for (Integer index : names.keySet()) { _names.put(index, names.get(index).toString()); } return buffer.toString(); } public String readNonNameString() { int len = this.read(); return this.readUTF(len); } } private final DatagramPacket _packet; private final long _receivedTime; private final MessageInputStream _messageInputStream; private int _senderUDPPayload; /** * Parse a message from a datagram packet. * * @param packet * @exception IOException */ public DNSIncoming(DatagramPacket packet) throws IOException { super(0, 0, packet.getPort() == DNSConstants.MDNS_PORT); this._packet = packet; InetAddress source = packet.getAddress(); this._messageInputStream = new MessageInputStream(packet.getData(), packet.getLength()); this._receivedTime = System.currentTimeMillis(); this._senderUDPPayload = DNSConstants.MAX_MSG_TYPICAL; try { this.setId(_messageInputStream.readUnsignedShort()); this.setFlags(_messageInputStream.readUnsignedShort()); int numQuestions = _messageInputStream.readUnsignedShort(); int numAnswers = _messageInputStream.readUnsignedShort(); int numAuthorities = _messageInputStream.readUnsignedShort(); int numAdditionals = _messageInputStream.readUnsignedShort(); // parse questions if (numQuestions > 0) { for (int i = 0; i < numQuestions; i++) { _questions.add(this.readQuestion()); } } // parse answers if (numAnswers > 0) { for (int i = 0; i < numAnswers; i++) { DNSRecord rec = this.readAnswer(source); if (rec != null) { // Add a record, if we were able to create one. _answers.add(rec); } } } if (numAuthorities > 0) { for (int i = 0; i < numAuthorities; i++) { DNSRecord rec = this.readAnswer(source); if (rec != null) { // Add a record, if we were able to create one. _authoritativeAnswers.add(rec); } } } if (numAdditionals > 0) { for (int i = 0; i < numAdditionals; i++) { DNSRecord rec = this.readAnswer(source); if (rec != null) { // Add a record, if we were able to create one. _additionals.add(rec); } } } } catch (Exception e) { logger.log(Level.WARNING, "DNSIncoming() dump " + print(true) + "\n exception ", e); // This ugly but some JVM don't implement the cause on IOException IOException ioe = new IOException("DNSIncoming corrupted message"); ioe.initCause(e); throw ioe; } } private DNSIncoming(int flags, int id, boolean multicast, DatagramPacket packet, long receivedTime) { super(flags, id, multicast); this._packet = packet; this._messageInputStream = new MessageInputStream(packet.getData(), packet.getLength()); this._receivedTime = receivedTime; } /* * (non-Javadoc) * * @see java.lang.Object#clone() */ @Override public DNSIncoming clone() { DNSIncoming in = new DNSIncoming(this.getFlags(), this.getId(), this.isMulticast(), this._packet, this._receivedTime); in._senderUDPPayload = this._senderUDPPayload; in._questions.addAll(this._questions); in._answers.addAll(this._answers); in._authoritativeAnswers.addAll(this._authoritativeAnswers); in._additionals.addAll(this._additionals); return in; } private DNSQuestion readQuestion() { String domain = _messageInputStream.readName(); DNSRecordType type = DNSRecordType.typeForIndex(_messageInputStream.readUnsignedShort()); if (type == DNSRecordType.TYPE_IGNORE) { logger.log(Level.SEVERE, "Could not find record type: " + this.print(true)); } int recordClassIndex = _messageInputStream.readUnsignedShort(); DNSRecordClass recordClass = DNSRecordClass.classForIndex(recordClassIndex); boolean unique = recordClass.isUnique(recordClassIndex); return DNSQuestion.newQuestion(domain, type, recordClass, unique); } private DNSRecord readAnswer(InetAddress source) { String domain = _messageInputStream.readName(); DNSRecordType type = DNSRecordType.typeForIndex(_messageInputStream.readUnsignedShort()); if (type == DNSRecordType.TYPE_IGNORE) { logger.log(Level.SEVERE, "Could not find record type. domain: " + domain + "\n" + this.print(true)); } int recordClassIndex = _messageInputStream.readUnsignedShort(); DNSRecordClass recordClass = (type == DNSRecordType.TYPE_OPT ? DNSRecordClass.CLASS_UNKNOWN : DNSRecordClass.classForIndex(recordClassIndex)); if ((recordClass == DNSRecordClass.CLASS_UNKNOWN) && (type != DNSRecordType.TYPE_OPT)) { logger.log(Level.SEVERE, "Could not find record class. domain: " + domain + " type: " + type + "\n" + this.print(true)); } boolean unique = recordClass.isUnique(recordClassIndex); int ttl = _messageInputStream.readInt(); int len = _messageInputStream.readUnsignedShort(); DNSRecord rec = null; switch (type) { case TYPE_A: // IPv4 rec = new DNSRecord.IPv4Address(domain, recordClass, unique, ttl, _messageInputStream.readBytes(len)); break; case TYPE_AAAA: // IPv6 rec = new DNSRecord.IPv6Address(domain, recordClass, unique, ttl, _messageInputStream.readBytes(len)); break; case TYPE_CNAME: case TYPE_PTR: String service = ""; service = _messageInputStream.readName(); if (service.length() > 0) { rec = new DNSRecord.Pointer(domain, recordClass, unique, ttl, service); } else { logger.log(Level.WARNING, "PTR record of class: " + recordClass + ", there was a problem reading the service name of the answer for domain:" + domain); } break; case TYPE_TXT: rec = new DNSRecord.Text(domain, recordClass, unique, ttl, _messageInputStream.readBytes(len)); break; case TYPE_SRV: int priority = _messageInputStream.readUnsignedShort(); int weight = _messageInputStream.readUnsignedShort(); int port = _messageInputStream.readUnsignedShort(); String target = ""; // 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 = _messageInputStream.readName(); } else { // [PJYF Nov 13 2010] Do we still need this? This looks really bad. All label are supposed to start by a length. target = _messageInputStream.readNonNameString(); } rec = new DNSRecord.Service(domain, recordClass, unique, ttl, priority, weight, port, target); break; case TYPE_HINFO: StringBuilder buf = new StringBuilder(); buf.append(_messageInputStream.readUTF(len)); int index = buf.indexOf(" "); String cpu = (index > 0 ? buf.substring(0, index) : buf.toString()).trim(); String os = (index > 0 ? buf.substring(index + 1) : "").trim(); rec = new DNSRecord.HostInformation(domain, recordClass, unique, ttl, cpu, os); break; case TYPE_OPT: DNSResultCode extendedResultCode = DNSResultCode.resultCodeForFlags(this.getFlags(), ttl); int version = (ttl & 0x00ff0000) >> 16; if (version == 0) { _senderUDPPayload = recordClassIndex; while (_messageInputStream.available() > 0) { // Read RDData int optionCodeInt = 0; DNSOptionCode optionCode = null; if (_messageInputStream.available() >= 2) { optionCodeInt = _messageInputStream.readUnsignedShort(); optionCode = DNSOptionCode.resultCodeForFlags(optionCodeInt); } else { logger.log(Level.WARNING, "There was a problem reading the OPT record. Ignoring."); break; } int optionLength = 0; if (_messageInputStream.available() >= 2) { optionLength = _messageInputStream.readUnsignedShort(); } else { logger.log(Level.WARNING, "There was a problem reading the OPT record. Ignoring."); break; } byte[] optiondata = new byte[0]; if (_messageInputStream.available() >= optionLength) { optiondata = _messageInputStream.readBytes(optionLength); } // // We should really do something with those options. switch (optionCode) { case Owner: // Valid length values are 8, 14, 18 and 20 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // |Opt|Len|V|S|Primary MAC|Wakeup MAC | Password | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // int ownerVersion = 0; int ownerSequence = 0; byte[] ownerPrimaryMacAddress = null; byte[] ownerWakeupMacAddress = null; byte[] ownerPassword = null; try { ownerVersion = optiondata[0]; ownerSequence = optiondata[1]; ownerPrimaryMacAddress = new byte[] { optiondata[2], optiondata[3], optiondata[4], optiondata[5], optiondata[6], optiondata[7] }; ownerWakeupMacAddress = ownerPrimaryMacAddress; if (optiondata.length > 8) { // We have a wakeupMacAddress. ownerWakeupMacAddress = new byte[] { optiondata[8], optiondata[9], optiondata[10], optiondata[11], optiondata[12], optiondata[13] }; } if (optiondata.length == 18) { // We have a short password. ownerPassword = new byte[] { optiondata[14], optiondata[15], optiondata[16], optiondata[17] }; } if (optiondata.length == 22) { // We have a long password. ownerPassword = new byte[] { optiondata[14], optiondata[15], optiondata[16], optiondata[17], optiondata[18], optiondata[19], optiondata[20], optiondata[21] }; } } catch (Exception exception) { logger.warning("Malformed OPT answer. Option code: Owner data: " + this._hexString(optiondata)); } if (logger.isLoggable(Level.FINE)) { logger.fine("Unhandled Owner OPT version: " + ownerVersion + " sequence: " + ownerSequence + " MAC address: " + this._hexString(ownerPrimaryMacAddress) + (ownerWakeupMacAddress != ownerPrimaryMacAddress ? " wakeup MAC address: " + this._hexString(ownerWakeupMacAddress) : "") + (ownerPassword != null ? " password: " + this._hexString(ownerPassword) : "")); } break; case LLQ: case NSID: case UL: if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "There was an OPT answer. Option code: " + optionCode + " data: " + this._hexString(optiondata)); } break; case Unknown: logger.log(Level.WARNING, "There was an OPT answer. Not currently handled. Option code: " + optionCodeInt + " data: " + this._hexString(optiondata)); break; default: // This is to keep the compiler happy. break; } } } else { logger.log(Level.WARNING, "There was an OPT answer. Wrong version number: " + version + " result code: " + extendedResultCode); } break; default: if (logger.isLoggable(Level.FINER)) { logger.finer("DNSIncoming() unknown type:" + type); } _messageInputStream.skip(len); break; } if (rec != null) { rec.setRecordSource(source); } return rec; } /** * Debugging. */ String print(boolean dump) { StringBuilder buf = new StringBuilder(); buf.append(this.print()); if (dump) { byte[] data = new byte[_packet.getLength()]; System.arraycopy(_packet.getData(), 0, data, 0, data.length); buf.append(this.print(data)); } return buf.toString(); } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(isQuery() ? "dns[query," : "dns[response,"); if (_packet.getAddress() != null) { buf.append(_packet.getAddress().getHostAddress()); } buf.append(':'); buf.append(_packet.getPort()); buf.append(", length="); buf.append(_packet.getLength()); buf.append(", id=0x"); buf.append(Integer.toHexString(this.getId())); if (this.getFlags() != 0) { buf.append(", flags=0x"); buf.append(Integer.toHexString(this.getFlags())); if ((this.getFlags() & DNSConstants.FLAGS_QR_RESPONSE) != 0) { buf.append(":r"); } if ((this.getFlags() & DNSConstants.FLAGS_AA) != 0) { buf.append(":aa"); } if ((this.getFlags() & DNSConstants.FLAGS_TC) != 0) { buf.append(":tc"); } } if (this.getNumberOfQuestions() > 0) { buf.append(", questions="); buf.append(this.getNumberOfQuestions()); } if (this.getNumberOfAnswers() > 0) { buf.append(", answers="); buf.append(this.getNumberOfAnswers()); } if (this.getNumberOfAuthorities() > 0) { buf.append(", authorities="); buf.append(this.getNumberOfAuthorities()); } if (this.getNumberOfAdditionals() > 0) { buf.append(", additionals="); buf.append(this.getNumberOfAdditionals()); } if (this.getNumberOfQuestions() > 0) { buf.append("\nquestions:"); for (DNSQuestion question : _questions) { buf.append("\n\t"); buf.append(question); } } if (this.getNumberOfAnswers() > 0) { buf.append("\nanswers:"); for (DNSRecord record : _answers) { buf.append("\n\t"); buf.append(record); } } if (this.getNumberOfAuthorities() > 0) { buf.append("\nauthorities:"); for (DNSRecord record : _authoritativeAnswers) { buf.append("\n\t"); buf.append(record); } } if (this.getNumberOfAdditionals() > 0) { buf.append("\nadditionals:"); for (DNSRecord record : _additionals) { buf.append("\n\t"); buf.append(record); } } buf.append("]"); return buf.toString(); } /** * Appends answers to this Incoming. * * @exception IllegalArgumentException * If not a query or if Truncated. */ void append(DNSIncoming that) { if (this.isQuery() && this.isTruncated() && that.isQuery()) { this._questions.addAll(that.getQuestions()); this._answers.addAll(that.getAnswers()); this._authoritativeAnswers.addAll(that.getAuthorities()); this._additionals.addAll(that.getAdditionals()); } else { throw new IllegalArgumentException(); } } public int elapseSinceArrival() { return (int) (System.currentTimeMillis() - _receivedTime); } /** * This will return the default UDP payload except if an OPT record was found with a different size. * * @return the senderUDPPayload */ public int getSenderUDPPayload() { return this._senderUDPPayload; } private static final char[] _nibbleToHex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; /** * Returns a hex-string for printing * * @param bytes * @return Returns a hex-string which can be used within a SQL expression */ private String _hexString(byte[] bytes) { StringBuilder result = new StringBuilder(2 * bytes.length); for (int i = 0; i < bytes.length; i++) { int b = bytes[i] & 0xFF; result.append(_nibbleToHex[b / 16]); result.append(_nibbleToHex[b % 16]); } return result.toString(); } } jmdns-3.4.1/src/javax/jmdns/impl/JmmDNSImpl.java0000644000175000017500000004711511625404056021250 0ustar mathieumathieu/** * */ package javax.jmdns.impl; import java.io.IOException; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.JmDNS; import javax.jmdns.JmmDNS; import javax.jmdns.NetworkTopologyDiscovery; import javax.jmdns.NetworkTopologyEvent; import javax.jmdns.NetworkTopologyListener; import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceListener; import javax.jmdns.ServiceTypeListener; import javax.jmdns.impl.constants.DNSConstants; /** * This class enable multihomming mDNS. It will open a mDNS per IP address of the machine. * * @author Cédrik Lime, Pierre Frisch */ public class JmmDNSImpl implements JmmDNS, NetworkTopologyListener, ServiceInfoImpl.Delegate { private static Logger logger = Logger.getLogger(JmmDNSImpl.class.getName()); private final Set _networkListeners; /** * Every JmDNS created. */ private final ConcurrentMap _knownMDNS; /** * This enable the service info text update. */ private final ConcurrentMap _services; private final ExecutorService _ListenerExecutor; private final ExecutorService _jmDNSExecutor; private final Timer _timer; /** * */ public JmmDNSImpl() { super(); _networkListeners = Collections.synchronizedSet(new HashSet()); _knownMDNS = new ConcurrentHashMap(); _services = new ConcurrentHashMap(20); _ListenerExecutor = Executors.newSingleThreadExecutor(); _jmDNSExecutor = Executors.newCachedThreadPool(); _timer = new Timer("Multihommed mDNS.Timer", true); (new NetworkChecker(this, NetworkTopologyDiscovery.Factory.getInstance())).start(_timer); } /* * (non-Javadoc) * @see java.io.Closeable#close() */ @Override public void close() throws IOException { if (logger.isLoggable(Level.FINER)) { logger.finer("Cancelling JmmDNS: " + this); } _timer.cancel(); _ListenerExecutor.shutdown(); // We need to cancel all the DNS ExecutorService executor = Executors.newCachedThreadPool(); for (final JmDNS mDNS : _knownMDNS.values()) { executor.submit(new Runnable() { /** * {@inheritDoc} */ @Override public void run() { try { mDNS.close(); } catch (IOException exception) { // JmDNS never throws this is only because of the closeable interface } } }); } executor.shutdown(); try { executor.awaitTermination(DNSConstants.CLOSE_TIMEOUT, TimeUnit.MILLISECONDS); } catch (InterruptedException exception) { logger.log(Level.WARNING, "Exception ", exception); } _knownMDNS.clear(); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#getNames() */ @Override public String[] getNames() { Set result = new HashSet(); for (JmDNS mDNS : _knownMDNS.values()) { result.add(mDNS.getName()); } return result.toArray(new String[result.size()]); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#getHostNames() */ @Override public String[] getHostNames() { Set result = new HashSet(); for (JmDNS mDNS : _knownMDNS.values()) { result.add(mDNS.getHostName()); } return result.toArray(new String[result.size()]); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#getInterfaces() */ @Override public InetAddress[] getInterfaces() throws IOException { Set result = new HashSet(); for (JmDNS mDNS : _knownMDNS.values()) { result.add(mDNS.getInterface()); } return result.toArray(new InetAddress[result.size()]); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String) */ @Override public ServiceInfo[] getServiceInfos(String type, String name) { return this.getServiceInfos(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String, long) */ @Override public ServiceInfo[] getServiceInfos(String type, String name, long timeout) { return this.getServiceInfos(type, name, false, timeout); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String, boolean) */ @Override public ServiceInfo[] getServiceInfos(String type, String name, boolean persistent) { return this.getServiceInfos(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String, boolean, long) */ @Override public ServiceInfo[] getServiceInfos(final String type, final String name, final boolean persistent, final long timeout) { // We need to run this in parallel to respect the timeout. final Set result = Collections.synchronizedSet(new HashSet(_knownMDNS.size())); ExecutorService executor = Executors.newCachedThreadPool(); for (final JmDNS mDNS : _knownMDNS.values()) { executor.submit(new Runnable() { /** * {@inheritDoc} */ @Override public void run() { result.add(mDNS.getServiceInfo(type, name, persistent, timeout)); } }); } executor.shutdown(); try { executor.awaitTermination(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException exception) { logger.log(Level.WARNING, "Exception ", exception); } return result.toArray(new ServiceInfo[result.size()]); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String) */ @Override public void requestServiceInfo(String type, String name) { this.requestServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String, boolean) */ @Override public void requestServiceInfo(String type, String name, boolean persistent) { this.requestServiceInfo(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String, long) */ @Override public void requestServiceInfo(String type, String name, long timeout) { this.requestServiceInfo(type, name, false, timeout); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String, boolean, long) */ @Override public void requestServiceInfo(final String type, final String name, final boolean persistent, final long timeout) { // We need to run this in parallel to respect the timeout. for (final JmDNS mDNS : _knownMDNS.values()) { _jmDNSExecutor.submit(new Runnable() { /** * {@inheritDoc} */ @Override public void run() { mDNS.requestServiceInfo(type, name, persistent, timeout); } }); } } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#addServiceTypeListener(javax.jmdns.ServiceTypeListener) */ @Override public void addServiceTypeListener(ServiceTypeListener listener) throws IOException { for (JmDNS mDNS : _knownMDNS.values()) { mDNS.addServiceTypeListener(listener); } } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#removeServiceTypeListener(javax.jmdns.ServiceTypeListener) */ @Override public void removeServiceTypeListener(ServiceTypeListener listener) { for (JmDNS mDNS : _knownMDNS.values()) { mDNS.removeServiceTypeListener(listener); } } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#addServiceListener(java.lang.String, javax.jmdns.ServiceListener) */ @Override public void addServiceListener(String type, ServiceListener listener) { for (JmDNS mDNS : _knownMDNS.values()) { mDNS.addServiceListener(type, listener); } } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#removeServiceListener(java.lang.String, javax.jmdns.ServiceListener) */ @Override public void removeServiceListener(String type, ServiceListener listener) { for (JmDNS mDNS : _knownMDNS.values()) { mDNS.removeServiceListener(type, listener); } } /* * (non-Javadoc) * @see javax.jmdns.impl.ServiceInfoImpl.Delegate#textValueUpdated(javax.jmdns.ServiceInfo, byte[]) */ @Override public void textValueUpdated(ServiceInfo target, byte[] value) { synchronized (_services) { for (JmDNS mDNS : _knownMDNS.values()) { ServiceInfo info = ((JmDNSImpl) mDNS).getServices().get(target.getQualifiedName()); if (info != null) { info.setText(value); } else { logger.warning("We have a mDNS that does not know about the service info being updated."); } } } } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#registerService(javax.jmdns.ServiceInfo) */ @Override public void registerService(ServiceInfo info) throws IOException { // This is really complex. We need to clone the service info for each DNS but then we loose the ability to update it. synchronized (_services) { for (JmDNS mDNS : _knownMDNS.values()) { mDNS.registerService(info.clone()); } ((ServiceInfoImpl) info).setDelegate(this); _services.put(info.getQualifiedName(), info); } } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#unregisterService(javax.jmdns.ServiceInfo) */ @Override public void unregisterService(ServiceInfo info) { synchronized (_services) { for (JmDNS mDNS : _knownMDNS.values()) { mDNS.unregisterService(info); } ((ServiceInfoImpl) info).setDelegate(null); _services.remove(info.getQualifiedName()); } } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#unregisterAllServices() */ @Override public void unregisterAllServices() { synchronized (_services) { for (JmDNS mDNS : _knownMDNS.values()) { mDNS.unregisterAllServices(); } _services.clear(); } } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#registerServiceType(java.lang.String) */ @Override public void registerServiceType(String type) { for (JmDNS mDNS : _knownMDNS.values()) { mDNS.registerServiceType(type); } } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#list(java.lang.String) */ @Override public ServiceInfo[] list(String type) { return this.list(type, DNSConstants.SERVICE_INFO_TIMEOUT); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#list(java.lang.String, long) */ @Override public ServiceInfo[] list(final String type, final long timeout) { // We need to run this in parallel to respect the timeout. final Set result = Collections.synchronizedSet(new HashSet(_knownMDNS.size() * 5)); ExecutorService executor = Executors.newCachedThreadPool(); for (final JmDNS mDNS : _knownMDNS.values()) { executor.submit(new Runnable() { /** * {@inheritDoc} */ @Override public void run() { result.addAll(Arrays.asList(mDNS.list(type, timeout))); } }); } executor.shutdown(); try { executor.awaitTermination(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException exception) { logger.log(Level.WARNING, "Exception ", exception); } return result.toArray(new ServiceInfo[result.size()]); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#listBySubtype(java.lang.String) */ @Override public Map listBySubtype(String type) { return this.listBySubtype(type, DNSConstants.SERVICE_INFO_TIMEOUT); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#listBySubtype(java.lang.String, long) */ @Override public Map listBySubtype(final String type, final long timeout) { Map> map = new HashMap>(5); for (ServiceInfo info : this.list(type, timeout)) { String subtype = info.getSubtype(); if (!map.containsKey(subtype)) { map.put(subtype, new ArrayList(10)); } map.get(subtype).add(info); } Map result = new HashMap(map.size()); for (String subtype : map.keySet()) { List infoForSubType = map.get(subtype); result.put(subtype, infoForSubType.toArray(new ServiceInfo[infoForSubType.size()])); } return result; } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#addNetworkTopologyListener(javax.jmdns.NetworkTopologyListener) */ @Override public void addNetworkTopologyListener(NetworkTopologyListener listener) { _networkListeners.add(listener); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#removeNetworkTopologyListener(javax.jmdns.NetworkTopologyListener) */ @Override public void removeNetworkTopologyListener(NetworkTopologyListener listener) { _networkListeners.remove(listener); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS#networkListeners() */ @Override public NetworkTopologyListener[] networkListeners() { return _networkListeners.toArray(new NetworkTopologyListener[_networkListeners.size()]); } /* * (non-Javadoc) * @see javax.jmdns.NetworkTopologyListener#inetAddressAdded(javax.jmdns.NetworkTopologyEvent) */ @Override public void inetAddressAdded(NetworkTopologyEvent event) { InetAddress address = event.getInetAddress(); try { synchronized (this) { if (!_knownMDNS.containsKey(address)) { _knownMDNS.put(address, JmDNS.create(address)); final NetworkTopologyEvent jmdnsEvent = new NetworkTopologyEventImpl(_knownMDNS.get(address), address); for (final NetworkTopologyListener listener : this.networkListeners()) { _ListenerExecutor.submit(new Runnable() { /** * {@inheritDoc} */ @Override public void run() { listener.inetAddressAdded(jmdnsEvent); } }); } } } } catch (Exception e) { logger.warning("Unexpected unhandled exception: " + e); } } /* * (non-Javadoc) * @see javax.jmdns.NetworkTopologyListener#inetAddressRemoved(javax.jmdns.NetworkTopologyEvent) */ @Override public void inetAddressRemoved(NetworkTopologyEvent event) { InetAddress address = event.getInetAddress(); try { synchronized (this) { if (_knownMDNS.containsKey(address)) { JmDNS mDNS = _knownMDNS.remove(address); mDNS.close(); final NetworkTopologyEvent jmdnsEvent = new NetworkTopologyEventImpl(mDNS, address); for (final NetworkTopologyListener listener : this.networkListeners()) { _ListenerExecutor.submit(new Runnable() { /** * {@inheritDoc} */ @Override public void run() { listener.inetAddressRemoved(jmdnsEvent); } }); } } } } catch (Exception e) { logger.warning("Unexpected unhandled exception: " + e); } } /** * Checks the network state.
* If the network change, this class will reconfigure the list of DNS do adapt to the new configuration. */ static class NetworkChecker extends TimerTask { private static Logger logger1 = Logger.getLogger(NetworkChecker.class.getName()); private final NetworkTopologyListener _mmDNS; private final NetworkTopologyDiscovery _topology; private Set _knownAddresses; public NetworkChecker(NetworkTopologyListener mmDNS, NetworkTopologyDiscovery topology) { super(); this._mmDNS = mmDNS; this._topology = topology; _knownAddresses = Collections.synchronizedSet(new HashSet()); } public void start(Timer timer) { timer.schedule(this, 0, DNSConstants.NETWORK_CHECK_INTERVAL); } /** * {@inheritDoc} */ @Override public void run() { try { InetAddress[] curentAddresses = _topology.getInetAddresses(); Set current = new HashSet(curentAddresses.length); for (InetAddress address : curentAddresses) { current.add(address); if (!_knownAddresses.contains(address)) { final NetworkTopologyEvent event = new NetworkTopologyEventImpl(_mmDNS, address); _mmDNS.inetAddressAdded(event); } } for (InetAddress address : _knownAddresses) { if (!current.contains(address)) { final NetworkTopologyEvent event = new NetworkTopologyEventImpl(_mmDNS, address); _mmDNS.inetAddressRemoved(event); } } _knownAddresses = current; } catch (Exception e) { logger1.warning("Unexpected unhandled exception: " + e); } } } } jmdns-3.4.1/src/javax/jmdns/impl/DNSRecord.java0000644000175000017500000010660511625404056021121 0ustar mathieumathieu// 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.DataOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceInfo.Fields; import javax.jmdns.impl.DNSOutgoing.MessageOutputStream; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; /** * DNS record * * @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, class, and ttl. */ DNSRecord(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl) { super(name, type, recordClass, unique); this._ttl = ttl; this._created = System.currentTimeMillis(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSEntry#equals(java.lang.Object) */ @Override public boolean equals(Object other) { return (other instanceof DNSRecord) && 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 this.getRecordType() == other.getRecordType(); } /** * 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 response 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 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 (DNSRecord answer : msg.getAllAnswers()) { if (suppressedBy(answer)) { 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 suppressed by an answer. This is the case if this record would not have a significantly longer TTL. */ boolean suppressedBy(DNSRecord other) { if (this.equals(other) && (other._ttl > _ttl / 2)) { return true; } return false; } /** * Get the expiration time of this record. */ long getExpirationTime(int percent) { // ttl is in seconds the constant 10 is 1000 ms / 100 % 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); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSEntry#isExpired(long) */ @Override public boolean isExpired(long now) { return getExpirationTime(100) <= now; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSEntry#isStale(long) */ @Override public 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; } /** * When a record flushed we don't remove it immediately, but mark it for rapid decay. */ void setWillExpireSoon(long now) { _created = now; _ttl = DNSConstants.RECORD_EXPIRY_DELAY; } /** * Write this record into an outgoing message. */ abstract void write(MessageOutputStream out); public static class IPv4Address extends Address { IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) { super(name, DNSRecordType.TYPE_A, recordClass, unique, ttl, addr); } IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress) { super(name, DNSRecordType.TYPE_A, recordClass, unique, ttl, rawAddress); } @Override void write(MessageOutputStream out) { if (_addr != null) { byte[] buffer = _addr.getAddress(); // 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); } int length = buffer.length; out.writeBytes(buffer, 0, length); } } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) */ @Override public ServiceInfo getServiceInfo(boolean persistent) { ServiceInfoImpl info = (ServiceInfoImpl) super.getServiceInfo(persistent); info.addAddress((Inet4Address) _addr); return info; } } public static class IPv6Address extends Address { IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) { super(name, DNSRecordType.TYPE_AAAA, recordClass, unique, ttl, addr); } IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress) { super(name, DNSRecordType.TYPE_AAAA, recordClass, unique, ttl, rawAddress); } @Override void write(MessageOutputStream out) { if (_addr != null) { byte[] buffer = _addr.getAddress(); // 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); } } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) */ @Override public ServiceInfo getServiceInfo(boolean persistent) { ServiceInfoImpl info = (ServiceInfoImpl) super.getServiceInfo(persistent); info.addAddress((Inet6Address) _addr); return info; } } /** * Address record. */ public static abstract class Address extends DNSRecord { private static Logger logger1 = Logger.getLogger(Address.class.getName()); InetAddress _addr; protected Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) { super(name, type, recordClass, unique, ttl); this._addr = addr; } protected Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress) { super(name, type, recordClass, unique, ttl); try { this._addr = InetAddress.getByAddress(rawAddress); } catch (UnknownHostException exception) { logger1.log(Level.WARNING, "Address() exception ", exception); } } boolean same(DNSRecord other) { if (! (other instanceof Address) ) { return false; } return ((sameName(other)) && ((sameValue(other)))); } boolean sameName(DNSRecord other) { return this.getName().equalsIgnoreCase(other.getName()); } @Override boolean sameValue(DNSRecord other) { if (! (other instanceof Address) ) { return false; } Address address = (Address) other; if ((this.getAddress() == null) && (address.getAddress() != null)) { return false; } return this.getAddress().equals(address.getAddress()); } @Override public boolean isSingleValued() { return false; } 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. */ @Override protected void toByteArray(DataOutputStream dout) throws IOException { super.toByteArray(dout); byte[] buffer = this.getAddress().getAddress(); for (int i = 0; i < buffer.length; i++) { dout.writeByte(buffer[i]); } } /** * Does the necessary actions, when this as a query. */ @Override boolean handleQuery(JmDNSImpl dns, long expirationTime) { if (dns.getLocalHost().conflictWithRecord(this)) { DNSRecord.Address localAddress = dns.getLocalHost().getDNSAddressRecord(this.getRecordType(), this.isUnique(), DNSConstants.DNS_TTL); int comparison = this.compareTo(localAddress); if (comparison == 0) { // the 2 records are identical this probably means we are seeing our own record. // With multiple 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. logger1.finer("handleQuery() Ignoring an identical address query"); return false; } logger1.finer("handleQuery() Conflicting query detected."); // Tie breaker test if (dns.isProbing() && comparison > 0) { // We lost the tie-break. We have to choose a different name. dns.getLocalHost().incrementHostName(); dns.getCache().clear(); for (ServiceInfo serviceInfo : dns.getServices().values()) { ServiceInfoImpl info = (ServiceInfoImpl) serviceInfo; info.revertState(); } } dns.revertState(); return true; } return false; } /** * Does the necessary actions, when this as a response. */ @Override boolean handleResponse(JmDNSImpl dns) { if (dns.getLocalHost().conflictWithRecord(this)) { logger1.finer("handleResponse() Denial detected"); if (dns.isProbing()) { dns.getLocalHost().incrementHostName(); dns.getCache().clear(); for (ServiceInfo serviceInfo : dns.getServices().values()) { ServiceInfoImpl info = (ServiceInfoImpl) serviceInfo; info.revertState(); } } dns.revertState(); return true; } return false; } @Override DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException { return out; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) */ @Override public ServiceInfo getServiceInfo(boolean persistent) { ServiceInfoImpl info = new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, (byte[]) null); // info.setAddress(_addr); This is done in the sub class so we don't have to test for class type return info; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl) */ @Override public ServiceEvent getServiceEvent(JmDNSImpl dns) { ServiceInfo info = this.getServiceInfo(false); ((ServiceInfoImpl) info).setDns(dns); return new ServiceEventImpl(dns, info.getType(), info.getName(), info); } /* * (non-Javadoc) * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder) */ @Override protected void toString(StringBuilder aLog) { super.toString(aLog); aLog.append(" address: '" + (this.getAddress() != null ? this.getAddress().getHostAddress() : "null") + "'"); } } /** * Pointer record. */ public static class Pointer extends DNSRecord { // private static Logger logger = Logger.getLogger(Pointer.class.getName()); private final String _alias; public Pointer(String name, DNSRecordClass recordClass, boolean unique, int ttl, String alias) { super(name, DNSRecordType.TYPE_PTR, recordClass, unique, ttl); this._alias = alias; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSEntry#isSameEntry(javax.jmdns.impl.DNSEntry) */ @Override public boolean isSameEntry(DNSEntry entry) { return super.isSameEntry(entry) && (entry instanceof Pointer) && this.sameValue((Pointer) entry); } @Override void write(MessageOutputStream out) { out.writeName(_alias); } @Override boolean sameValue(DNSRecord other) { if (! (other instanceof Pointer) ) { return false; } Pointer pointer = (Pointer) other; if ((_alias == null) && (pointer._alias != null)) { return false; } return _alias.equals(pointer._alias); } @Override public boolean isSingleValued() { return false; } @Override boolean handleQuery(JmDNSImpl dns, long expirationTime) { // Nothing to do (?) // I think there is no possibility for conflicts for this record type? return false; } @Override 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; } @Override DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException { return out; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) */ @Override public ServiceInfo getServiceInfo(boolean persistent) { if (this.isServicesDiscoveryMetaQuery()) { // The service name is in the alias Map map = ServiceInfoImpl.decodeQualifiedNameMapForType(this.getAlias()); return new ServiceInfoImpl(map, 0, 0, 0, persistent, (byte[]) null); } else if (this.isReverseLookup()) { return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, (byte[]) null); } else if (this.isDomainDiscoveryQuery()) { // FIXME [PJYF Nov 16 2010] We do not currently support domain discovery return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, (byte[]) null); } Map map = ServiceInfoImpl.decodeQualifiedNameMapForType(this.getAlias()); map.put(Fields.Subtype, this.getQualifiedNameMap().get(Fields.Subtype)); return new ServiceInfoImpl(map, 0, 0, 0, persistent, this.getAlias()); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl) */ @Override public ServiceEvent getServiceEvent(JmDNSImpl dns) { ServiceInfo info = this.getServiceInfo(false); ((ServiceInfoImpl) info).setDns(dns); String domainName = info.getType(); String serviceName = JmDNSImpl.toUnqualifiedName(domainName, this.getAlias()); return new ServiceEventImpl(dns, domainName, serviceName, info); } /* * (non-Javadoc) * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder) */ @Override protected void toString(StringBuilder aLog) { super.toString(aLog); aLog.append(" alias: '" + (_alias != null ? _alias.toString() : "null") + "'"); } } public final static byte[] EMPTY_TXT = new byte[] { 0 }; public static class Text extends DNSRecord { // private static Logger logger = Logger.getLogger(Text.class.getName()); private final byte[] _text; public Text(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte text[]) { super(name, DNSRecordType.TYPE_TXT, recordClass, unique, ttl); this._text = (text != null && text.length > 0 ? text : EMPTY_TXT); } /** * @return the text */ byte[] getText() { return this._text; } @Override void write(MessageOutputStream out) { out.writeBytes(_text, 0, _text.length); } @Override boolean sameValue(DNSRecord other) { if (! (other instanceof Text) ) { return false; } Text txt = (Text) other; if ((_text == null) && (txt._text != null)) { return false; } 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; } @Override public boolean isSingleValued() { return true; } @Override boolean handleQuery(JmDNSImpl dns, long expirationTime) { // Nothing to do (?) // I think there is no possibility for conflicts for this record type? return false; } @Override 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; } @Override DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException { return out; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) */ @Override public ServiceInfo getServiceInfo(boolean persistent) { return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, _text); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl) */ @Override public ServiceEvent getServiceEvent(JmDNSImpl dns) { ServiceInfo info = this.getServiceInfo(false); ((ServiceInfoImpl) info).setDns(dns); return new ServiceEventImpl(dns, info.getType(), info.getName(), info); } /* * (non-Javadoc) * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder) */ @Override protected void toString(StringBuilder aLog) { super.toString(aLog); aLog.append(" text: '" + ((_text.length > 20) ? new String(_text, 0, 17) + "..." : new String(_text)) + "'"); } } /** * Service record. */ public static class Service extends DNSRecord { private static Logger logger1 = Logger.getLogger(Service.class.getName()); private final int _priority; private final int _weight; private final int _port; private final String _server; public Service(String name, DNSRecordClass recordClass, boolean unique, int ttl, int priority, int weight, int port, String server) { super(name, DNSRecordType.TYPE_SRV, recordClass, unique, ttl); this._priority = priority; this._weight = weight; this._port = port; this._server = server; } @Override void write(MessageOutputStream out) { out.writeShort(_priority); out.writeShort(_weight); out.writeShort(_port); if (DNSIncoming.USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET) { out.writeName(_server); } else { // [PJYF Nov 13 2010] Do we still need this? This looks really bad. All label are supposed to start by a length. 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); } } @Override protected void toByteArray(DataOutputStream dout) throws IOException { super.toByteArray(dout); dout.writeShort(_priority); dout.writeShort(_weight); dout.writeShort(_port); try { dout.write(_server.getBytes("UTF-8")); } catch (UnsupportedEncodingException exception) { /* UTF-8 is always present */ } } String getServer() { return _server; } /** * @return the priority */ public int getPriority() { return this._priority; } /** * @return the weight */ public int getWeight() { return this._weight; } /** * @return the port */ public int getPort() { return this._port; } @Override boolean sameValue(DNSRecord other) { if (! (other instanceof Service) ) { return false; } Service s = (Service) other; return (_priority == s._priority) && (_weight == s._weight) && (_port == s._port) && _server.equals(s._server); } @Override public boolean isSingleValued() { return true; } @Override boolean handleQuery(JmDNSImpl dns, long expirationTime) { ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(this.getKey()); if (info != null && (info.isAnnouncing() || info.isAnnounced()) && (_port != info.getPort() || !_server.equalsIgnoreCase(dns.getLocalHost().getName()))) { logger1.finer("handleQuery() Conflicting probe detected from: " + getRecordSource()); DNSRecord.Service localService = new DNSRecord.Service(info.getQualifiedName(), DNSRecordClass.CLASS_IN, DNSRecordClass.UNIQUE, DNSConstants.DNS_TTL, info.getPriority(), info.getWeight(), info.getPort(), dns.getLocalHost().getName()); // This block is useful for debugging race conditions when jmdns is responding to itself. try { if (dns.getInterface().equals(getRecordSource())) { logger1.warning("Got conflicting probe from ourselves\n" + "incoming: " + this.toString() + "\n" + "local : " + localService.toString()); } } catch (IOException e) { logger1.log(Level.WARNING, "IOException", e); } int comparison = this.compareTo(localService); if (comparison == 0) { // the 2 records are identical this probably means we are seeing our own record. // With multiple 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. logger1.finer("handleQuery() Ignoring a identical service query"); return false; } // Tie breaker test if (info.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); logger1.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; } @Override boolean handleResponse(JmDNSImpl dns) { ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(this.getKey()); if (info != null && (_port != info.getPort() || !_server.equalsIgnoreCase(dns.getLocalHost().getName()))) { logger1.finer("handleResponse() Denial detected"); if (info.isProbing()) { String oldName = info.getQualifiedName().toLowerCase(); info.setName(dns.incrementName(info.getName())); dns.getServices().remove(oldName); dns.getServices().put(info.getQualifiedName().toLowerCase(), info); logger1.finer("handleResponse() New unique name chose:" + info.getName()); } info.revertState(); return true; } return false; } @Override DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException { ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(this.getKey()); if (info != null) { if (this._port == info.getPort() != _server.equals(dns.getLocalHost().getName())) { return dns.addAnswer(in, addr, port, out, new DNSRecord.Service(info.getQualifiedName(), DNSRecordClass.CLASS_IN, DNSRecordClass.UNIQUE, DNSConstants.DNS_TTL, info.getPriority(), info.getWeight(), info.getPort(), dns .getLocalHost().getName())); } } return out; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) */ @Override public ServiceInfo getServiceInfo(boolean persistent) { return new ServiceInfoImpl(this.getQualifiedNameMap(), _port, _weight, _priority, persistent, _server); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl) */ @Override public ServiceEvent getServiceEvent(JmDNSImpl dns) { ServiceInfo info = this.getServiceInfo(false); ((ServiceInfoImpl) info).setDns(dns); // String domainName = ""; // String serviceName = this.getServer(); // int index = serviceName.indexOf('.'); // if (index > 0) // { // serviceName = this.getServer().substring(0, index); // if (index + 1 < this.getServer().length()) // domainName = this.getServer().substring(index + 1); // } // return new ServiceEventImpl(dns, domainName, serviceName, info); return new ServiceEventImpl(dns, info.getType(), info.getName(), info); } /* * (non-Javadoc) * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder) */ @Override protected void toString(StringBuilder aLog) { super.toString(aLog); aLog.append(" server: '" + _server + ":" + _port + "'"); } } public static class HostInformation extends DNSRecord { String _os; String _cpu; /** * @param name * @param recordClass * @param unique * @param ttl * @param cpu * @param os */ public HostInformation(String name, DNSRecordClass recordClass, boolean unique, int ttl, String cpu, String os) { super(name, DNSRecordType.TYPE_HINFO, recordClass, unique, ttl); _cpu = cpu; _os = os; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#addAnswer(javax.jmdns.impl.JmDNSImpl, javax.jmdns.impl.DNSIncoming, java.net.InetAddress, int, javax.jmdns.impl.DNSOutgoing) */ @Override DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException { return out; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#handleQuery(javax.jmdns.impl.JmDNSImpl, long) */ @Override boolean handleQuery(JmDNSImpl dns, long expirationTime) { return false; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#handleResponse(javax.jmdns.impl.JmDNSImpl) */ @Override boolean handleResponse(JmDNSImpl dns) { return false; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#sameValue(javax.jmdns.impl.DNSRecord) */ @Override boolean sameValue(DNSRecord other) { if (! (other instanceof HostInformation) ) { return false; } HostInformation hinfo = (HostInformation) other; if ((_cpu == null) && (hinfo._cpu != null)) { return false; } if ((_os == null) && (hinfo._os != null)) { return false; } return _cpu.equals(hinfo._cpu) && _os.equals(hinfo._os); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#isSingleValued() */ @Override public boolean isSingleValued() { return true; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#write(javax.jmdns.impl.DNSOutgoing) */ @Override void write(MessageOutputStream out) { String hostInfo = _cpu + " " + _os; out.writeUTF(hostInfo, 0, hostInfo.length()); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) */ @Override public ServiceInfo getServiceInfo(boolean persistent) { Map hinfo = new HashMap(2); hinfo.put("cpu", _cpu); hinfo.put("os", _os); return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, hinfo); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl) */ @Override public ServiceEvent getServiceEvent(JmDNSImpl dns) { ServiceInfo info = this.getServiceInfo(false); ((ServiceInfoImpl) info).setDns(dns); return new ServiceEventImpl(dns, info.getType(), info.getName(), info); } /* * (non-Javadoc) * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder) */ @Override protected void toString(StringBuilder aLog) { super.toString(aLog); aLog.append(" cpu: '" + _cpu + "' os: '" + _os + "'"); } } /** * Determine if a record can have multiple values in the cache. * * @return false if this record can have multiple values in the cache, true otherwise. */ public abstract boolean isSingleValued(); /** * Return a service information associated with that record if appropriate. * * @return service information */ public ServiceInfo getServiceInfo() { return this.getServiceInfo(false); } /** * Return a service information associated with that record if appropriate. * * @param persistent * if true ServiceListener.resolveService will be called whenever new new information is received. * @return service information */ public abstract ServiceInfo getServiceInfo(boolean persistent); /** * Creates and return a service event for this record. * * @param dns * DNS serviced by this event * @return service event */ public abstract ServiceEvent getServiceEvent(JmDNSImpl dns); public void setRecordSource(InetAddress source) { this._source = source; } public InetAddress getRecordSource() { return _source; } /* * (non-Javadoc) * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder) */ @Override protected void toString(StringBuilder aLog) { super.toString(aLog); aLog.append(" ttl: '" + getRemainingTTL(System.currentTimeMillis()) + "/" + _ttl + "'"); } public void setTTL(int ttl) { this._ttl = ttl; } public int getTTL() { return _ttl; } } jmdns-3.4.1/src/javax/jmdns/impl/DNSQuestion.java0000644000175000017500000003031411625404056021503 0ustar mathieumathieu// Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns.impl; import java.net.InetAddress; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceInfo.Fields; import javax.jmdns.impl.JmDNSImpl.ServiceTypeEntry; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; /** * A DNS question. * * @author Arthur van Hoff, Pierre Frisch */ public class DNSQuestion extends DNSEntry { private static Logger logger = Logger.getLogger(DNSQuestion.class.getName()); /** * Address question. */ private static class DNS4Address extends DNSQuestion { DNS4Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique) { super(name, type, recordClass, unique); } @Override public void addAnswers(JmDNSImpl jmDNSImpl, Set answers) { DNSRecord answer = jmDNSImpl.getLocalHost().getDNSAddressRecord(this.getRecordType(), DNSRecordClass.UNIQUE, DNSConstants.DNS_TTL); if (answer != null) { answers.add(answer); } } @Override public boolean iAmTheOnlyOne(JmDNSImpl jmDNSImpl) { String name = this.getName().toLowerCase(); return jmDNSImpl.getLocalHost().getName().equals(name) || jmDNSImpl.getServices().keySet().contains(name); } } /** * Address question. */ private static class DNS6Address extends DNSQuestion { DNS6Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique) { super(name, type, recordClass, unique); } @Override public void addAnswers(JmDNSImpl jmDNSImpl, Set answers) { DNSRecord answer = jmDNSImpl.getLocalHost().getDNSAddressRecord(this.getRecordType(), DNSRecordClass.UNIQUE, DNSConstants.DNS_TTL); if (answer != null) { answers.add(answer); } } @Override public boolean iAmTheOnlyOne(JmDNSImpl jmDNSImpl) { String name = this.getName().toLowerCase(); return jmDNSImpl.getLocalHost().getName().equals(name) || jmDNSImpl.getServices().keySet().contains(name); } } /** * Host Information question. */ private static class HostInformation extends DNSQuestion { HostInformation(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique) { super(name, type, recordClass, unique); } } /** * Pointer question. */ private static class Pointer extends DNSQuestion { Pointer(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique) { super(name, type, recordClass, unique); } @Override public void addAnswers(JmDNSImpl jmDNSImpl, Set answers) { // find matching services for (ServiceInfo serviceInfo : jmDNSImpl.getServices().values()) { this.addAnswersForServiceInfo(jmDNSImpl, answers, (ServiceInfoImpl) serviceInfo); } if (this.isServicesDiscoveryMetaQuery()) { for (String serviceType : jmDNSImpl.getServiceTypes().keySet()) { ServiceTypeEntry typeEntry = jmDNSImpl.getServiceTypes().get(serviceType); answers.add(new DNSRecord.Pointer("_services._dns-sd._udp.local.", DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, DNSConstants.DNS_TTL, typeEntry.getType())); } } else if (this.isReverseLookup()) { String ipValue = this.getQualifiedNameMap().get(Fields.Instance); if ((ipValue != null) && (ipValue.length() > 0)) { InetAddress address = jmDNSImpl.getLocalHost().getInetAddress(); String hostIPAddress = (address != null ? address.getHostAddress() : ""); if (ipValue.equalsIgnoreCase(hostIPAddress)) { if (this.isV4ReverseLookup()) { answers.add(jmDNSImpl.getLocalHost().getDNSReverseAddressRecord(DNSRecordType.TYPE_A, DNSRecordClass.NOT_UNIQUE, DNSConstants.DNS_TTL)); } if (this.isV6ReverseLookup()) { answers.add(jmDNSImpl.getLocalHost().getDNSReverseAddressRecord(DNSRecordType.TYPE_AAAA, DNSRecordClass.NOT_UNIQUE, DNSConstants.DNS_TTL)); } } } } else if (this.isDomainDiscoveryQuery()) { // FIXME [PJYF Nov 16 2010] We do not currently support domain discovery } } } /** * Service question. */ private static class Service extends DNSQuestion { Service(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique) { super(name, type, recordClass, unique); } @Override public void addAnswers(JmDNSImpl jmDNSImpl, Set answers) { String loname = this.getName().toLowerCase(); if (jmDNSImpl.getLocalHost().getName().equalsIgnoreCase(loname)) { // type = DNSConstants.TYPE_A; answers.addAll(jmDNSImpl.getLocalHost().answers(this.isUnique(), DNSConstants.DNS_TTL)); return; } // Service type request if (jmDNSImpl.getServiceTypes().containsKey(loname)) { DNSQuestion question = new Pointer(this.getName(), DNSRecordType.TYPE_PTR, this.getRecordClass(), this.isUnique()); question.addAnswers(jmDNSImpl, answers); return; } this.addAnswersForServiceInfo(jmDNSImpl, answers, (ServiceInfoImpl) jmDNSImpl.getServices().get(loname)); } @Override public boolean iAmTheOnlyOne(JmDNSImpl jmDNSImpl) { String name = this.getName().toLowerCase(); return jmDNSImpl.getLocalHost().getName().equals(name) || jmDNSImpl.getServices().keySet().contains(name); } } /** * Text question. */ private static class Text extends DNSQuestion { Text(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique) { super(name, type, recordClass, unique); } @Override public void addAnswers(JmDNSImpl jmDNSImpl, Set answers) { this.addAnswersForServiceInfo(jmDNSImpl, answers, (ServiceInfoImpl) jmDNSImpl.getServices().get(this.getName().toLowerCase())); } @Override public boolean iAmTheOnlyOne(JmDNSImpl jmDNSImpl) { String name = this.getName().toLowerCase(); return jmDNSImpl.getLocalHost().getName().equals(name) || jmDNSImpl.getServices().keySet().contains(name); } } /** * AllRecords question. */ private static class AllRecords extends DNSQuestion { AllRecords(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique) { super(name, type, recordClass, unique); } @Override public boolean isSameType(DNSEntry entry) { // We match all non null entry return (entry != null); } @Override public void addAnswers(JmDNSImpl jmDNSImpl, Set answers) { String loname = this.getName().toLowerCase(); if (jmDNSImpl.getLocalHost().getName().equalsIgnoreCase(loname)) { // type = DNSConstants.TYPE_A; answers.addAll(jmDNSImpl.getLocalHost().answers(this.isUnique(), DNSConstants.DNS_TTL)); return; } // Service type request if (jmDNSImpl.getServiceTypes().containsKey(loname)) { DNSQuestion question = new Pointer(this.getName(), DNSRecordType.TYPE_PTR, this.getRecordClass(), this.isUnique()); question.addAnswers(jmDNSImpl, answers); return; } this.addAnswersForServiceInfo(jmDNSImpl, answers, (ServiceInfoImpl) jmDNSImpl.getServices().get(loname)); } @Override public boolean iAmTheOnlyOne(JmDNSImpl jmDNSImpl) { String name = this.getName().toLowerCase(); return jmDNSImpl.getLocalHost().getName().equals(name) || jmDNSImpl.getServices().keySet().contains(name); } } DNSQuestion(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique) { super(name, type, recordClass, unique); } /** * Create a question. * * @param name * DNS name to be resolved * @param type * Record type to resolve * @param recordClass * Record class to resolve * @param unique * Request unicast response (Currently not supported in this implementation) * @return new question */ public static DNSQuestion newQuestion(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique) { switch (type) { case TYPE_A: return new DNS4Address(name, type, recordClass, unique); case TYPE_A6: return new DNS6Address(name, type, recordClass, unique); case TYPE_AAAA: return new DNS6Address(name, type, recordClass, unique); case TYPE_ANY: return new AllRecords(name, type, recordClass, unique); case TYPE_HINFO: return new HostInformation(name, type, recordClass, unique); case TYPE_PTR: return new Pointer(name, type, recordClass, unique); case TYPE_SRV: return new Service(name, type, recordClass, unique); case TYPE_TXT: return new Text(name, type, recordClass, unique); default: return new DNSQuestion(name, type, recordClass, unique); } } /** * Check if this question is answered by a given DNS record. */ boolean answeredBy(DNSEntry rec) { return this.isSameRecordClass(rec) && this.isSameType(rec) && this.getName().equals(rec.getName()); } /** * Adds answers to the list for our question. * * @param jmDNSImpl * DNS holding the records * @param answers * List of previous answer to append. */ public void addAnswers(JmDNSImpl jmDNSImpl, Set answers) { // By default we do nothing } protected void addAnswersForServiceInfo(JmDNSImpl jmDNSImpl, Set answers, ServiceInfoImpl info) { if ((info != null) && info.isAnnounced()) { if (this.getName().equalsIgnoreCase(info.getQualifiedName()) || this.getName().equalsIgnoreCase(info.getType())) { answers.addAll(jmDNSImpl.getLocalHost().answers(DNSRecordClass.UNIQUE, DNSConstants.DNS_TTL)); answers.addAll(info.answers(DNSRecordClass.UNIQUE, DNSConstants.DNS_TTL, jmDNSImpl.getLocalHost())); } if (logger.isLoggable(Level.FINER)) { logger.finer(jmDNSImpl.getName() + " DNSQuestion(" + this.getName() + ").addAnswersForServiceInfo(): info: " + info + "\n" + answers); } } } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSEntry#isStale(long) */ @Override public boolean isStale(long now) { return false; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSEntry#isExpired(long) */ @Override public boolean isExpired(long now) { return false; } /** * Checks if we are the only to be able to answer that question. * * @param jmDNSImpl * DNS holding the records * @return true if we are the only one with the answer to the question, false otherwise. */ public boolean iAmTheOnlyOne(JmDNSImpl jmDNSImpl) { return false; } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSEntry#toString(java.lang.StringBuilder) */ @Override public void toString(StringBuilder aLog) { // do nothing } }jmdns-3.4.1/src/javax/jmdns/impl/ServiceEventImpl.java0000644000175000017500000000621211625404056022553 0ustar mathieumathieu// /Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns.impl; import javax.jmdns.JmDNS; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; /** * ServiceEvent. * * @author Werner Randelshofer, Rick Blair */ /** * */ public class ServiceEventImpl extends ServiceEvent { /** * */ private static final long serialVersionUID = 7107973622016897488L; // private static Logger logger = Logger.getLogger(ServiceEvent.class.getName()); /** * The type name of the service. */ private final String _type; /** * The instance name of the service. Or null, if the event was fired to a service type listener. */ private final 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 final ServiceInfo _info; /** * Creates a new instance. * * @param jmDNS * 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 jmDNS, String type, String name, ServiceInfo info) { super(jmDNS); this._type = type; this._name = name; this._info = info; } /* * (non-Javadoc) * @see javax.jmdns.ServiceEvent#getDNS() */ @Override public JmDNS getDNS() { return (JmDNS) getSource(); } /* * (non-Javadoc) * @see javax.jmdns.ServiceEvent#getType() */ @Override public String getType() { return _type; } /* * (non-Javadoc) * @see javax.jmdns.ServiceEvent#getName() */ @Override public String getName() { return _name; } /* * (non-Javadoc) * @see java.util.EventObject#toString() */ @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("[" + this.getClass().getSimpleName() + "@" + System.identityHashCode(this) + " "); buf.append("\n\tname: '"); buf.append(this.getName()); buf.append("' type: '"); buf.append(this.getType()); buf.append("' info: '"); buf.append(this.getInfo()); buf.append("']"); // buf.append("' source: "); // buf.append("\n\t" + source + ""); // buf.append("\n]"); return buf.toString(); } /* * (non-Javadoc) * @see javax.jmdns.ServiceEvent#getInfo() */ @Override public ServiceInfo getInfo() { return _info; } /* * (non-Javadoc) * @see javax.jmdns.ServiceEvent#clone() */ @Override public ServiceEventImpl clone() { ServiceInfoImpl newInfo = new ServiceInfoImpl(this.getInfo()); return new ServiceEventImpl((JmDNSImpl) this.getDNS(), this.getType(), this.getName(), newInfo); } } jmdns-3.4.1/src/javax/jmdns/impl/constants/0000755000175000017500000000000011625404056020477 5ustar mathieumathieujmdns-3.4.1/src/javax/jmdns/impl/constants/DNSRecordType.java0000644000175000017500000001362611625404056023777 0ustar mathieumathieu/** * */ package javax.jmdns.impl.constants; import java.util.logging.Logger; /** * DNS Record Type * * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer, Pierre Frisch, Rick Blair */ public enum DNSRecordType { /** * Address */ TYPE_IGNORE("ignore", 0), /** * Address */ TYPE_A("a", 1), /** * Name Server */ TYPE_NS("ns", 2), /** * Mail Destination */ TYPE_MD("md", 3), /** * Mail Forwarder */ TYPE_MF("mf", 4), /** * Canonical Name */ TYPE_CNAME("cname", 5), /** * Start of Authority */ TYPE_SOA("soa", 6), /** * Mailbox */ TYPE_MB("mb", 7), /** * Mail Group */ TYPE_MG("mg", 8), /** * Mail Rename */ TYPE_MR("mr", 9), /** * NULL RR */ TYPE_NULL("null", 10), /** * Well-known-service */ TYPE_WKS("wks", 11), /** * Domain Name pointer */ TYPE_PTR("ptr", 12), /** * Host information */ TYPE_HINFO("hinfo", 13), /** * Mailbox information */ TYPE_MINFO("minfo", 14), /** * Mail exchanger */ TYPE_MX("mx", 15), /** * Arbitrary text string */ TYPE_TXT("txt", 16), /** * for Responsible Person [RFC1183] */ TYPE_RP("rp", 17), /** * for AFS Data Base location [RFC1183] */ TYPE_AFSDB("afsdb", 18), /** * for X.25 PSDN address [RFC1183] */ TYPE_X25("x25", 19), /** * for ISDN address [RFC1183] */ TYPE_ISDN("isdn", 20), /** * for Route Through [RFC1183] */ TYPE_RT("rt", 21), /** * for NSAP address, NSAP style A record [RFC1706] */ TYPE_NSAP("nsap", 22), /** * */ TYPE_NSAP_PTR("nsap-otr", 23), /** * for security signature [RFC2931] */ TYPE_SIG("sig", 24), /** * for security key [RFC2535] */ TYPE_KEY("key", 25), /** * X.400 mail mapping information [RFC2163] */ TYPE_PX("px", 26), /** * Geographical Position [RFC1712] */ TYPE_GPOS("gpos", 27), /** * IP6 Address [Thomson] */ TYPE_AAAA("aaaa", 28), /** * Location Information [Vixie] */ TYPE_LOC("loc", 29), /** * Next Domain - OBSOLETE [RFC2535, RFC3755] */ TYPE_NXT("nxt", 30), /** * Endpoint Identifier [Patton] */ TYPE_EID("eid", 31), /** * Nimrod Locator [Patton] */ TYPE_NIMLOC("nimloc", 32), /** * Server Selection [RFC2782] */ TYPE_SRV("srv", 33), /** * ATM Address [Dobrowski] */ TYPE_ATMA("atma", 34), /** * Naming Authority Pointer [RFC2168, RFC2915] */ TYPE_NAPTR("naptr", 35), /** * Key Exchanger [RFC2230] */ TYPE_KX("kx", 36), /** * CERT [RFC2538] */ TYPE_CERT("cert", 37), /** * A6 [RFC2874] */ TYPE_A6("a6", 38), /** * DNAME [RFC2672] */ TYPE_DNAME("dname", 39), /** * SINK [Eastlake] */ TYPE_SINK("sink", 40), /** * OPT [RFC2671] */ TYPE_OPT("opt", 41), /** * APL [RFC3123] */ TYPE_APL("apl", 42), /** * Delegation Signer [RFC3658] */ TYPE_DS("ds", 43), /** * SSH Key Fingerprint [RFC-ietf-secsh-dns-05.txt] */ TYPE_SSHFP("sshfp", 44), /** * RRSIG [RFC3755] */ TYPE_RRSIG("rrsig", 46), /** * NSEC [RFC3755] */ TYPE_NSEC("nsec", 47), /** * DNSKEY [RFC3755] */ TYPE_DNSKEY("dnskey", 48), /** * [IANA-Reserved] */ TYPE_UINFO("uinfo", 100), /** * [IANA-Reserved] */ TYPE_UID("uid", 101), /** * [IANA-Reserved] */ TYPE_GID("gid", 102), /** * [IANA-Reserved] */ TYPE_UNSPEC("unspec", 103), /** * Transaction Key [RFC2930] */ TYPE_TKEY("tkey", 249), /** * Transaction Signature [RFC2845] */ TYPE_TSIG("tsig", 250), /** * Incremental transfer [RFC1995] */ TYPE_IXFR("ixfr", 251), /** * Transfer of an entire zone [RFC1035] */ TYPE_AXFR("axfr", 252), /** * Mailbox-related records (MB, MG or MR) [RFC1035] */ TYPE_MAILA("mails", 253), /** * Mail agent RRs (Obsolete - see MX) [RFC1035] */ TYPE_MAILB("mailb", 254), /** * Request for all records [RFC1035] */ TYPE_ANY("any", 255); private static Logger logger = Logger.getLogger(DNSRecordType.class.getName()); private final String _externalName; private final int _index; DNSRecordType(String name, int index) { _externalName = name; _index = index; } /** * Return the string representation of this type * * @return String */ public String externalName() { return _externalName; } /** * Return the numeric value of this type * * @return String */ public int indexValue() { return _index; } /** * @param name * @return type for name */ public static DNSRecordType typeForName(String name) { if (name != null) { String aName = name.toLowerCase(); for (DNSRecordType aType : DNSRecordType.values()) { if (aType._externalName.equals(aName)) return aType; } } logger.severe("Could not find record type for name: " + name); return TYPE_IGNORE; } /** * @param index * @return type for name */ public static DNSRecordType typeForIndex(int index) { for (DNSRecordType aType : DNSRecordType.values()) { if (aType._index == index) return aType; } logger.severe("Could not find record type for index: " + index); return TYPE_IGNORE; } @Override public String toString() { return this.name() + " index " + this.indexValue(); } } jmdns-3.4.1/src/javax/jmdns/impl/constants/DNSRecordClass.java0000644000175000017500000000627311625404056024123 0ustar mathieumathieu/** * */ package javax.jmdns.impl.constants; import java.util.logging.Level; import java.util.logging.Logger; /** * DNS Record Class * * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer, Pierre Frisch, Rick Blair */ public enum DNSRecordClass { /** * */ CLASS_UNKNOWN("?", 0), /** * static final Internet */ CLASS_IN("in", 1), /** * CSNET */ CLASS_CS("cs", 2), /** * CHAOS */ CLASS_CH("ch", 3), /** * Hesiod */ CLASS_HS("hs", 4), /** * Used in DNS UPDATE [RFC 2136] */ CLASS_NONE("none", 254), /** * Not a DNS class, but a DNS query class, meaning "all classes" */ CLASS_ANY("any", 255); private static Logger logger = Logger.getLogger(DNSRecordClass.class.getName()); /** * Multicast DNS uses the bottom 15 bits to identify the record class...
* Except for pseudo records like OPT. */ public static final int CLASS_MASK = 0x7FFF; /** * For answers the top bit indicates that all other cached records are now invalid.
* For questions it indicates that we should send a unicast response. */ public static final int CLASS_UNIQUE = 0x8000; /** * */ public static final boolean UNIQUE = true; /** * */ public static final boolean NOT_UNIQUE = false; private final String _externalName; private final int _index; DNSRecordClass(String name, int index) { _externalName = name; _index = index; } /** * Return the string representation of this type * * @return String */ public String externalName() { return _externalName; } /** * Return the numeric value of this type * * @return String */ public int indexValue() { return _index; } /** * Checks if the class is unique * * @param index * @return true is the class is unique, false otherwise. */ public boolean isUnique(int index) { return (this != CLASS_UNKNOWN) && ((index & CLASS_UNIQUE) != 0); } /** * @param name * @return class for name */ public static DNSRecordClass classForName(String name) { if (name != null) { String aName = name.toLowerCase(); for (DNSRecordClass aClass : DNSRecordClass.values()) { if (aClass._externalName.equals(aName)) return aClass; } } logger.log(Level.WARNING, "Could not find record class for name: " + name); return CLASS_UNKNOWN; } /** * @param index * @return class for name */ public static DNSRecordClass classForIndex(int index) { int maskedIndex = index & CLASS_MASK; for (DNSRecordClass aClass : DNSRecordClass.values()) { if (aClass._index == maskedIndex) return aClass; } logger.log(Level.WARNING, "Could not find record class for index: " + index); return CLASS_UNKNOWN; } @Override public String toString() { return this.name() + " index " + this.indexValue(); } } jmdns-3.4.1/src/javax/jmdns/impl/constants/DNSState.java0000644000175000017500000001276111625404056022776 0ustar mathieumathieu// Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns.impl.constants; /** * DNSState defines the possible states for services registered with JmDNS. * * @author Werner Randelshofer, Rick Blair, Pierre Frisch */ public enum DNSState { /** * */ PROBING_1("probing 1", StateClass.probing), /** * */ PROBING_2("probing 2", StateClass.probing), /** * */ PROBING_3("probing 3", StateClass.probing), /** * */ ANNOUNCING_1("announcing 1", StateClass.announcing), /** * */ ANNOUNCING_2("announcing 2", StateClass.announcing), /** * */ ANNOUNCED("announced", StateClass.announced), /** * */ CANCELING_1("canceling 1", StateClass.canceling), /** * */ CANCELING_2("canceling 2", StateClass.canceling), /** * */ CANCELING_3("canceling 3", StateClass.canceling), /** * */ CANCELED("canceled", StateClass.canceled), /** * */ CLOSING("closing", StateClass.closing), /** * */ CLOSED("closed", StateClass.closed); private enum StateClass { probing, announcing, announced, canceling, canceled, closing, closed } // private static Logger logger = Logger.getLogger(DNSState.class.getName()); private final String _name; private final StateClass _state; private DNSState(String name, StateClass state) { _name = name; _state = state; } @Override public final String toString() { return _name; } /** * 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.
* or CANCELING_1, CANCELING_2, CANCELING_3, CANCELED Does not advance for ANNOUNCED and CANCELED state. * * @return next state */ public final DNSState advance() { switch (this) { case PROBING_1: return PROBING_2; case PROBING_2: return PROBING_3; case PROBING_3: return ANNOUNCING_1; case ANNOUNCING_1: return ANNOUNCING_2; case ANNOUNCING_2: return ANNOUNCED; case ANNOUNCED: return ANNOUNCED; case CANCELING_1: return CANCELING_2; case CANCELING_2: return CANCELING_3; case CANCELING_3: return CANCELED; case CANCELED: return CANCELED; case CLOSING: return CLOSED; case CLOSED: return CLOSED; default: // This is just to keep the compiler happy as we have covered all cases before. return this; } } /** * Returns to the next reverted state. All states except CANCELED revert to PROBING_1. Status CANCELED does not revert. * * @return reverted state */ public final DNSState revert() { switch (this) { case PROBING_1: case PROBING_2: case PROBING_3: case ANNOUNCING_1: case ANNOUNCING_2: case ANNOUNCED: return PROBING_1; case CANCELING_1: case CANCELING_2: case CANCELING_3: return CANCELING_1; case CANCELED: return CANCELED; case CLOSING: return CLOSING; case CLOSED: return CLOSED; default: // This is just to keep the compiler happy as we have covered all cases before. return this; } } /** * Returns true, if this is a probing state. * * @return true if probing state, false otherwise */ public final boolean isProbing() { return _state == StateClass.probing; } /** * Returns true, if this is an announcing state. * * @return true if announcing state, false otherwise */ public final boolean isAnnouncing() { return _state == StateClass.announcing; } /** * Returns true, if this is an announced state. * * @return true if announced state, false otherwise */ public final boolean isAnnounced() { return _state == StateClass.announced; } /** * Returns true, if this is a canceling state. * * @return true if canceling state, false otherwise */ public final boolean isCanceling() { return _state == StateClass.canceling; } /** * Returns true, if this is a canceled state. * * @return true if canceled state, false otherwise */ public final boolean isCanceled() { return _state == StateClass.canceled; } /** * Returns true, if this is a closing state. * * @return true if closing state, false otherwise */ public final boolean isClosing() { return _state == StateClass.closing; } /** * Returns true, if this is a closing state. * * @return true if closed state, false otherwise */ public final boolean isClosed() { return _state == StateClass.closed; } } jmdns-3.4.1/src/javax/jmdns/impl/constants/DNSLabel.java0000644000175000017500000000334711625404056022735 0ustar mathieumathieu/** * */ package javax.jmdns.impl.constants; /** * DNS label. * * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer, Pierre Frisch, Rick Blair */ public enum DNSLabel { /** * This is unallocated. */ Unknown("", 0x80), /** * Standard label [RFC 1035] */ Standard("standard label", 0x00), /** * Compressed label [RFC 1035] */ Compressed("compressed label", 0xC0), /** * Extended label [RFC 2671] */ Extended("extended label", 0x40); /** * DNS label types are encoded on the first 2 bits */ static final int LABEL_MASK = 0xC0; static final int LABEL_NOT_MASK = 0x3F; private final String _externalName; private final int _index; DNSLabel(String name, int index) { _externalName = name; _index = index; } /** * Return the string representation of this type * * @return String */ public String externalName() { return _externalName; } /** * Return the numeric value of this type * * @return String */ public int indexValue() { return _index; } /** * @param index * @return label */ public static DNSLabel labelForByte(int index) { int maskedIndex = index & LABEL_MASK; for (DNSLabel aLabel : DNSLabel.values()) { if (aLabel._index == maskedIndex) return aLabel; } return Unknown; } /** * @param index * @return masked value */ public static int labelValue(int index) { return index & LABEL_NOT_MASK; } @Override public String toString() { return this.name() + " index " + this.indexValue(); } } jmdns-3.4.1/src/javax/jmdns/impl/constants/DNSOperationCode.java0000644000175000017500000000326411625404056024447 0ustar mathieumathieu/** * */ package javax.jmdns.impl.constants; /** * DNS operation code. * * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer, Pierre Frisch, Rick Blair */ public enum DNSOperationCode { /** * Query [RFC1035] */ Query("Query", 0), /** * IQuery (Inverse Query, Obsolete) [RFC3425] */ IQuery("Inverse Query", 1), /** * Status [RFC1035] */ Status("Status", 2), /** * Unassigned */ Unassigned("Unassigned", 3), /** * Notify [RFC1996] */ Notify("Notify", 4), /** * Update [RFC2136] */ Update("Update", 5); /** * DNS RCode types are encoded on the last 4 bits */ static final int OpCode_MASK = 0x7800; private final String _externalName; private final int _index; DNSOperationCode(String name, int index) { _externalName = name; _index = index; } /** * Return the string representation of this type * * @return String */ public String externalName() { return _externalName; } /** * Return the numeric value of this type * * @return String */ public int indexValue() { return _index; } /** * @param flags * @return label */ public static DNSOperationCode operationCodeForFlags(int flags) { int maskedIndex = (flags & OpCode_MASK) >> 11; for (DNSOperationCode aCode : DNSOperationCode.values()) { if (aCode._index == maskedIndex) return aCode; } return Unassigned; } @Override public String toString() { return this.name() + " index " + this.indexValue(); } } jmdns-3.4.1/src/javax/jmdns/impl/constants/DNSOptionCode.java0000644000175000017500000000305111625404056023751 0ustar mathieumathieu/** * */ package javax.jmdns.impl.constants; /** * DNS option code. * * @author Arthur van Hoff, Pierre Frisch, Rick Blair */ public enum DNSOptionCode { /** * Token */ Unknown("Unknown", 65535), /** * Long-Lived Queries Option [http://files.dns-sd.org/draft-sekar-dns-llq.txt] */ LLQ("LLQ", 1), /** * Update Leases Option [http://files.dns-sd.org/draft-sekar-dns-ul.txt] */ UL("UL", 2), /** * Name Server Identifier Option [RFC5001] */ NSID("NSID", 3), /** * Owner Option [draft-cheshire-edns0-owner-option] */ Owner("Owner", 4); private final String _externalName; private final int _index; DNSOptionCode(String name, int index) { _externalName = name; _index = index; } /** * Return the string representation of this type * * @return String */ public String externalName() { return _externalName; } /** * Return the numeric value of this type * * @return String */ public int indexValue() { return _index; } /** * @param optioncode * @return label */ public static DNSOptionCode resultCodeForFlags(int optioncode) { int maskedIndex = optioncode; for (DNSOptionCode aCode : DNSOptionCode.values()) { if (aCode._index == maskedIndex) return aCode; } return Unknown; } @Override public String toString() { return this.name() + " index " + this.indexValue(); } } jmdns-3.4.1/src/javax/jmdns/impl/constants/DNSConstants.java0000644000175000017500000001174411625404056023672 0ustar mathieumathieu// Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns.impl.constants; /** * DNS constants. * * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer, Pierre Frisch, Rick Blair */ public final class DNSConstants { // http://www.iana.org/assignments/dns-parameters // changed to final class - jeffs public static final String MDNS_GROUP = "224.0.0.251"; public static final String MDNS_GROUP_IPV6 = "FF02::FB"; public static final int MDNS_PORT = Integer.parseInt(System.getProperty("net.mdns.port", "5353")); public static final int DNS_PORT = 53; public static final int DNS_TTL = 60 * 60; // default one hour TTL // public static final int DNS_TTL = 120 * 60; // two hour TTL (draft-cheshire-dnsext-multicastdns.txt ch 13) public static final int MAX_MSG_TYPICAL = 1460; public static final int MAX_MSG_ABSOLUTE = 8972; public static final int FLAGS_QR_MASK = 0x8000; // Query response mask public static final int FLAGS_QR_QUERY = 0x0000; // Query public static final int FLAGS_QR_RESPONSE = 0x8000; // Response public static final int FLAGS_AA = 0x0400; // Authorative answer public static final int FLAGS_TC = 0x0200; // Truncated public static final int FLAGS_RD = 0x0100; // Recursion desired public static final int FLAGS_RA = 0x8000; // Recursion available public static final int FLAGS_Z = 0x0040; // Zero public static final int FLAGS_AD = 0x0020; // Authentic data public static final int FLAGS_CD = 0x0010; // Checking disabled // Time Intervals for various functions public static final int SHARED_QUERY_TIME = 20; // milliseconds before send shared query public static final int QUERY_WAIT_INTERVAL = 225; // milliseconds between query loops. public static final int PROBE_WAIT_INTERVAL = 250; // milliseconds between probe loops. public static final int RESPONSE_MIN_WAIT_INTERVAL = 20; // minimal wait interval for response. public static final int RESPONSE_MAX_WAIT_INTERVAL = 115; // maximal wait interval for response public static final int PROBE_CONFLICT_INTERVAL = 1000; // milliseconds to wait after conflict. public static final int PROBE_THROTTLE_COUNT = 10; // After x tries go 1 time a sec. on probes. public static final int PROBE_THROTTLE_COUNT_INTERVAL = 5000; // We only increment the throttle count, if the previous increment is inside this interval. public static final int ANNOUNCE_WAIT_INTERVAL = 1000; // milliseconds between Announce loops. public static final int RECORD_REAPER_INTERVAL = 10000; // milliseconds between cache cleanups. public static final int RECORD_EXPIRY_DELAY = 1; // This is 1s delay used in ttl and therefore in seconds public static final int KNOWN_ANSWER_TTL = 120; public static final int ANNOUNCED_RENEWAL_TTL_INTERVAL = DNS_TTL * 500; // 50% of the TTL in milliseconds public static final long CLOSE_TIMEOUT = ANNOUNCE_WAIT_INTERVAL * 5L; public static final long SERVICE_INFO_TIMEOUT = ANNOUNCE_WAIT_INTERVAL * 6L; public static final int NETWORK_CHECK_INTERVAL = 10 * 1000; // 10 secondes } jmdns-3.4.1/src/javax/jmdns/impl/constants/package-info.java0000644000175000017500000000004511625404056023665 0ustar mathieumathieupackage javax.jmdns.impl.constants; jmdns-3.4.1/src/javax/jmdns/impl/constants/DNSResultCode.java0000644000175000017500000000766511625404056023776 0ustar mathieumathieu/** * */ package javax.jmdns.impl.constants; /** * DNS result code. * * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer, Pierre Frisch, Rick Blair */ public enum DNSResultCode { /** * Token */ Unknown("Unknown", 65535), /** * No Error [RFC1035] */ NoError("No Error", 0), /** * Format Error [RFC1035] */ FormErr("Format Error", 1), /** * Server Failure [RFC1035] */ ServFail("Server Failure", 2), /** * Non-Existent Domain [RFC1035] */ NXDomain("Non-Existent Domain", 3), /** * Not Implemented [RFC1035] */ NotImp("Not Implemented", 4), /** * Query Refused [RFC1035] */ Refused("Query Refused", 5), /** * Name Exists when it should not [RFC2136] */ YXDomain("Name Exists when it should not", 6), /** * RR Set Exists when it should not [RFC2136] */ YXRRSet("RR Set Exists when it should not", 7), /** * RR Set that should exist does not [RFC2136] */ NXRRSet("RR Set that should exist does not", 8), /** * Server Not Authoritative for zone [RFC2136]] */ NotAuth("Server Not Authoritative for zone", 9), /** * Name not contained in zone [RFC2136] */ NotZone("NotZone Name not contained in zone", 10), ; // 0 NoError No Error [RFC1035] // 1 FormErr Format Error [RFC1035] // 2 ServFail Server Failure [RFC1035] // 3 NXDomain Non-Existent Domain [RFC1035] // 4 NotImp Not Implemented [RFC1035] // 5 Refused Query Refused [RFC1035] // 6 YXDomain Name Exists when it should not [RFC2136] // 7 YXRRSet RR Set Exists when it should not [RFC2136] // 8 NXRRSet RR Set that should exist does not [RFC2136] // 9 NotAuth Server Not Authoritative for zone [RFC2136] // 10 NotZone Name not contained in zone [RFC2136] // 11-15 Unassigned // 16 BADVERS Bad OPT Version [RFC2671] // 16 BADSIG TSIG Signature Failure [RFC2845] // 17 BADKEY Key not recognized [RFC2845] // 18 BADTIME Signature out of time window [RFC2845] // 19 BADMODE Bad TKEY Mode [RFC2930] // 20 BADNAME Duplicate key name [RFC2930] // 21 BADALG Algorithm not supported [RFC2930] // 22 BADTRUNC Bad Truncation [RFC4635] // 23-3840 Unassigned // 3841-4095 Reserved for Private Use [RFC5395] // 4096-65534 Unassigned // 65535 Reserved, can be allocated by Standards Action [RFC5395] /** * DNS Result Code types are encoded on the last 4 bits */ final static int RCode_MASK = 0x0F; /** * DNS Extended Result Code types are encoded on the first 8 bits */ final static int ExtendedRCode_MASK = 0xFF; private final String _externalName; private final int _index; DNSResultCode(String name, int index) { _externalName = name; _index = index; } /** * Return the string representation of this type * * @return String */ public String externalName() { return _externalName; } /** * Return the numeric value of this type * * @return String */ public int indexValue() { return _index; } /** * @param flags * @return label */ public static DNSResultCode resultCodeForFlags(int flags) { int maskedIndex = flags & RCode_MASK; for (DNSResultCode aCode : DNSResultCode.values()) { if (aCode._index == maskedIndex) return aCode; } return Unknown; } public static DNSResultCode resultCodeForFlags(int flags, int extendedRCode) { int maskedIndex = ((extendedRCode >> 28) & ExtendedRCode_MASK) | (flags & RCode_MASK); for (DNSResultCode aCode : DNSResultCode.values()) { if (aCode._index == maskedIndex) return aCode; } return Unknown; } @Override public String toString() { return this.name() + " index " + this.indexValue(); } } jmdns-3.4.1/src/javax/jmdns/impl/ServiceInfoImpl.java0000644000175000017500000012306111625404056022367 0ustar mathieumathieu// 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.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; import javax.jmdns.impl.DNSRecord.Pointer; import javax.jmdns.impl.DNSRecord.Service; import javax.jmdns.impl.DNSRecord.Text; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; import javax.jmdns.impl.constants.DNSState; import javax.jmdns.impl.tasks.DNSTask; /** * JmDNS service information. * * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer */ public class ServiceInfoImpl extends ServiceInfo implements DNSListener, DNSStatefulObject { private static Logger logger = Logger.getLogger(ServiceInfoImpl.class.getName()); private String _domain; private String _protocol; private String _application; private String _name; private String _subtype; private String _server; private int _port; private int _weight; private int _priority; private byte _text[]; private Map _props; private final Set _ipv4Addresses; private final Set _ipv6Addresses; private transient String _key; private boolean _persistent; private boolean _needTextAnnouncing; private final ServiceInfoState _state; private Delegate _delegate; public static interface Delegate { public void textValueUpdated(ServiceInfo target, byte[] value); } private final static class ServiceInfoState extends DNSStatefulObject.DefaultImplementation { private static final long serialVersionUID = 1104131034952196820L; private final ServiceInfoImpl _info; /** * @param info */ public ServiceInfoState(ServiceInfoImpl info) { super(); _info = info; } @Override protected void setTask(DNSTask task) { super.setTask(task); if ((this._task == null) && _info.needTextAnnouncing()) { this.lock(); try { if ((this._task == null) && _info.needTextAnnouncing()) { if (this._state.isAnnounced()) { this.setState(DNSState.ANNOUNCING_1); if (this.getDns() != null) { this.getDns().startAnnouncer(); } } _info.setNeedTextAnnouncing(false); } } finally { this.unlock(); } } } @Override public void setDns(JmDNSImpl dns) { super.setDns(dns); } } /** * @param type * @param name * @param subtype * @param port * @param weight * @param priority * @param persistent * @param text * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, String) */ public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, String text) { this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, (byte[]) null); _server = text; try { ByteArrayOutputStream out = new ByteArrayOutputStream(text.length()); writeUTF(out, text); this._text = out.toByteArray(); } catch (IOException e) { throw new RuntimeException("unexpected exception: " + e); } } /** * @param type * @param name * @param subtype * @param port * @param weight * @param priority * @param persistent * @param props * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, Map) */ public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, Map props) { this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, textFromProperties(props)); } /** * @param type * @param name * @param subtype * @param port * @param weight * @param priority * @param persistent * @param text * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, byte[]) */ public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, byte text[]) { this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, text); } public ServiceInfoImpl(Map qualifiedNameMap, int port, int weight, int priority, boolean persistent, Map props) { this(qualifiedNameMap, port, weight, priority, persistent, textFromProperties(props)); } ServiceInfoImpl(Map qualifiedNameMap, int port, int weight, int priority, boolean persistent, String text) { this(qualifiedNameMap, port, weight, priority, persistent, (byte[]) null); _server = text; try { ByteArrayOutputStream out = new ByteArrayOutputStream(text.length()); writeUTF(out, text); this._text = out.toByteArray(); } catch (IOException e) { throw new RuntimeException("unexpected exception: " + e); } } ServiceInfoImpl(Map qualifiedNameMap, int port, int weight, int priority, boolean persistent, byte text[]) { Map map = ServiceInfoImpl.checkQualifiedNameMap(qualifiedNameMap); this._domain = map.get(Fields.Domain); this._protocol = map.get(Fields.Protocol); this._application = map.get(Fields.Application); this._name = map.get(Fields.Instance); this._subtype = map.get(Fields.Subtype); this._port = port; this._weight = weight; this._priority = priority; this._text = text; this.setNeedTextAnnouncing(false); this._state = new ServiceInfoState(this); this._persistent = persistent; this._ipv4Addresses = Collections.synchronizedSet(new LinkedHashSet()); this._ipv6Addresses = Collections.synchronizedSet(new LinkedHashSet()); } /** * During recovery we need to duplicate service info to reregister them * * @param info */ ServiceInfoImpl(ServiceInfo info) { this._ipv4Addresses = Collections.synchronizedSet(new LinkedHashSet()); this._ipv6Addresses = Collections.synchronizedSet(new LinkedHashSet()); if (info != null) { this._domain = info.getDomain(); this._protocol = info.getProtocol(); this._application = info.getApplication(); this._name = info.getName(); this._subtype = info.getSubtype(); this._port = info.getPort(); this._weight = info.getWeight(); this._priority = info.getPriority(); this._text = info.getTextBytes(); this._persistent = info.isPersistent(); Inet6Address[] ipv6Addresses = info.getInet6Addresses(); for (Inet6Address address : ipv6Addresses) { this._ipv6Addresses.add(address); } Inet4Address[] ipv4Addresses = info.getInet4Addresses(); for (Inet4Address address : ipv4Addresses) { this._ipv4Addresses.add(address); } } this._state = new ServiceInfoState(this); } public static Map decodeQualifiedNameMap(String type, String name, String subtype) { Map qualifiedNameMap = decodeQualifiedNameMapForType(type); qualifiedNameMap.put(Fields.Instance, name); qualifiedNameMap.put(Fields.Subtype, subtype); return checkQualifiedNameMap(qualifiedNameMap); } public static Map decodeQualifiedNameMapForType(String type) { int index; String casePreservedType = type; String aType = type.toLowerCase(); String application = aType; String protocol = ""; String subtype = ""; String name = ""; String domain = ""; if (aType.contains("in-addr.arpa") || aType.contains("ip6.arpa")) { index = (aType.contains("in-addr.arpa") ? aType.indexOf("in-addr.arpa") : aType.indexOf("ip6.arpa")); name = removeSeparators(casePreservedType.substring(0, index)); domain = casePreservedType.substring(index); application = ""; } else if ((!aType.contains("_")) && aType.contains(".")) { index = aType.indexOf('.'); name = removeSeparators(casePreservedType.substring(0, index)); domain = removeSeparators(casePreservedType.substring(index)); application = ""; } else { // First remove the name if it there. if (!aType.startsWith("_") || aType.startsWith("_services")) { index = aType.indexOf('.'); if (index > 0) { // We need to preserve the case for the user readable name. name = casePreservedType.substring(0, index); if (index + 1 < aType.length()) { aType = aType.substring(index + 1); casePreservedType = casePreservedType.substring(index + 1); } } } index = aType.lastIndexOf("._"); if (index > 0) { int start = index + 2; int end = aType.indexOf('.', start); protocol = casePreservedType.substring(start, end); } if (protocol.length() > 0) { index = aType.indexOf("_" + protocol.toLowerCase() + "."); int start = index + protocol.length() + 2; int end = aType.length() - (aType.endsWith(".") ? 1 : 0); domain = casePreservedType.substring(start, end); application = casePreservedType.substring(0, index - 1); } index = application.toLowerCase().indexOf("._sub"); if (index > 0) { int start = index + 5; subtype = removeSeparators(application.substring(0, index)); application = application.substring(start); } } final Map qualifiedNameMap = new HashMap(5); qualifiedNameMap.put(Fields.Domain, removeSeparators(domain)); qualifiedNameMap.put(Fields.Protocol, protocol); qualifiedNameMap.put(Fields.Application, removeSeparators(application)); qualifiedNameMap.put(Fields.Instance, name); qualifiedNameMap.put(Fields.Subtype, subtype); return qualifiedNameMap; } protected static Map checkQualifiedNameMap(Map qualifiedNameMap) { Map checkedQualifiedNameMap = new HashMap(5); // Optional domain String domain = (qualifiedNameMap.containsKey(Fields.Domain) ? qualifiedNameMap.get(Fields.Domain) : "local"); if ((domain == null) || (domain.length() == 0)) { domain = "local"; } domain = removeSeparators(domain); checkedQualifiedNameMap.put(Fields.Domain, domain); // Optional protocol String protocol = (qualifiedNameMap.containsKey(Fields.Protocol) ? qualifiedNameMap.get(Fields.Protocol) : "tcp"); if ((protocol == null) || (protocol.length() == 0)) { protocol = "tcp"; } protocol = removeSeparators(protocol); checkedQualifiedNameMap.put(Fields.Protocol, protocol); // Application String application = (qualifiedNameMap.containsKey(Fields.Application) ? qualifiedNameMap.get(Fields.Application) : ""); if ((application == null) || (application.length() == 0)) { application = ""; } application = removeSeparators(application); checkedQualifiedNameMap.put(Fields.Application, application); // Instance String instance = (qualifiedNameMap.containsKey(Fields.Instance) ? qualifiedNameMap.get(Fields.Instance) : ""); if ((instance == null) || (instance.length() == 0)) { instance = ""; // throw new IllegalArgumentException("The instance name component of a fully qualified service cannot be empty."); } instance = removeSeparators(instance); checkedQualifiedNameMap.put(Fields.Instance, instance); // Optional Subtype String subtype = (qualifiedNameMap.containsKey(Fields.Subtype) ? qualifiedNameMap.get(Fields.Subtype) : ""); if ((subtype == null) || (subtype.length() == 0)) { subtype = ""; } subtype = removeSeparators(subtype); checkedQualifiedNameMap.put(Fields.Subtype, subtype); return checkedQualifiedNameMap; } private static String removeSeparators(String name) { if (name == null) { return ""; } String newName = name.trim(); if (newName.startsWith(".")) { newName = newName.substring(1); } if (newName.startsWith("_")) { newName = newName.substring(1); } if (newName.endsWith(".")) { newName = newName.substring(0, newName.length() - 1); } return newName; } /** * {@inheritDoc} */ @Override public String getType() { String domain = this.getDomain(); String protocol = this.getProtocol(); String application = this.getApplication(); return (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; } /** * {@inheritDoc} */ @Override public String getTypeWithSubtype() { String subtype = this.getSubtype(); return (subtype.length() > 0 ? "_" + subtype.toLowerCase() + "._sub." : "") + this.getType(); } /** * {@inheritDoc} */ @Override public String getName() { return (_name != null ? _name : ""); } /** * {@inheritDoc} */ @Override public String getKey() { if (this._key == null) { this._key = this.getQualifiedName().toLowerCase(); } return this._key; } /** * Sets the service instance name. * * @param name * unqualified service instance name, such as foobar */ void setName(String name) { this._name = name; this._key = null; } /** * {@inheritDoc} */ @Override public String getQualifiedName() { String domain = this.getDomain(); String protocol = this.getProtocol(); String application = this.getApplication(); String instance = this.getName(); // String subtype = this.getSubtype(); // return (instance.length() > 0 ? instance + "." : "") + (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + (subtype.length() > 0 ? ",_" + subtype.toLowerCase() + "." : ".") : "") + domain // + "."; return (instance.length() > 0 ? instance + "." : "") + (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; } /** * @see javax.jmdns.ServiceInfo#getServer() */ @Override public String getServer() { return (_server != null ? _server : ""); } /** * @param server * the server to set */ void setServer(String server) { this._server = server; } /** * {@inheritDoc} */ @Deprecated @Override public String getHostAddress() { String[] names = this.getHostAddresses(); return (names.length > 0 ? names[0] : ""); } /** * {@inheritDoc} */ @Override public String[] getHostAddresses() { Inet4Address[] ip4Aaddresses = this.getInet4Addresses(); Inet6Address[] ip6Aaddresses = this.getInet6Addresses(); String[] names = new String[ip4Aaddresses.length + ip6Aaddresses.length]; for (int i = 0; i < ip4Aaddresses.length; i++) { names[i] = ip4Aaddresses[i].getHostAddress(); } for (int i = 0; i < ip6Aaddresses.length; i++) { names[i + ip4Aaddresses.length] = "[" + ip6Aaddresses[i].getHostAddress() + "]"; } return names; } /** * @param addr * the addr to add */ void addAddress(Inet4Address addr) { _ipv4Addresses.add(addr); } /** * @param addr * the addr to add */ void addAddress(Inet6Address addr) { _ipv6Addresses.add(addr); } /** * {@inheritDoc} */ @Deprecated @Override public InetAddress getAddress() { return this.getInetAddress(); } /** * {@inheritDoc} */ @Deprecated @Override public InetAddress getInetAddress() { InetAddress[] addresses = this.getInetAddresses(); return (addresses.length > 0 ? addresses[0] : null); } /** * {@inheritDoc} */ @Deprecated @Override public Inet4Address getInet4Address() { Inet4Address[] addresses = this.getInet4Addresses(); return (addresses.length > 0 ? addresses[0] : null); } /** * {@inheritDoc} */ @Deprecated @Override public Inet6Address getInet6Address() { Inet6Address[] addresses = this.getInet6Addresses(); return (addresses.length > 0 ? addresses[0] : null); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getInetAddresses() */ @Override public InetAddress[] getInetAddresses() { List aList = new ArrayList(_ipv4Addresses.size() + _ipv6Addresses.size()); aList.addAll(_ipv4Addresses); aList.addAll(_ipv6Addresses); return aList.toArray(new InetAddress[aList.size()]); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getInet4Addresses() */ @Override public Inet4Address[] getInet4Addresses() { return _ipv4Addresses.toArray(new Inet4Address[_ipv4Addresses.size()]); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getInet6Addresses() */ @Override public Inet6Address[] getInet6Addresses() { return _ipv6Addresses.toArray(new Inet6Address[_ipv6Addresses.size()]); } /** * @see javax.jmdns.ServiceInfo#getPort() */ @Override public int getPort() { return _port; } /** * @see javax.jmdns.ServiceInfo#getPriority() */ @Override public int getPriority() { return _priority; } /** * @see javax.jmdns.ServiceInfo#getWeight() */ @Override public int getWeight() { return _weight; } /** * @see javax.jmdns.ServiceInfo#getTextBytes() */ @Override public byte[] getTextBytes() { return (this._text != null && this._text.length > 0 ? this._text : DNSRecord.EMPTY_TXT); } /** * {@inheritDoc} */ @Deprecated @Override public String getTextString() { Map properties = this.getProperties(); for (String key : properties.keySet()) { byte[] value = properties.get(key); if ((value != null) && (value.length > 0)) { return key + "=" + new String(value); } return key; } return ""; } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getURL() */ @Deprecated @Override public String getURL() { return this.getURL("http"); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getURLs() */ @Override public String[] getURLs() { return this.getURLs("http"); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getURL(java.lang.String) */ @Deprecated @Override public String getURL(String protocol) { String[] urls = this.getURLs(protocol); return (urls.length > 0 ? urls[0] : protocol + "://null:" + getPort()); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#getURLs(java.lang.String) */ @Override public String[] getURLs(String protocol) { InetAddress[] addresses = this.getInetAddresses(); String[] urls = new String[addresses.length]; for (int i = 0; i < addresses.length; i++) { String url = protocol + "://" + addresses[i].getHostAddress() + ":" + getPort(); String path = getPropertyString("path"); if (path != null) { if (path.indexOf("://") >= 0) { url = path; } else { url += path.startsWith("/") ? path : "/" + path; } } urls[i] = url; } return urls; } /** * {@inheritDoc} */ @Override public synchronized byte[] getPropertyBytes(String name) { return this.getProperties().get(name); } /** * {@inheritDoc} */ @Override public synchronized String getPropertyString(String name) { byte data[] = this.getProperties().get(name); if (data == null) { return null; } if (data == NO_VALUE) { return "true"; } return readUTF(data, 0, data.length); } /** * {@inheritDoc} */ @Override public Enumeration getPropertyNames() { Map properties = this.getProperties(); Collection names = (properties != null ? properties.keySet() : Collections. emptySet()); return new Vector(names).elements(); } /** * {@inheritDoc} */ @Override public String getApplication() { return (_application != null ? _application : ""); } /** * {@inheritDoc} */ @Override public String getDomain() { return (_domain != null ? _domain : "local"); } /** * {@inheritDoc} */ @Override public String getProtocol() { return (_protocol != null ? _protocol : "tcp"); } /** * {@inheritDoc} */ @Override public String getSubtype() { return (_subtype != null ? _subtype : ""); } /** * {@inheritDoc} */ @Override public Map getQualifiedNameMap() { Map map = new HashMap(5); map.put(Fields.Domain, this.getDomain()); map.put(Fields.Protocol, this.getProtocol()); map.put(Fields.Application, this.getApplication()); map.put(Fields.Instance, this.getName()); map.put(Fields.Subtype, this.getSubtype()); return map; } /** * Write a UTF string with a length to a stream. */ static 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) { int offset = off; StringBuffer buf = new StringBuffer(); for (int end = offset + len; offset < end;) { int ch = data[offset++] & 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 (offset >= len) { return null; } // 110x xxxx 10xx xxxx ch = ((ch & 0x1F) << 6) | (data[offset++] & 0x3F); break; case 14: if (offset + 2 >= len) { return null; } // 1110 xxxx 10xx xxxx 10xx xxxx ch = ((ch & 0x0f) << 12) | ((data[offset++] & 0x3F) << 6) | (data[offset++] & 0x3F); break; default: if (offset + 1 >= len) { return null; } // 10xx xxxx, 1111 xxxx ch = ((ch & 0x3F) << 4) | (data[offset++] & 0x0f); break; } buf.append((char) ch); } return buf.toString(); } synchronized Map getProperties() { if ((_props == null) && (this.getTextBytes() != null)) { Hashtable properties = new Hashtable(); try { int off = 0; while (off < getTextBytes().length) { // length of the next key value pair int len = getTextBytes()[off++] & 0xFF; if ((len == 0) || (off + len > getTextBytes().length)) { properties.clear(); break; } // look for the '=' int i = 0; for (; (i < len) && (getTextBytes()[off + i] != '='); i++) { /* Stub */ } // get the property name String name = readUTF(getTextBytes(), off, i); if (name == null) { properties.clear(); break; } if (i == len) { properties.put(name, NO_VALUE); } else { byte value[] = new byte[len - ++i]; System.arraycopy(getTextBytes(), off + i, value, 0, len - i); properties.put(name, value); off += len; } } } catch (Exception exception) { // We should get better logging. logger.log(Level.WARNING, "Malformed TXT Field ", exception); } this._props = properties; } return (_props != null ? _props : Collections. emptyMap()); } /** * JmDNS callback to update a DNS record. * * @param dnsCache * @param now * @param rec */ @Override public void updateRecord(DNSCache dnsCache, long now, DNSEntry rec) { if ((rec instanceof DNSRecord) && !rec.isExpired(now)) { boolean serviceUpdated = false; switch (rec.getRecordType()) { case TYPE_A: // IPv4 if (rec.getName().equalsIgnoreCase(this.getServer())) { _ipv4Addresses.add((Inet4Address) ((DNSRecord.Address) rec).getAddress()); serviceUpdated = true; } break; case TYPE_AAAA: // IPv6 if (rec.getName().equalsIgnoreCase(this.getServer())) { _ipv6Addresses.add((Inet6Address) ((DNSRecord.Address) rec).getAddress()); serviceUpdated = true; } break; case TYPE_SRV: if (rec.getName().equalsIgnoreCase(this.getQualifiedName())) { DNSRecord.Service srv = (DNSRecord.Service) rec; boolean serverChanged = (_server == null) || !_server.equalsIgnoreCase(srv.getServer()); _server = srv.getServer(); _port = srv.getPort(); _weight = srv.getWeight(); _priority = srv.getPriority(); if (serverChanged) { _ipv4Addresses.clear(); _ipv6Addresses.clear(); for (DNSEntry entry : dnsCache.getDNSEntryList(_server, DNSRecordType.TYPE_A, DNSRecordClass.CLASS_IN)) { this.updateRecord(dnsCache, now, entry); } for (DNSEntry entry : dnsCache.getDNSEntryList(_server, DNSRecordType.TYPE_AAAA, DNSRecordClass.CLASS_IN)) { this.updateRecord(dnsCache, now, entry); } // We do not want to trigger the listener in this case as it will be triggered if the address resolves. } else { serviceUpdated = true; } } break; case TYPE_TXT: if (rec.getName().equalsIgnoreCase(this.getQualifiedName())) { DNSRecord.Text txt = (DNSRecord.Text) rec; _text = txt.getText(); serviceUpdated = true; } break; case TYPE_PTR: if ((this.getSubtype().length() == 0) && (rec.getSubtype().length() != 0)) { _subtype = rec.getSubtype(); serviceUpdated = true; } break; default: break; } if (serviceUpdated && this.hasData()) { JmDNSImpl dns = this.getDns(); if (dns != null) { ServiceEvent event = ((DNSRecord) rec).getServiceEvent(dns); event = new ServiceEventImpl(dns, event.getType(), event.getName(), this); dns.handleServiceResolved(event); } } // This is done, to notify the wait loop in method JmDNS.waitForInfoData(ServiceInfo info, int timeout); synchronized (this) { this.notifyAll(); } } } /** * Returns true if the service info is filled with data. * * @return true if the service info has data, false otherwise. */ @Override public synchronized boolean hasData() { return this.getServer() != null && this.hasInetAddress() && this.getTextBytes() != null && this.getTextBytes().length > 0; // return this.getServer() != null && (this.getAddress() != null || (this.getTextBytes() != null && this.getTextBytes().length > 0)); } private final boolean hasInetAddress() { return _ipv4Addresses.size() > 0 || _ipv6Addresses.size() > 0; } // State machine /** * {@inheritDoc} */ @Override public boolean advanceState(DNSTask task) { return _state.advanceState(task); } /** * {@inheritDoc} */ @Override public boolean revertState() { return _state.revertState(); } /** * {@inheritDoc} */ @Override public boolean cancelState() { return _state.cancelState(); } /** * {@inheritDoc} */ @Override public boolean closeState() { return this._state.closeState(); } /** * {@inheritDoc} */ @Override public boolean recoverState() { return this._state.recoverState(); } /** * {@inheritDoc} */ @Override public void removeAssociationWithTask(DNSTask task) { _state.removeAssociationWithTask(task); } /** * {@inheritDoc} */ @Override public void associateWithTask(DNSTask task, DNSState state) { _state.associateWithTask(task, state); } /** * {@inheritDoc} */ @Override public boolean isAssociatedWithTask(DNSTask task, DNSState state) { return _state.isAssociatedWithTask(task, state); } /** * {@inheritDoc} */ @Override public boolean isProbing() { return _state.isProbing(); } /** * {@inheritDoc} */ @Override public boolean isAnnouncing() { return _state.isAnnouncing(); } /** * {@inheritDoc} */ @Override public boolean isAnnounced() { return _state.isAnnounced(); } /** * {@inheritDoc} */ @Override public boolean isCanceling() { return this._state.isCanceling(); } /** * {@inheritDoc} */ @Override public boolean isCanceled() { return _state.isCanceled(); } /** * {@inheritDoc} */ @Override public boolean isClosing() { return _state.isClosing(); } /** * {@inheritDoc} */ @Override public boolean isClosed() { return _state.isClosed(); } /** * {@inheritDoc} */ @Override public boolean waitForAnnounced(long timeout) { return _state.waitForAnnounced(timeout); } /** * {@inheritDoc} */ @Override public boolean waitForCanceled(long timeout) { return _state.waitForCanceled(timeout); } /** * {@inheritDoc} */ @Override public int hashCode() { return getQualifiedName().hashCode(); } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { return (obj instanceof ServiceInfoImpl) && getQualifiedName().equals(((ServiceInfoImpl) obj).getQualifiedName()); } /** * {@inheritDoc} */ @Override public String getNiceTextString() { StringBuffer buf = new StringBuffer(); for (int i = 0, len = this.getTextBytes().length; i < len; i++) { if (i >= 200) { buf.append("..."); break; } int ch = getTextBytes()[i] & 0xFF; if ((ch < ' ') || (ch > 127)) { buf.append("\\0"); buf.append(Integer.toString(ch, 8)); } else { buf.append((char) ch); } } return buf.toString(); } /* * (non-Javadoc) * @see javax.jmdns.ServiceInfo#clone() */ @Override public ServiceInfoImpl clone() { ServiceInfoImpl serviceInfo = new ServiceInfoImpl(this.getQualifiedNameMap(), _port, _weight, _priority, _persistent, _text); Inet6Address[] ipv6Addresses = this.getInet6Addresses(); for (Inet6Address address : ipv6Addresses) { serviceInfo._ipv6Addresses.add(address); } Inet4Address[] ipv4Addresses = this.getInet4Addresses(); for (Inet4Address address : ipv4Addresses) { serviceInfo._ipv4Addresses.add(address); } return serviceInfo; } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("[" + this.getClass().getSimpleName() + "@" + System.identityHashCode(this) + " "); buf.append("name: '"); buf.append((this.getName().length() > 0 ? this.getName() + "." : "") + this.getTypeWithSubtype()); buf.append("' address: '"); InetAddress[] addresses = this.getInetAddresses(); if (addresses.length > 0) { for (InetAddress address : addresses) { buf.append(address); buf.append(':'); buf.append(this.getPort()); buf.append(' '); } } else { buf.append("(null):"); buf.append(this.getPort()); } buf.append("' status: '"); buf.append(_state.toString()); buf.append(this.isPersistent() ? "' is persistent," : "',"); buf.append(" has "); buf.append(this.hasData() ? "" : "NO "); buf.append("data"); if (this.getTextBytes().length > 0) { // buf.append("\n"); // buf.append(this.getNiceTextString()); Map properties = this.getProperties(); if (!properties.isEmpty()) { buf.append("\n"); for (String key : properties.keySet()) { buf.append("\t" + key + ": " + new String(properties.get(key)) + "\n"); } } else { buf.append(" empty"); } } buf.append(']'); return buf.toString(); } public Collection answers(boolean unique, int ttl, HostInfo localHost) { List list = new ArrayList(); if (this.getSubtype().length() > 0) { list.add(new Pointer(this.getTypeWithSubtype(), DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, ttl, this.getQualifiedName())); } list.add(new Pointer(this.getType(), DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, ttl, this.getQualifiedName())); list.add(new Service(this.getQualifiedName(), DNSRecordClass.CLASS_IN, unique, ttl, _priority, _weight, _port, localHost.getName())); list.add(new Text(this.getQualifiedName(), DNSRecordClass.CLASS_IN, unique, ttl, this.getTextBytes())); return list; } /** * {@inheritDoc} */ @Override public void setText(byte[] text) throws IllegalStateException { synchronized (this) { this._text = text; this._props = null; this.setNeedTextAnnouncing(true); } } /** * {@inheritDoc} */ @Override public void setText(Map props) throws IllegalStateException { this.setText(textFromProperties(props)); } /** * This is used internally by the framework * * @param text */ void _setText(byte[] text) { this._text = text; this._props = null; } private static byte[] textFromProperties(Map props) { byte[] text = null; if (props != null) { try { ByteArrayOutputStream out = new ByteArrayOutputStream(256); for (String key : props.keySet()) { Object val = props.get(key); ByteArrayOutputStream out2 = new ByteArrayOutputStream(100); writeUTF(out2, key); if (val == null) { // Skip } else if (val instanceof String) { out2.write('='); writeUTF(out2, (String) val); } else if (val instanceof byte[]) { byte[] bval = (byte[]) val; if (bval.length > 0) { out2.write('='); out2.write(bval, 0, bval.length); } else { val = null; } } else { throw new IllegalArgumentException("invalid property value: " + val); } byte data[] = out2.toByteArray(); if (data.length > 255) { throw new IOException("Cannot have individual values larger that 255 chars. Offending value: " + key + (val != null ? "" : "=" + val)); } out.write((byte) data.length); out.write(data, 0, data.length); } text = out.toByteArray(); } catch (IOException e) { throw new RuntimeException("unexpected exception: " + e); } } return (text != null && text.length > 0 ? text : DNSRecord.EMPTY_TXT); } public void setDns(JmDNSImpl dns) { this._state.setDns(dns); } /** * {@inheritDoc} */ @Override public JmDNSImpl getDns() { return this._state.getDns(); } /** * {@inheritDoc} */ @Override public boolean isPersistent() { return _persistent; } /** * @param needTextAnnouncing * the needTextAnnouncing to set */ public void setNeedTextAnnouncing(boolean needTextAnnouncing) { this._needTextAnnouncing = needTextAnnouncing; if (this._needTextAnnouncing) { _state.setTask(null); } } /** * @return the needTextAnnouncing */ public boolean needTextAnnouncing() { return _needTextAnnouncing; } /** * @return the delegate */ Delegate getDelegate() { return this._delegate; } /** * @param delegate * the delegate to set */ void setDelegate(Delegate delegate) { this._delegate = delegate; } } jmdns-3.4.1/src/javax/jmdns/impl/DNSListener.java0000644000175000017500000000124211625404056021457 0ustar mathieumathieu// 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. * * @param dnsCache * record cache * @param now * update date * @param record * DNS record */ void updateRecord(DNSCache dnsCache, long now, DNSEntry record); } jmdns-3.4.1/src/javax/jmdns/impl/DNSCache.java0000644000175000017500000003677611625404056020721 0ustar mathieumathieu// 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.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; /** * A table of DNS entries. This is a map table which can handle multiple entries with the same name. *

* Storing multiple entries with the same name is implemented using a linked list. This is hidden from the user and can change in later implementation. *

* Here's how to iterate over all entries: * *

 *       for (Iterator i=dnscache.allValues().iterator(); i.hasNext(); ) {
 *             DNSEntry entry = i.next();
 *             ...do something with entry...
 *       }
 * 
*

* And here's how to iterate over all entries having a given name: * *

 *       for (Iterator i=dnscache.getDNSEntryList(name).iterator(); i.hasNext(); ) {
 *             DNSEntry entry = i.next();
 *           ...do something with entry...
 *       }
 * 
* * @author Arthur van Hoff, Werner Randelshofer, Rick Blair, Pierre Frisch */ public class DNSCache extends AbstractMap> { // private static Logger logger = Logger.getLogger(DNSCache.class.getName()); private transient Set>> _entrySet = null; /** * */ public static final DNSCache EmptyCache = new _EmptyCache(); static final class _EmptyCache extends DNSCache { /** * {@inheritDoc} */ @Override public int size() { return 0; } /** * {@inheritDoc} */ @Override public boolean isEmpty() { return true; } /** * {@inheritDoc} */ @Override public boolean containsKey(Object key) { return false; } /** * {@inheritDoc} */ @Override public boolean containsValue(Object value) { return false; } /** * {@inheritDoc} */ @Override public List get(Object key) { return null; } /** * {@inheritDoc} */ @Override public Set keySet() { return Collections.emptySet(); } /** * {@inheritDoc} */ @Override public Collection> values() { return Collections.emptySet(); } /** * {@inheritDoc} */ @Override public Set>> entrySet() { return Collections.emptySet(); } /** * {@inheritDoc} */ @Override public boolean equals(Object o) { return (o instanceof Map) && ((Map) o).size() == 0; } /** * {@inheritDoc} */ @Override public List put(String key, List value) { return null; } /** * {@inheritDoc} */ @Override public int hashCode() { return 0; } } /** * */ protected static class _CacheEntry extends Object implements Map.Entry> { private List _value; private String _key; /** * @param key * @param value */ protected _CacheEntry(String key, List value) { super(); _key = (key != null ? key.trim().toLowerCase() : null); _value = value; } /** * @param entry */ protected _CacheEntry(Map.Entry> entry) { super(); if (entry instanceof _CacheEntry) { _key = ((_CacheEntry) entry).getKey(); _value = ((_CacheEntry) entry).getValue(); } } /** * {@inheritDoc} */ @Override public String getKey() { return (_key != null ? _key : ""); } /** * {@inheritDoc} */ @Override public List getValue() { return _value; } /** * {@inheritDoc} */ @Override public List setValue(List value) { List oldValue = _value; _value = value; return oldValue; } /** * Returns true if this list contains no elements. * * @return true if this list contains no elements */ public boolean isEmpty() { return this.getValue().isEmpty(); } /** * {@inheritDoc} */ @Override public boolean equals(Object entry) { if (!(entry instanceof Map.Entry)) { return false; } return this.getKey().equals(((Map.Entry) entry).getKey()) && this.getValue().equals(((Map.Entry) entry).getValue()); } /** * {@inheritDoc} */ @Override public int hashCode() { return (_key == null ? 0 : _key.hashCode()); } /** * {@inheritDoc} */ @Override public synchronized String toString() { StringBuffer aLog = new StringBuffer(200); aLog.append("\n\t\tname '"); aLog.append(_key); aLog.append("' "); if ((_value != null) && (!_value.isEmpty())) { for (DNSEntry entry : _value) { aLog.append("\n\t\t\t"); aLog.append(entry.toString()); } } else { aLog.append(" no entries"); } return aLog.toString(); } } /** * */ public DNSCache() { this(1024); } /** * @param map */ public DNSCache(DNSCache map) { this(map != null ? map.size() : 1024); if (map != null) { this.putAll(map); } } /** * Create a table with a given initial size. * * @param initialCapacity */ public DNSCache(int initialCapacity) { super(); _entrySet = new HashSet>>(initialCapacity); } // ==================================================================== // Map /* * (non-Javadoc) * @see java.util.AbstractMap#entrySet() */ @Override public Set>> entrySet() { if (_entrySet == null) { _entrySet = new HashSet>>(); } return _entrySet; } /** * @param key * @return map entry for the key */ protected Map.Entry> getEntry(String key) { String stringKey = (key != null ? key.trim().toLowerCase() : null); for (Map.Entry> entry : this.entrySet()) { if (stringKey != null) { if (stringKey.equals(entry.getKey())) { return entry; } } else { if (entry.getKey() == null) { return entry; } } } return null; } /** * {@inheritDoc} */ @Override public List put(String key, List value) { synchronized (this) { List oldValue = null; Map.Entry> oldEntry = this.getEntry(key); if (oldEntry != null) { oldValue = oldEntry.setValue(value); } else { this.entrySet().add(new _CacheEntry(key, value)); } return oldValue; } } /** * {@inheritDoc} */ @Override protected Object clone() throws CloneNotSupportedException { return new DNSCache(this); } // ==================================================================== /** * Returns all entries in the cache * * @return all entries in the cache */ public synchronized Collection allValues() { List allValues = new ArrayList(); for (List entry : this.values()) { if (entry != null) { allValues.addAll(entry); } } return allValues; } /** * Iterate only over items with matching name. Returns an list of DNSEntry or null. To retrieve all entries, one must iterate over this linked list. * * @param name * @return list of DNSEntries */ public synchronized Collection getDNSEntryList(String name) { Collection entryList = this._getDNSEntryList(name); if (entryList != null) { entryList = new ArrayList(entryList); } else { entryList = Collections.emptyList(); } return entryList; } private Collection _getDNSEntryList(String name) { return this.get(name != null ? name.toLowerCase() : null); } /** * Get a matching DNS entry from the table (using isSameEntry). Returns the entry that was found. * * @param dnsEntry * @return DNSEntry */ public synchronized DNSEntry getDNSEntry(DNSEntry dnsEntry) { DNSEntry result = null; if (dnsEntry != null) { Collection entryList = this._getDNSEntryList(dnsEntry.getKey()); if (entryList != null) { for (DNSEntry testDNSEntry : entryList) { if (testDNSEntry.isSameEntry(dnsEntry)) { result = testDNSEntry; break; } } } } return result; } /** * Get a matching DNS entry from the table. * * @param name * @param type * @param recordClass * @return DNSEntry */ public synchronized DNSEntry getDNSEntry(String name, DNSRecordType type, DNSRecordClass recordClass) { DNSEntry result = null; Collection entryList = this._getDNSEntryList(name); if (entryList != null) { for (DNSEntry testDNSEntry : entryList) { if (testDNSEntry.getRecordType().equals(type) && ((DNSRecordClass.CLASS_ANY == recordClass) || testDNSEntry.getRecordClass().equals(recordClass))) { result = testDNSEntry; break; } } } return result; } /** * Get all matching DNS entries from the table. * * @param name * @param type * @param recordClass * @return list of entries */ public synchronized Collection getDNSEntryList(String name, DNSRecordType type, DNSRecordClass recordClass) { Collection entryList = this._getDNSEntryList(name); if (entryList != null) { entryList = new ArrayList(entryList); for (Iterator i = entryList.iterator(); i.hasNext();) { DNSEntry testDNSEntry = i.next(); if (!testDNSEntry.getRecordType().equals(type) || ((DNSRecordClass.CLASS_ANY != recordClass) && !testDNSEntry.getRecordClass().equals(recordClass))) { i.remove(); } } } else { entryList = Collections.emptyList(); } return entryList; } /** * Adds an entry to the table. * * @param dnsEntry * @return true if the entry was added */ public synchronized boolean addDNSEntry(final DNSEntry dnsEntry) { boolean result = false; if (dnsEntry != null) { Map.Entry> oldEntry = this.getEntry(dnsEntry.getKey()); List aNewValue = null; if (oldEntry != null) { aNewValue = new ArrayList(oldEntry.getValue()); } else { aNewValue = new ArrayList(); } aNewValue.add(dnsEntry); if (oldEntry != null) { oldEntry.setValue(aNewValue); } else { this.entrySet().add(new _CacheEntry(dnsEntry.getKey(), aNewValue)); } // This is probably not very informative result = true; } return result; } /** * Removes a specific entry from the table. Returns true if the entry was found. * * @param dnsEntry * @return true if the entry was removed */ public synchronized boolean removeDNSEntry(DNSEntry dnsEntry) { boolean result = false; if (dnsEntry != null) { Map.Entry> existingEntry = this.getEntry(dnsEntry.getKey()); if (existingEntry != null) { result = existingEntry.getValue().remove(dnsEntry); // If we just removed the last one we need to get rid of the entry if (existingEntry.getValue().isEmpty()) { this.entrySet().remove(existingEntry); } } } return result; } /** * Replace an existing entry by a new one.
* Note: the 2 entries must have the same key. * * @param newDNSEntry * @param existingDNSEntry * @return true if the entry has been replace, false otherwise. */ public synchronized boolean replaceDNSEntry(DNSEntry newDNSEntry, DNSEntry existingDNSEntry) { boolean result = false; if ((newDNSEntry != null) && (existingDNSEntry != null) && (newDNSEntry.getKey().equals(existingDNSEntry.getKey()))) { Map.Entry> oldEntry = this.getEntry(newDNSEntry.getKey()); List aNewValue = null; if (oldEntry != null) { aNewValue = new ArrayList(oldEntry.getValue()); } else { aNewValue = new ArrayList(); } aNewValue.remove(existingDNSEntry); aNewValue.add(newDNSEntry); if (oldEntry != null) { oldEntry.setValue(aNewValue); } else { this.entrySet().add(new _CacheEntry(newDNSEntry.getKey(), aNewValue)); } // This is probably not very informative result = true; } return result; } /** * {@inheritDoc} */ @Override public synchronized String toString() { StringBuffer aLog = new StringBuffer(2000); aLog.append("\t---- cache ----"); for (Map.Entry> entry : this.entrySet()) { aLog.append("\n\t\t"); aLog.append(entry.toString()); } return aLog.toString(); } } jmdns-3.4.1/src/javax/jmdns/impl/DNSOutgoing.java0000644000175000017500000003504411625404056021474 0ustar mathieumathieu// 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.util.HashMap; import java.util.Map; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSRecordClass; /** * An outgoing DNS message. * * @author Arthur van Hoff, Rick Blair, Werner Randelshofer */ public final class DNSOutgoing extends DNSMessage { public static class MessageOutputStream extends ByteArrayOutputStream { private final DNSOutgoing _out; private final int _offset; /** * Creates a new message stream, with a buffer capacity of the specified size, in bytes. * * @param size * the initial size. * @exception IllegalArgumentException * if size is negative. */ MessageOutputStream(int size, DNSOutgoing out) { this(size, out, 0); } MessageOutputStream(int size, DNSOutgoing out, int offset) { super(size); _out = out; _offset = offset; } void writeByte(int value) { this.write(value & 0xFF); } void writeBytes(String str, int off, int len) { for (int i = 0; i < len; i++) { writeByte(str.charAt(off + i)); } } void writeBytes(byte data[]) { if (data != null) { writeBytes(data, 0, data.length); } } void writeBytes(byte data[], int off, int len) { for (int i = 0; i < len; i++) { writeByte(data[off + i]); } } void writeShort(int value) { writeByte(value >> 8); writeByte(value); } void writeInt(int value) { writeShort(value >> 16); writeShort(value); } void writeUTF(String str, int off, int len) { // 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) { writeName(name, true); } void writeName(String name, boolean useCompression) { String aName = name; while (true) { int n = aName.indexOf('.'); if (n < 0) { n = aName.length(); } if (n <= 0) { writeByte(0); return; } String label = aName.substring(0, n); if (useCompression && USE_DOMAIN_NAME_COMPRESSION) { Integer offset = _out._names.get(aName); if (offset != null) { int val = offset.intValue(); writeByte((val >> 8) | 0xC0); writeByte(val & 0xFF); return; } _out._names.put(aName, Integer.valueOf(this.size() + _offset)); writeUTF(label, 0, label.length()); } else { writeUTF(label, 0, label.length()); } aName = aName.substring(n); if (aName.startsWith(".")) { aName = aName.substring(1); } } } void writeQuestion(DNSQuestion question) { writeName(question.getName()); writeShort(question.getRecordType().indexValue()); writeShort(question.getRecordClass().indexValue()); } void writeRecord(DNSRecord rec, long now) { writeName(rec.getName()); writeShort(rec.getRecordType().indexValue()); writeShort(rec.getRecordClass().indexValue() | ((rec.isUnique() && _out.isMulticast()) ? DNSRecordClass.CLASS_UNIQUE : 0)); writeInt((now == 0) ? rec.getTTL() : rec.getRemainingTTL(now)); // We need to take into account the 2 size bytes MessageOutputStream record = new MessageOutputStream(512, _out, _offset + this.size() + 2); rec.write(record); byte[] byteArray = record.toByteArray(); writeShort(byteArray.length); write(byteArray, 0, byteArray.length); } } /** * 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; Map _names; private int _maxUDPPayload; private final MessageOutputStream _questionsBytes; private final MessageOutputStream _answersBytes; private final MessageOutputStream _authoritativeAnswersBytes; private final MessageOutputStream _additionalsAnswersBytes; private final static int HEADER_SIZE = 12; /** * Create an outgoing multicast query or response. * * @param flags */ public DNSOutgoing(int flags) { this(flags, true, DNSConstants.MAX_MSG_TYPICAL); } /** * Create an outgoing query or response. * * @param flags * @param multicast */ public DNSOutgoing(int flags, boolean multicast) { this(flags, multicast, DNSConstants.MAX_MSG_TYPICAL); } /** * Create an outgoing query or response. * * @param flags * @param multicast * @param senderUDPPayload * The sender's UDP payload size is the number of bytes of the largest UDP payload that can be reassembled and delivered in the sender's network stack. */ public DNSOutgoing(int flags, boolean multicast, int senderUDPPayload) { super(flags, 0, multicast); _names = new HashMap(); _maxUDPPayload = (senderUDPPayload > 0 ? senderUDPPayload : DNSConstants.MAX_MSG_TYPICAL); _questionsBytes = new MessageOutputStream(senderUDPPayload, this); _answersBytes = new MessageOutputStream(senderUDPPayload, this); _authoritativeAnswersBytes = new MessageOutputStream(senderUDPPayload, this); _additionalsAnswersBytes = new MessageOutputStream(senderUDPPayload, this); } /** * Return the number of byte available in the message. * * @return available space */ public int availableSpace() { return _maxUDPPayload - HEADER_SIZE - _questionsBytes.size() - _answersBytes.size() - _authoritativeAnswersBytes.size() - _additionalsAnswersBytes.size(); } /** * Add a question to the message. * * @param rec * @exception IOException */ public void addQuestion(DNSQuestion rec) throws IOException { MessageOutputStream record = new MessageOutputStream(512, this); record.writeQuestion(rec); byte[] byteArray = record.toByteArray(); if (byteArray.length < this.availableSpace()) { _questions.add(rec); _questionsBytes.write(byteArray, 0, byteArray.length); } else { throw new IOException("message full"); } } /** * Add an answer if it is not suppressed. * * @param in * @param rec * @exception IOException */ public void addAnswer(DNSIncoming in, DNSRecord rec) throws IOException { if ((in == null) || !rec.suppressedBy(in)) { this.addAnswer(rec, 0); } } /** * Add an answer to the message. * * @param rec * @param now * @exception IOException */ public void addAnswer(DNSRecord rec, long now) throws IOException { if (rec != null) { if ((now == 0) || !rec.isExpired(now)) { MessageOutputStream record = new MessageOutputStream(512, this); record.writeRecord(rec, now); byte[] byteArray = record.toByteArray(); if (byteArray.length < this.availableSpace()) { _answers.add(rec); _answersBytes.write(byteArray, 0, byteArray.length); } else { throw new IOException("message full"); } } } } /** * Add an authoritative answer to the message. * * @param rec * @exception IOException */ public void addAuthorativeAnswer(DNSRecord rec) throws IOException { MessageOutputStream record = new MessageOutputStream(512, this); record.writeRecord(rec, 0); byte[] byteArray = record.toByteArray(); if (byteArray.length < this.availableSpace()) { _authoritativeAnswers.add(rec); _authoritativeAnswersBytes.write(byteArray, 0, byteArray.length); } else { throw new IOException("message full"); } } /** * Add an additional answer to the record. Omit if there is no room. * * @param in * @param rec * @exception IOException */ public void addAdditionalAnswer(DNSIncoming in, DNSRecord rec) throws IOException { MessageOutputStream record = new MessageOutputStream(512, this); record.writeRecord(rec, 0); byte[] byteArray = record.toByteArray(); if (byteArray.length < this.availableSpace()) { _additionals.add(rec); _additionalsAnswersBytes.write(byteArray, 0, byteArray.length); } else { throw new IOException("message full"); } } /** * Builds the final message buffer to be send and returns it. * * @return bytes to send. */ public byte[] data() { long now = System.currentTimeMillis(); // System.currentTimeMillis() _names.clear(); MessageOutputStream message = new MessageOutputStream(_maxUDPPayload, this); message.writeShort(_multicast ? 0 : this.getId()); message.writeShort(this.getFlags()); message.writeShort(this.getNumberOfQuestions()); message.writeShort(this.getNumberOfAnswers()); message.writeShort(this.getNumberOfAuthorities()); message.writeShort(this.getNumberOfAdditionals()); for (DNSQuestion question : _questions) { message.writeQuestion(question); } for (DNSRecord record : _answers) { message.writeRecord(record, now); } for (DNSRecord record : _authoritativeAnswers) { message.writeRecord(record, now); } for (DNSRecord record : _additionals) { message.writeRecord(record, now); } return message.toByteArray(); } @Override public boolean isQuery() { return (this.getFlags() & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_QUERY; } /** * Debugging. */ String print(boolean dump) { StringBuilder buf = new StringBuilder(); buf.append(this.print()); if (dump) { buf.append(this.print(this.data())); } return buf.toString(); } @Override public String toString() { StringBuffer buf = new StringBuffer(); buf.append(isQuery() ? "dns[query:" : "dns[response:"); buf.append(" id=0x"); buf.append(Integer.toHexString(this.getId())); if (this.getFlags() != 0) { buf.append(", flags=0x"); buf.append(Integer.toHexString(this.getFlags())); if ((this.getFlags() & DNSConstants.FLAGS_QR_RESPONSE) != 0) { buf.append(":r"); } if ((this.getFlags() & DNSConstants.FLAGS_AA) != 0) { buf.append(":aa"); } if ((this.getFlags() & DNSConstants.FLAGS_TC) != 0) { buf.append(":tc"); } } if (this.getNumberOfQuestions() > 0) { buf.append(", questions="); buf.append(this.getNumberOfQuestions()); } if (this.getNumberOfAnswers() > 0) { buf.append(", answers="); buf.append(this.getNumberOfAnswers()); } if (this.getNumberOfAuthorities() > 0) { buf.append(", authorities="); buf.append(this.getNumberOfAuthorities()); } if (this.getNumberOfAdditionals() > 0) { buf.append(", additionals="); buf.append(this.getNumberOfAdditionals()); } if (this.getNumberOfQuestions() > 0) { buf.append("\nquestions:"); for (DNSQuestion question : _questions) { buf.append("\n\t"); buf.append(question); } } if (this.getNumberOfAnswers() > 0) { buf.append("\nanswers:"); for (DNSRecord record : _answers) { buf.append("\n\t"); buf.append(record); } } if (this.getNumberOfAuthorities() > 0) { buf.append("\nauthorities:"); for (DNSRecord record : _authoritativeAnswers) { buf.append("\n\t"); buf.append(record); } } if (this.getNumberOfAdditionals() > 0) { buf.append("\nadditionals:"); for (DNSRecord record : _additionals) { buf.append("\n\t"); buf.append(record); } } buf.append("\nnames="); buf.append(_names); buf.append("]"); return buf.toString(); } /** * @return the maxUDPPayload */ public int getMaxUDPPayload() { return this._maxUDPPayload; } } jmdns-3.4.1/src/javax/jmdns/impl/NetworkTopologyEventImpl.java0000644000175000017500000000404411625404056024342 0ustar mathieumathieu/** * */ package javax.jmdns.impl; import java.net.InetAddress; import javax.jmdns.JmDNS; import javax.jmdns.NetworkTopologyEvent; import javax.jmdns.NetworkTopologyListener; /** * @author Cédrik Lime, Pierre Frisch */ public class NetworkTopologyEventImpl extends NetworkTopologyEvent implements Cloneable { /** * */ private static final long serialVersionUID = 1445606146153550463L; private final InetAddress _inetAddress; /** * Constructs a Network Topology Event. * * @param jmDNS * @param inetAddress * @exception IllegalArgumentException * if source is null. */ public NetworkTopologyEventImpl(JmDNS jmDNS, InetAddress inetAddress) { super(jmDNS); this._inetAddress = inetAddress; } NetworkTopologyEventImpl(NetworkTopologyListener jmmDNS, InetAddress inetAddress) { super(jmmDNS); this._inetAddress = inetAddress; } /* * (non-Javadoc) * @see javax.jmdns.NetworkTopologyEvent#getDNS() */ @Override public JmDNS getDNS() { return (this.getSource() instanceof JmDNS ? (JmDNS) getSource() : null); } /* * (non-Javadoc) * @see javax.jmdns.NetworkTopologyEvent#getInetAddress() */ @Override public InetAddress getInetAddress() { return _inetAddress; } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("[" + this.getClass().getSimpleName() + "@" + System.identityHashCode(this) + " "); buf.append("\n\tinetAddress: '"); buf.append(this.getInetAddress()); buf.append("']"); // buf.append("' source: "); // buf.append("\n\t" + source + ""); // buf.append("\n]"); return buf.toString(); } /* * (non-Javadoc) * @see java.lang.Object#clone() */ @Override public NetworkTopologyEventImpl clone() throws CloneNotSupportedException { return new NetworkTopologyEventImpl(getDNS(), getInetAddress()); } } jmdns-3.4.1/src/javax/jmdns/impl/HostInfo.java0000644000175000017500000003171211625404056021063 0ustar mathieumathieu// 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.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.NetworkTopologyDiscovery; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; import javax.jmdns.impl.constants.DNSState; import javax.jmdns.impl.tasks.DNSTask; /** * HostInfo information on the local host to be able to cope with change of addresses. * * @author Pierre Frisch, Werner Randelshofer */ public class HostInfo implements DNSStatefulObject { private static Logger logger = Logger.getLogger(HostInfo.class.getName()); protected String _name; protected InetAddress _address; protected NetworkInterface _interfaze; private final HostInfoState _state; private final static class HostInfoState extends DNSStatefulObject.DefaultImplementation { private static final long serialVersionUID = -8191476803620402088L; /** * @param dns */ public HostInfoState(JmDNSImpl dns) { super(); this.setDns(dns); } } /** * @param address * IP address to bind * @param dns * JmDNS instance * @param jmdnsName * JmDNS name * @return new HostInfo */ public static HostInfo newHostInfo(InetAddress address, JmDNSImpl dns, String jmdnsName) { HostInfo localhost = null; String aName = ""; InetAddress addr = address; try { if (addr == null) { String ip = System.getProperty("net.mdns.interface"); if (ip != null) { addr = InetAddress.getByName(ip); } else { addr = InetAddress.getLocalHost(); if (addr.isLoopbackAddress()) { // Find local address that isn't a loopback address InetAddress[] addresses = NetworkTopologyDiscovery.Factory.getInstance().getInetAddresses(); if (addresses.length > 0) { addr = addresses[0]; } } } aName = addr.getHostName(); if (addr.isLoopbackAddress()) { logger.warning("Could not find any address beside the loopback."); } } else { aName = addr.getHostName(); } if (aName.contains("in-addr.arpa") || (aName.equals(addr.getHostAddress()))) { aName = ((jmdnsName != null) && (jmdnsName.length() > 0) ? jmdnsName : addr.getHostAddress()); } } catch (final IOException e) { logger.log(Level.WARNING, "Could not intialize the host network interface on " + address + "because of an error: " + e.getMessage(), e); // This is only used for running unit test on Debian / Ubuntu addr = loopbackAddress(); aName = ((jmdnsName != null) && (jmdnsName.length() > 0) ? jmdnsName : "computer"); } // A host name with "." is illegal. so strip off everything and append .local. aName = aName.replace('.', '-'); aName += ".local."; localhost = new HostInfo(addr, aName, dns); return localhost; } private static InetAddress loopbackAddress() { try { return InetAddress.getByName(null); } catch (UnknownHostException exception) { return null; } } /** * This is used to create a unique name for the host name. */ private int hostNameCount; private HostInfo(final InetAddress address, final String name, final JmDNSImpl dns) { super(); this._state = new HostInfoState(dns); this._address = address; this._name = name; if (address != null) { try { _interfaze = NetworkInterface.getByInetAddress(address); } catch (Exception exception) { logger.log(Level.SEVERE, "LocalHostInfo() exception ", exception); } } } public String getName() { return _name; } public InetAddress getInetAddress() { return _address; } Inet4Address getInet4Address() { if (this.getInetAddress() instanceof Inet4Address) { return (Inet4Address) _address; } return null; } Inet6Address getInet6Address() { if (this.getInetAddress() instanceof Inet6Address) { return (Inet6Address) _address; } return null; } public NetworkInterface getInterface() { return _interfaze; } public boolean conflictWithRecord(DNSRecord.Address record) { DNSRecord.Address hostAddress = this.getDNSAddressRecord(record.getRecordType(), record.isUnique(), DNSConstants.DNS_TTL); if (hostAddress != null) { return hostAddress.sameType(record) && hostAddress.sameName(record) && (!hostAddress.sameValue(record)); } return false; } 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 (this.getInetAddress() != null) { InetAddress from = packet.getAddress(); if (from != null) { if (from.isLinkLocalAddress() && (!this.getInetAddress().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() && (!this.getInetAddress().isLoopbackAddress())) { // Ignore loopback packets on a regular interface unless this is also a loopback interface. result = true; } } } return result; } DNSRecord.Address getDNSAddressRecord(DNSRecordType type, boolean unique, int ttl) { switch (type) { case TYPE_A: return this.getDNS4AddressRecord(unique, ttl); case TYPE_A6: case TYPE_AAAA: return this.getDNS6AddressRecord(unique, ttl); default: } return null; } private DNSRecord.Address getDNS4AddressRecord(boolean unique, int ttl) { if ((this.getInetAddress() instanceof Inet4Address) || ((this.getInetAddress() instanceof Inet6Address) && (((Inet6Address) this.getInetAddress()).isIPv4CompatibleAddress()))) { return new DNSRecord.IPv4Address(this.getName(), DNSRecordClass.CLASS_IN, unique, ttl, this.getInetAddress()); } return null; } private DNSRecord.Address getDNS6AddressRecord(boolean unique, int ttl) { if (this.getInetAddress() instanceof Inet6Address) { return new DNSRecord.IPv6Address(this.getName(), DNSRecordClass.CLASS_IN, unique, ttl, this.getInetAddress()); } return null; } DNSRecord.Pointer getDNSReverseAddressRecord(DNSRecordType type, boolean unique, int ttl) { switch (type) { case TYPE_A: return this.getDNS4ReverseAddressRecord(unique, ttl); case TYPE_A6: case TYPE_AAAA: return this.getDNS6ReverseAddressRecord(unique, ttl); default: } return null; } private DNSRecord.Pointer getDNS4ReverseAddressRecord(boolean unique, int ttl) { if (this.getInetAddress() instanceof Inet4Address) { return new DNSRecord.Pointer(this.getInetAddress().getHostAddress() + ".in-addr.arpa.", DNSRecordClass.CLASS_IN, unique, ttl, this.getName()); } if ((this.getInetAddress() instanceof Inet6Address) && (((Inet6Address) this.getInetAddress()).isIPv4CompatibleAddress())) { byte[] rawAddress = this.getInetAddress().getAddress(); String address = (rawAddress[12] & 0xff) + "." + (rawAddress[13] & 0xff) + "." + (rawAddress[14] & 0xff) + "." + (rawAddress[15] & 0xff); return new DNSRecord.Pointer(address + ".in-addr.arpa.", DNSRecordClass.CLASS_IN, unique, ttl, this.getName()); } return null; } private DNSRecord.Pointer getDNS6ReverseAddressRecord(boolean unique, int ttl) { if (this.getInetAddress() instanceof Inet6Address) { return new DNSRecord.Pointer(this.getInetAddress().getHostAddress() + ".ip6.arpa.", DNSRecordClass.CLASS_IN, unique, ttl, this.getName()); } return null; } @Override public String toString() { StringBuilder buf = new StringBuilder(1024); buf.append("local host info["); buf.append(getName() != null ? getName() : "no name"); buf.append(", "); buf.append(getInterface() != null ? getInterface().getDisplayName() : "???"); buf.append(":"); buf.append(getInetAddress() != null ? getInetAddress().getHostAddress() : "no address"); buf.append(", "); buf.append(_state); buf.append("]"); return buf.toString(); } public Collection answers(boolean unique, int ttl) { List list = new ArrayList(); DNSRecord answer = this.getDNS4AddressRecord(unique, ttl); if (answer != null) { list.add(answer); } answer = this.getDNS6AddressRecord(unique, ttl); if (answer != null) { list.add(answer); } return list; } /** * {@inheritDoc} */ @Override public JmDNSImpl getDns() { return this._state.getDns(); } /** * {@inheritDoc} */ @Override public boolean advanceState(DNSTask task) { return this._state.advanceState(task); } /** * {@inheritDoc} */ @Override public void removeAssociationWithTask(DNSTask task) { this._state.removeAssociationWithTask(task); } /** * {@inheritDoc} */ @Override public boolean revertState() { return this._state.revertState(); } /** * {@inheritDoc} */ @Override public void associateWithTask(DNSTask task, DNSState state) { this._state.associateWithTask(task, state); } /** * {@inheritDoc} */ @Override public boolean isAssociatedWithTask(DNSTask task, DNSState state) { return this._state.isAssociatedWithTask(task, state); } /** * {@inheritDoc} */ @Override public boolean cancelState() { return this._state.cancelState(); } /** * {@inheritDoc} */ @Override public boolean closeState() { return this._state.closeState(); } /** * {@inheritDoc} */ @Override public boolean recoverState() { return this._state.recoverState(); } /** * {@inheritDoc} */ @Override public boolean isProbing() { return this._state.isProbing(); } /** * {@inheritDoc} */ @Override public boolean isAnnouncing() { return this._state.isAnnouncing(); } /** * {@inheritDoc} */ @Override public boolean isAnnounced() { return this._state.isAnnounced(); } /** * {@inheritDoc} */ @Override public boolean isCanceling() { return this._state.isCanceling(); } /** * {@inheritDoc} */ @Override public boolean isCanceled() { return this._state.isCanceled(); } /** * {@inheritDoc} */ @Override public boolean isClosing() { return this._state.isClosing(); } /** * {@inheritDoc} */ @Override public boolean isClosed() { return this._state.isClosed(); } /** * {@inheritDoc} */ @Override public boolean waitForAnnounced(long timeout) { return _state.waitForAnnounced(timeout); } /** * {@inheritDoc} */ @Override public boolean waitForCanceled(long timeout) { if (_address == null) { // No need to wait this was never announced. return true; } return _state.waitForCanceled(timeout); } } jmdns-3.4.1/src/javax/jmdns/impl/NetworkTopologyDiscoveryImpl.java0000644000175000017500000001046211625404056025231 0ustar mathieumathieu/** * */ package javax.jmdns.impl; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.NetworkTopologyDiscovery; /** * This class implements NetworkTopologyDiscovery. * * @author Pierre Frisch */ public class NetworkTopologyDiscoveryImpl implements NetworkTopologyDiscovery { private final static Logger logger = Logger.getLogger(NetworkTopologyDiscoveryImpl.class.getName()); private final Method _isUp; private final Method _supportsMulticast; /** * */ public NetworkTopologyDiscoveryImpl() { super(); Method isUp; try { isUp = NetworkInterface.class.getMethod("isUp", (Class[]) null); } catch (Exception exception) { // We do not want to throw anything if the method does not exist. isUp = null; } _isUp = isUp; Method supportsMulticast; try { supportsMulticast = NetworkInterface.class.getMethod("supportsMulticast", (Class[]) null); } catch (Exception exception) { // We do not want to throw anything if the method does not exist. supportsMulticast = null; } _supportsMulticast = supportsMulticast; } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS.NetworkTopologyDiscovery#getInetAddresses() */ @Override public InetAddress[] getInetAddresses() { Set result = new HashSet(); try { for (Enumeration nifs = NetworkInterface.getNetworkInterfaces(); nifs.hasMoreElements();) { NetworkInterface nif = nifs.nextElement(); for (Enumeration iaenum = nif.getInetAddresses(); iaenum.hasMoreElements();) { InetAddress interfaceAddress = iaenum.nextElement(); if (logger.isLoggable(Level.FINEST)) { logger.finest("Found NetworkInterface/InetAddress: " + nif + " -- " + interfaceAddress); } if (this.useInetAddress(nif, interfaceAddress)) { result.add(interfaceAddress); } } } } catch (SocketException se) { logger.warning("Error while fetching network interfaces addresses: " + se); } return result.toArray(new InetAddress[result.size()]); } /* * (non-Javadoc) * @see javax.jmdns.JmmDNS.NetworkTopologyDiscovery#useInetAddress(java.net.NetworkInterface, java.net.InetAddress) */ @Override public boolean useInetAddress(NetworkInterface networkInterface, InetAddress interfaceAddress) { try { if (_isUp != null) { try { if (!((Boolean) _isUp.invoke(networkInterface, (Object[]) null)).booleanValue()) { return false; } } catch (Exception exception) { // We should hide that exception. } } if (_supportsMulticast != null) { try { if (!((Boolean) _supportsMulticast.invoke(networkInterface, (Object[]) null)).booleanValue()) { return false; } } catch (Exception exception) { // We should hide that exception. } } if (interfaceAddress.isLoopbackAddress()) { return false; } return true; } catch (Exception exception) { return false; } } /* * (non-Javadoc) * @see javax.jmdns.NetworkTopologyDiscovery#lockInetAddress(java.net.InetAddress) */ @Override public void lockInetAddress(InetAddress interfaceAddress) { // Default implementation does nothing. } /* * (non-Javadoc) * @see javax.jmdns.NetworkTopologyDiscovery#unlockInetAddress(java.net.InetAddress) */ @Override public void unlockInetAddress(InetAddress interfaceAddress) { // Default implementation does nothing. } } jmdns-3.4.1/src/javax/jmdns/impl/ListenerStatus.java0000644000175000017500000002404011625404056022317 0ustar mathieumathieu/** * */ package javax.jmdns.impl; import java.util.EventListener; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; 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; /** * This class track the status of listener.
* The main purpose of this class is to collapse consecutive events so that we can guarantee the correct call back sequence. * * @param * listener type */ public class ListenerStatus { public static class ServiceListenerStatus extends ListenerStatus { private static Logger logger = Logger.getLogger(ServiceListenerStatus.class.getName()); private final ConcurrentMap _addedServices; /** * @param listener * listener being tracked. * @param synch * true if that listener can be called asynchronously */ public ServiceListenerStatus(ServiceListener listener, boolean synch) { super(listener, synch); _addedServices = new ConcurrentHashMap(32); } /** * A service has been added.
* Note:This event is only the service added event. The service info associated with this event does not include resolution information.
* To get the full resolved information you need to listen to {@link #serviceResolved(ServiceEvent)} or call {@link JmDNS#getServiceInfo(String, String, long)} * *
         *  ServiceInfo info = event.getDNS().getServiceInfo(event.getType(), event.getName())
         * 
*

* Please note that service resolution may take a few second to resolve. *

* * @param event * The ServiceEvent providing the name and fully qualified type of the service. */ void serviceAdded(ServiceEvent event) { String qualifiedName = event.getName() + "." + event.getType(); if (null == _addedServices.putIfAbsent(qualifiedName, event.getInfo().clone())) { this.getListener().serviceAdded(event); ServiceInfo info = event.getInfo(); if ((info != null) && (info.hasData())) { this.getListener().serviceResolved(event); } } else { logger.finer("Service Added called for a service already added: " + event); } } /** * A service has been removed. * * @param event * The ServiceEvent providing the name and fully qualified type of the service. */ void serviceRemoved(ServiceEvent event) { String qualifiedName = event.getName() + "." + event.getType(); if (_addedServices.remove(qualifiedName, _addedServices.get(qualifiedName))) { this.getListener().serviceRemoved(event); } else { logger.finer("Service Removed called for a service already removed: " + event); } } /** * A service has been resolved. Its details are now available in the ServiceInfo record.
* Note:This call back will never be called if the service does not resolve.
* * @param event * The ServiceEvent providing the name, the fully qualified type of the service, and the service info record. */ synchronized void serviceResolved(ServiceEvent event) { ServiceInfo info = event.getInfo(); if ((info != null) && (info.hasData())) { String qualifiedName = event.getName() + "." + event.getType(); ServiceInfo previousServiceInfo = _addedServices.get(qualifiedName); if (!_sameInfo(info, previousServiceInfo)) { if (null == previousServiceInfo) { if (null == _addedServices.putIfAbsent(qualifiedName, info.clone())) { this.getListener().serviceResolved(event); } } else { if (_addedServices.replace(qualifiedName, previousServiceInfo, info.clone())) { this.getListener().serviceResolved(event); } } } else { logger.finer("Service Resolved called for a service already resolved: " + event); } } else { logger.warning("Service Resolved called for an unresolved event: " + event); } } private static final boolean _sameInfo(ServiceInfo info, ServiceInfo lastInfo) { if (info == null) return false; if (lastInfo == null) return false; if (!info.equals(lastInfo)) return false; byte[] text = info.getTextBytes(); byte[] lastText = lastInfo.getTextBytes(); if (text.length != lastText.length) return false; for (int i = 0; i < text.length; i++) { if (text[i] != lastText[i]) return false; } return true; } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder aLog = new StringBuilder(2048); aLog.append("[Status for "); aLog.append(this.getListener().toString()); if (_addedServices.isEmpty()) { aLog.append(" no type event "); } else { aLog.append(" ("); for (String service : _addedServices.keySet()) { aLog.append(service + ", "); } aLog.append(") "); } aLog.append("]"); return aLog.toString(); } } public static class ServiceTypeListenerStatus extends ListenerStatus { private static Logger logger = Logger.getLogger(ServiceTypeListenerStatus.class.getName()); private final ConcurrentMap _addedTypes; /** * @param listener * listener being tracked. * @param synch * true if that listener can be called asynchronously */ public ServiceTypeListenerStatus(ServiceTypeListener listener, boolean synch) { super(listener, synch); _addedTypes = new ConcurrentHashMap(32); } /** * A new service type was discovered. * * @param event * The service event providing the fully qualified type of the service. */ void serviceTypeAdded(ServiceEvent event) { if (null == _addedTypes.putIfAbsent(event.getType(), event.getType())) { this.getListener().serviceTypeAdded(event); } else { logger.finest("Service Type Added called for a service type already added: " + event); } } /** * A new subtype for the service type was discovered. * *
         * <sub>._sub.<app>.<protocol>.<servicedomain>.<parentdomain>.
         * 
* * @param event * The service event providing the fully qualified type of the service with subtype. */ void subTypeForServiceTypeAdded(ServiceEvent event) { if (null == _addedTypes.putIfAbsent(event.getType(), event.getType())) { this.getListener().subTypeForServiceTypeAdded(event); } else { logger.finest("Service Sub Type Added called for a service sub type already added: " + event); } } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder aLog = new StringBuilder(2048); aLog.append("[Status for "); aLog.append(this.getListener().toString()); if (_addedTypes.isEmpty()) { aLog.append(" no type event "); } else { aLog.append(" ("); for (String type : _addedTypes.keySet()) { aLog.append(type + ", "); } aLog.append(") "); } aLog.append("]"); return aLog.toString(); } } public final static boolean SYNCHONEOUS = true; public final static boolean ASYNCHONEOUS = false; private final T _listener; private final boolean _synch; /** * @param listener * listener being tracked. * @param synch * true if that listener can be called asynchronously */ public ListenerStatus(T listener, boolean synch) { super(); _listener = listener; _synch = synch; } /** * @return the listener */ public T getListener() { return _listener; } /** * Return true if the listener must be called synchronously. * * @return the synch */ public boolean isSynchronous() { return _synch; } /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return this.getListener().hashCode(); } /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { return (obj instanceof ListenerStatus) && this.getListener().equals(((ListenerStatus) obj).getListener()); } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return "[Status for " + this.getListener().toString() + "]"; } } jmdns-3.4.1/src/javax/jmdns/impl/DNSEntry.java0000644000175000017500000002261511625404056021002 0ustar mathieumathieu// 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.util.Collections; import java.util.Map; import javax.jmdns.ServiceInfo.Fields; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; /** * DNS entry with a name, type, and class. This is the base class for questions and records. * * @author Arthur van Hoff, Pierre Frisch, Rick Blair */ public abstract class DNSEntry { // private static Logger logger = Logger.getLogger(DNSEntry.class.getName()); private final String _key; private final String _name; private final String _type; private final DNSRecordType _recordType; private final DNSRecordClass _dnsClass; private final boolean _unique; final Map _qualifiedNameMap; /** * Create an entry. */ DNSEntry(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique) { _name = name; // _key = (name != null ? name.trim().toLowerCase() : null); _recordType = type; _dnsClass = recordClass; _unique = unique; _qualifiedNameMap = ServiceInfoImpl.decodeQualifiedNameMapForType(this.getName()); String domain = _qualifiedNameMap.get(Fields.Domain); String protocol = _qualifiedNameMap.get(Fields.Protocol); String application = _qualifiedNameMap.get(Fields.Application); String instance = _qualifiedNameMap.get(Fields.Instance).toLowerCase(); _type = (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; _key = ((instance.length() > 0 ? instance + "." : "") + _type).toLowerCase(); } /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { boolean result = false; if (obj instanceof DNSEntry) { DNSEntry other = (DNSEntry) obj; result = this.getKey().equals(other.getKey()) && this.getRecordType().equals(other.getRecordType()) && this.getRecordClass() == other.getRecordClass(); } return result; } /** * Check if two entries have exactly the same name, type, and class. * * @param entry * @return true if the two entries have are for the same record, false otherwise */ public boolean isSameEntry(DNSEntry entry) { return this.getKey().equals(entry.getKey()) && this.getRecordType().equals(entry.getRecordType()) && ((DNSRecordClass.CLASS_ANY == entry.getRecordClass()) || this.getRecordClass().equals(entry.getRecordClass())); } /** * Check if two entries have the same subtype. * * @param other * @return true if the two entries have are for the same subtype, false otherwise */ public boolean sameSubtype(DNSEntry other) { return this.getSubtype().equals(other.getSubtype()); } /** * Returns the subtype of this entry * * @return subtype of this entry */ public String getSubtype() { String subtype = this.getQualifiedNameMap().get(Fields.Subtype); return (subtype != null ? subtype : ""); } /** * Returns the name of this entry * * @return name of this entry */ public String getName() { return (_name != null ? _name : ""); } /** * @return the type */ public String getType() { return (_type != null ? _type : ""); } /** * Returns the key for this entry. The key is the lower case name. * * @return key for this entry */ public String getKey() { return (_key != null ? _key : ""); } /** * @return record type */ public DNSRecordType getRecordType() { return (_recordType != null ? _recordType : DNSRecordType.TYPE_IGNORE); } /** * @return record class */ public DNSRecordClass getRecordClass() { return (_dnsClass != null ? _dnsClass : DNSRecordClass.CLASS_UNKNOWN); } /** * @return true if unique */ public boolean isUnique() { return _unique; } public Map getQualifiedNameMap() { return Collections.unmodifiableMap(_qualifiedNameMap); } public boolean isServicesDiscoveryMetaQuery() { return _qualifiedNameMap.get(Fields.Application).equals("dns-sd") && _qualifiedNameMap.get(Fields.Instance).equals("_services"); } public boolean isDomainDiscoveryQuery() { // b._dns-sd._udp.. // db._dns-sd._udp.. // r._dns-sd._udp.. // dr._dns-sd._udp.. // lb._dns-sd._udp.. if (_qualifiedNameMap.get(Fields.Application).equals("dns-sd")) { String name = _qualifiedNameMap.get(Fields.Instance); return "b".equals(name) || "db".equals(name) || "r".equals(name) || "dr".equals(name) || "lb".equals(name); } return false; } public boolean isReverseLookup() { return this.isV4ReverseLookup() || this.isV6ReverseLookup(); } public boolean isV4ReverseLookup() { return _qualifiedNameMap.get(Fields.Domain).endsWith("in-addr.arpa"); } public boolean isV6ReverseLookup() { return _qualifiedNameMap.get(Fields.Domain).endsWith("ip6.arpa"); } /** * Check if the record is stale, i.e. it has outlived more than half of its TTL. * * @param now * update date * @return true is the record is stale, false otherwise. */ public abstract boolean isStale(long now); /** * Check if the record is expired. * * @param now * update date * @return true is the record is expired, false otherwise. */ public abstract boolean isExpired(long now); /** * Check that 2 entries are of the same class. * * @param entry * @return true is the two class are the same, false otherwise. */ public boolean isSameRecordClass(DNSEntry entry) { return (entry != null) && (entry.getRecordClass() == this.getRecordClass()); } /** * Check that 2 entries are of the same type. * * @param entry * @return true is the two type are the same, false otherwise. */ public boolean isSameType(DNSEntry entry) { return (entry != null) && (entry.getRecordType() == this.getRecordType()); } /** * @param dout * @exception IOException */ protected void toByteArray(DataOutputStream dout) throws IOException { dout.write(this.getName().getBytes("UTF8")); dout.writeShort(this.getRecordType().indexValue()); dout.writeShort(this.getRecordClass().indexValue()); } /** * 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. * * @return byte array representation */ protected byte[] toByteArray() { try { ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); this.toByteArray(dout); 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. * * @param that * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. */ public int compareTo(DNSEntry 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; } /** * Overriden, to return a value which is consistent with the value returned by equals(Object). */ @Override public int hashCode() { return this.getKey().hashCode() + this.getRecordType().indexValue() + this.getRecordClass().indexValue(); } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder aLog = new StringBuilder(200); aLog.append("[" + this.getClass().getSimpleName() + "@" + System.identityHashCode(this)); aLog.append(" type: " + this.getRecordType()); aLog.append(", class: " + this.getRecordClass()); aLog.append((_unique ? "-unique," : ",")); aLog.append(" name: " + _name); this.toString(aLog); aLog.append("]"); return aLog.toString(); } /** * @param aLog */ protected void toString(StringBuilder aLog) { // Stub } } jmdns-3.4.1/src/javax/jmdns/impl/JmDNSImpl.java0000644000175000017500000023201411625404056021065 0ustar mathieumathieu// /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.MulticastSocket; import java.net.SocketException; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.ReentrantLock; 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.ServiceInfo.Fields; import javax.jmdns.ServiceListener; import javax.jmdns.ServiceTypeListener; import javax.jmdns.impl.ListenerStatus.ServiceListenerStatus; import javax.jmdns.impl.ListenerStatus.ServiceTypeListenerStatus; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; import javax.jmdns.impl.constants.DNSState; import javax.jmdns.impl.tasks.DNSTask; // REMIND: multiple IP addresses /** * mDNS implementation in Java. * * @author Arthur van Hoff, Rick Blair, Jeff Sonstein, Werner Randelshofer, Pierre Frisch, Scott Lewis */ public class JmDNSImpl extends JmDNS implements DNSStatefulObject, DNSTaskStarter { private static Logger logger = Logger.getLogger(JmDNSImpl.class.getName()); public enum Operation { Remove, Update, Add, RegisterServiceType, Noop } /** * This is the multicast group, we are listening to for multicast DNS messages. */ private volatile InetAddress _group; /** * This is our multicast socket. */ private volatile MulticastSocket _socket; /** * Holds instances of JmDNS.DNSListener. Must by a synchronized collection, because it is updated from concurrent threads. */ private final List _listeners; /** * Holds instances of ServiceListener's. Keys are Strings holding a fully qualified service type. Values are LinkedList's of ServiceListener's. */ private final ConcurrentMap> _serviceListeners; /** * Holds instances of ServiceTypeListener's. */ private final Set _typeListeners; /** * Cache for DNSEntry's. */ private final 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. */ private final ConcurrentMap _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. */ private final ConcurrentMap _serviceTypes; private volatile Delegate _delegate; /** * This is used to store type entries. The type is stored as a call variable and the map support the subtypes. *

* The key is the lowercase version as the value is the case preserved version. *

*/ public static class ServiceTypeEntry extends AbstractMap implements Cloneable { private final Set> _entrySet; private final String _type; private static class SubTypeEntry implements Entry, java.io.Serializable, Cloneable { private static final long serialVersionUID = 9188503522395855322L; private final String _key; private final String _value; public SubTypeEntry(String subtype) { super(); _value = (subtype != null ? subtype : ""); _key = _value.toLowerCase(); } /** * {@inheritDoc} */ @Override public String getKey() { return _key; } /** * {@inheritDoc} */ @Override public String getValue() { return _value; } /** * Replaces the value corresponding to this entry with the specified value (optional operation). This implementation simply throws UnsupportedOperationException, as this class implements an immutable map entry. * * @param value * new value to be stored in this entry * @return (Does not return) * @exception UnsupportedOperationException * always */ @Override public String setValue(String value) { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ @Override public boolean equals(Object entry) { if (!(entry instanceof Map.Entry)) { return false; } return this.getKey().equals(((Map.Entry) entry).getKey()) && this.getValue().equals(((Map.Entry) entry).getValue()); } /** * {@inheritDoc} */ @Override public int hashCode() { return (_key == null ? 0 : _key.hashCode()) ^ (_value == null ? 0 : _value.hashCode()); } /* * (non-Javadoc) * @see java.lang.Object#clone() */ @Override public SubTypeEntry clone() { // Immutable object return this; } /** * {@inheritDoc} */ @Override public String toString() { return _key + "=" + _value; } } public ServiceTypeEntry(String type) { super(); this._type = type; this._entrySet = new HashSet>(); } /** * The type associated with this entry. * * @return the type */ public String getType() { return _type; } /* * (non-Javadoc) * @see java.util.AbstractMap#entrySet() */ @Override public Set> entrySet() { return _entrySet; } /** * Returns true if this set contains the specified element. More formally, returns true if and only if this set contains an element e such that * (o==null ? e==null : o.equals(e)). * * @param subtype * element whose presence in this set is to be tested * @return true if this set contains the specified element */ public boolean contains(String subtype) { return subtype != null && this.containsKey(subtype.toLowerCase()); } /** * Adds the specified element to this set if it is not already present. More formally, adds the specified element e to this set if this set contains no element e2 such that * (e==null ? e2==null : e.equals(e2)). If this set already contains the element, the call leaves the set unchanged and returns false. * * @param subtype * element to be added to this set * @return true if this set did not already contain the specified element */ public boolean add(String subtype) { if (subtype == null || this.contains(subtype)) { return false; } _entrySet.add(new SubTypeEntry(subtype)); return true; } /** * Returns an iterator over the elements in this set. The elements are returned in no particular order (unless this set is an instance of some class that provides a guarantee). * * @return an iterator over the elements in this set */ public Iterator iterator() { return this.keySet().iterator(); } /* * (non-Javadoc) * @see java.util.AbstractMap#clone() */ @Override public ServiceTypeEntry clone() { ServiceTypeEntry entry = new ServiceTypeEntry(this.getType()); for (Map.Entry subTypeEntry : this.entrySet()) { entry.add(subTypeEntry.getValue()); } return entry; } /* * (non-Javadoc) * @see java.util.AbstractMap#toString() */ @Override public String toString() { final StringBuilder aLog = new StringBuilder(200); if (this.isEmpty()) { aLog.append("empty"); } else { for (String value : this.values()) { aLog.append(value); aLog.append(", "); } aLog.setLength(aLog.length() - 2); } return aLog.toString(); } } /** * This is the shutdown hook, we registered with the java runtime. */ protected Thread _shutdown; /** * Handle on the local host */ private HostInfo _localHost; private Thread _incomingListener; /** * 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; private final ExecutorService _executor = Executors.newSingleThreadExecutor(); // // 2009-09-16 ldeck: adding docbug patch with slight ammendments // 'Fixes two deadlock conditions involving JmDNS.close() - ID: 1473279' // // --------------------------------------------------- /** * The timer that triggers our announcements. We can't use the main timer object, because that could cause a deadlock where Prober waits on JmDNS.this lock held by close(), close() waits for us to finish, and we wait for Prober to give us back * the timer thread so we can announce. (Patch from docbug in 2006-04-19 still wasn't patched .. so I'm doing it!) */ // private final Timer _cancelerTimer; // --------------------------------------------------- /** * 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 final ReentrantLock _ioLock = new ReentrantLock(); /** * 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.
* FIXME [PJYF June 8 2010]: This does not work well with multiple planned answers for packages that came in from different clients. */ private DNSIncoming _plannedAnswer; // State machine /** * 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 ConcurrentMap _serviceCollectors; private final String _name; /** * Main method to display API information if run from java -jar * * @param argv * the command line arguments */ public static void main(String[] argv) { String version = null; try { final Properties pomProperties = new Properties(); pomProperties.load(JmDNSImpl.class.getResourceAsStream("/META-INF/maven/javax.jmdns/jmdns/pom.properties")); version = pomProperties.getProperty("version"); } catch (Exception e) { version = "RUNNING.IN.IDE.FULL"; } System.out.println("JmDNS version \"" + version + "\""); System.out.println(" "); System.out.println("Running on java version \"" + System.getProperty("java.version") + "\"" + " (build " + System.getProperty("java.runtime.version") + ")" + " from " + System.getProperty("java.vendor")); System.out.println("Operating environment \"" + System.getProperty("os.name") + "\"" + " version " + System.getProperty("os.version") + " on " + System.getProperty("os.arch")); System.out.println("For more information on JmDNS please visit https://sourceforge.net/projects/jmdns/"); } /** * Create an instance of JmDNS and bind it to a specific network interface given its IP-address. * * @param address * IP address to bind to. * @param name * name of the newly created JmDNS * @exception IOException */ public JmDNSImpl(InetAddress address, String name) throws IOException { super(); if (logger.isLoggable(Level.FINER)) { logger.finer("JmDNS instance created"); } _cache = new DNSCache(100); _listeners = Collections.synchronizedList(new ArrayList()); _serviceListeners = new ConcurrentHashMap>(); _typeListeners = Collections.synchronizedSet(new HashSet()); _serviceCollectors = new ConcurrentHashMap(); _services = new ConcurrentHashMap(20); _serviceTypes = new ConcurrentHashMap(20); _localHost = HostInfo.newHostInfo(address, this, name); _name = (name != null ? name : _localHost.getName()); // _cancelerTimer = new Timer("JmDNS.cancelerTimer"); // (ldeck 2.1.1) preventing shutdown blocking thread // ------------------------------------------------- // _shutdown = new Thread(new Shutdown(), "JmDNS.Shutdown"); // Runtime.getRuntime().addShutdownHook(_shutdown); // ------------------------------------------------- // Bind to multicast socket this.openMulticastSocket(this.getLocalHost()); this.start(this.getServices().values()); this.startReaper(); } private void start(Collection serviceInfos) { if (_incomingListener == null) { _incomingListener = new SocketListener(this); _incomingListener.start(); } this.startProber(); for (ServiceInfo info : serviceInfos) { try { this.registerService(new ServiceInfoImpl(info)); } catch (final Exception exception) { logger.log(Level.WARNING, "start() Registration exception ", exception); } } } private void openMulticastSocket(HostInfo hostInfo) throws IOException { if (_group == null) { if (hostInfo.getInetAddress() instanceof Inet6Address) { _group = InetAddress.getByName(DNSConstants.MDNS_GROUP_IPV6); } else { _group = InetAddress.getByName(DNSConstants.MDNS_GROUP); } } if (_socket != null) { this.closeMulticastSocket(); } _socket = new MulticastSocket(DNSConstants.MDNS_PORT); if ((hostInfo != null) && (hostInfo.getInterface() != null)) { try { _socket.setNetworkInterface(hostInfo.getInterface()); } catch (SocketException e) { if (logger.isLoggable(Level.FINE)) { logger.fine("openMulticastSocket() Set network interface exception: " + e.getMessage()); } } } _socket.setTimeToLive(255); _socket.joinGroup(_group); } private void closeMulticastSocket() { // jP: 20010-01-18. See below. We'll need this monitor... // assert (Thread.holdsLock(this)); if (logger.isLoggable(Level.FINER)) { logger.finer("closeMulticastSocket()"); } if (_socket != null) { // close socket try { try { _socket.leaveGroup(_group); } catch (SocketException exception) { // } _socket.close(); // jP: 20010-01-18. It isn't safe to join() on the listener // thread - it attempts to lock the IoLock object, and deadlock // ensues. Per issue #2933183, changed this to wait on the JmDNS // monitor, checking on each notify (or timeout) that the // listener thread has stopped. // while (_incomingListener != null && _incomingListener.isAlive()) { synchronized (this) { try { if (_incomingListener != null && _incomingListener.isAlive()) { // wait time is arbitrary, we're really expecting notification. if (logger.isLoggable(Level.FINER)) { logger.finer("closeMulticastSocket(): waiting for jmDNS monitor"); } this.wait(1000); } } catch (InterruptedException ignored) { // Ignored } } } _incomingListener = null; } catch (final Exception exception) { logger.log(Level.WARNING, "closeMulticastSocket() Close socket exception ", exception); } _socket = null; } } // State machine /** * {@inheritDoc} */ @Override public boolean advanceState(DNSTask task) { return this._localHost.advanceState(task); } /** * {@inheritDoc} */ @Override public boolean revertState() { return this._localHost.revertState(); } /** * {@inheritDoc} */ @Override public boolean cancelState() { return this._localHost.cancelState(); } /** * {@inheritDoc} */ @Override public boolean closeState() { return this._localHost.closeState(); } /** * {@inheritDoc} */ @Override public boolean recoverState() { return this._localHost.recoverState(); } /** * {@inheritDoc} */ @Override public JmDNSImpl getDns() { return this; } /** * {@inheritDoc} */ @Override public void associateWithTask(DNSTask task, DNSState state) { this._localHost.associateWithTask(task, state); } /** * {@inheritDoc} */ @Override public void removeAssociationWithTask(DNSTask task) { this._localHost.removeAssociationWithTask(task); } /** * {@inheritDoc} */ @Override public boolean isAssociatedWithTask(DNSTask task, DNSState state) { return this._localHost.isAssociatedWithTask(task, state); } /** * {@inheritDoc} */ @Override public boolean isProbing() { return this._localHost.isProbing(); } /** * {@inheritDoc} */ @Override public boolean isAnnouncing() { return this._localHost.isAnnouncing(); } /** * {@inheritDoc} */ @Override public boolean isAnnounced() { return this._localHost.isAnnounced(); } /** * {@inheritDoc} */ @Override public boolean isCanceling() { return this._localHost.isCanceling(); } /** * {@inheritDoc} */ @Override public boolean isCanceled() { return this._localHost.isCanceled(); } /** * {@inheritDoc} */ @Override public boolean isClosing() { return this._localHost.isClosing(); } /** * {@inheritDoc} */ @Override public boolean isClosed() { return this._localHost.isClosed(); } /** * {@inheritDoc} */ @Override public boolean waitForAnnounced(long timeout) { return this._localHost.waitForAnnounced(timeout); } /** * {@inheritDoc} */ @Override public boolean waitForCanceled(long timeout) { return this._localHost.waitForCanceled(timeout); } /** * Return the DNSCache associated with the cache variable * * @return DNS cache */ public DNSCache getCache() { return _cache; } /** * {@inheritDoc} */ @Override public String getName() { return _name; } /** * {@inheritDoc} */ @Override public String getHostName() { return _localHost.getName(); } /** * Returns the local host info * * @return local host info */ public HostInfo getLocalHost() { return _localHost; } /** * {@inheritDoc} */ @Override public InetAddress getInterface() throws IOException { return _socket.getInterface(); } /** * {@inheritDoc} */ @Override public ServiceInfo getServiceInfo(String type, String name) { return this.getServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT); } /** * {@inheritDoc} */ @Override public ServiceInfo getServiceInfo(String type, String name, long timeout) { return this.getServiceInfo(type, name, false, timeout); } /** * {@inheritDoc} */ @Override public ServiceInfo getServiceInfo(String type, String name, boolean persistent) { return this.getServiceInfo(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT); } /** * {@inheritDoc} */ @Override public ServiceInfo getServiceInfo(String type, String name, boolean persistent, long timeout) { final ServiceInfoImpl info = this.resolveServiceInfo(type, name, "", persistent); this.waitForInfoData(info, timeout); return (info.hasData() ? info : null); } ServiceInfoImpl resolveServiceInfo(String type, String name, String subtype, boolean persistent) { this.cleanCache(); String loType = type.toLowerCase(); this.registerServiceType(type); if (_serviceCollectors.putIfAbsent(loType, new ServiceCollector(type)) == null) { this.addServiceListener(loType, _serviceCollectors.get(loType), ListenerStatus.SYNCHONEOUS); } // Check if the answer is in the cache. final ServiceInfoImpl info = this.getServiceInfoFromCache(type, name, subtype, persistent); // We still run the resolver to do the dispatch but if the info is already there it will quit immediately this.startServiceInfoResolver(info); return info; } ServiceInfoImpl getServiceInfoFromCache(String type, String name, String subtype, boolean persistent) { // Check if the answer is in the cache. ServiceInfoImpl info = new ServiceInfoImpl(type, name, subtype, 0, 0, 0, persistent, (byte[]) null); DNSEntry pointerEntry = this.getCache().getDNSEntry(new DNSRecord.Pointer(type, DNSRecordClass.CLASS_ANY, false, 0, info.getQualifiedName())); if (pointerEntry instanceof DNSRecord) { ServiceInfoImpl cachedInfo = (ServiceInfoImpl) ((DNSRecord) pointerEntry).getServiceInfo(persistent); if (cachedInfo != null) { // To get a complete info record we need to retrieve the service, address and the text bytes. Map map = cachedInfo.getQualifiedNameMap(); byte[] srvBytes = null; String server = ""; DNSEntry serviceEntry = this.getCache().getDNSEntry(info.getQualifiedName(), DNSRecordType.TYPE_SRV, DNSRecordClass.CLASS_ANY); if (serviceEntry instanceof DNSRecord) { ServiceInfo cachedServiceEntryInfo = ((DNSRecord) serviceEntry).getServiceInfo(persistent); if (cachedServiceEntryInfo != null) { cachedInfo = new ServiceInfoImpl(map, cachedServiceEntryInfo.getPort(), cachedServiceEntryInfo.getWeight(), cachedServiceEntryInfo.getPriority(), persistent, (byte[]) null); srvBytes = cachedServiceEntryInfo.getTextBytes(); server = cachedServiceEntryInfo.getServer(); } } DNSEntry addressEntry = this.getCache().getDNSEntry(server, DNSRecordType.TYPE_A, DNSRecordClass.CLASS_ANY); if (addressEntry instanceof DNSRecord) { ServiceInfo cachedAddressInfo = ((DNSRecord) addressEntry).getServiceInfo(persistent); if (cachedAddressInfo != null) { for (Inet4Address address : cachedAddressInfo.getInet4Addresses()) { cachedInfo.addAddress(address); } cachedInfo._setText(cachedAddressInfo.getTextBytes()); } } addressEntry = this.getCache().getDNSEntry(server, DNSRecordType.TYPE_AAAA, DNSRecordClass.CLASS_ANY); if (addressEntry instanceof DNSRecord) { ServiceInfo cachedAddressInfo = ((DNSRecord) addressEntry).getServiceInfo(persistent); if (cachedAddressInfo != null) { for (Inet6Address address : cachedAddressInfo.getInet6Addresses()) { cachedInfo.addAddress(address); } cachedInfo._setText(cachedAddressInfo.getTextBytes()); } } DNSEntry textEntry = this.getCache().getDNSEntry(cachedInfo.getQualifiedName(), DNSRecordType.TYPE_TXT, DNSRecordClass.CLASS_ANY); if (textEntry instanceof DNSRecord) { ServiceInfo cachedTextInfo = ((DNSRecord) textEntry).getServiceInfo(persistent); if (cachedTextInfo != null) { cachedInfo._setText(cachedTextInfo.getTextBytes()); } } if (cachedInfo.getTextBytes().length == 0) { cachedInfo._setText(srvBytes); } if (cachedInfo.hasData()) { info = cachedInfo; } } } return info; } private void waitForInfoData(ServiceInfo info, long timeout) { synchronized (info) { long loops = (timeout / 200L); if (loops < 1) { loops = 1; } for (int i = 0; i < loops; i++) { if (info.hasData()) { break; } try { info.wait(200); } catch (final InterruptedException e) { /* Stub */ } } } } /** * {@inheritDoc} */ @Override public void requestServiceInfo(String type, String name) { this.requestServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT); } /** * {@inheritDoc} */ @Override public void requestServiceInfo(String type, String name, boolean persistent) { this.requestServiceInfo(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT); } /** * {@inheritDoc} */ @Override public void requestServiceInfo(String type, String name, long timeout) { this.requestServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT); } /** * {@inheritDoc} */ @Override public void requestServiceInfo(String type, String name, boolean persistent, long timeout) { final ServiceInfoImpl info = this.resolveServiceInfo(type, name, "", persistent); this.waitForInfoData(info, timeout); } void handleServiceResolved(ServiceEvent event) { List list = _serviceListeners.get(event.getType().toLowerCase()); final List listCopy; if ((list != null) && (!list.isEmpty())) { if ((event.getInfo() != null) && event.getInfo().hasData()) { final ServiceEvent localEvent = event; synchronized (list) { listCopy = new ArrayList(list); } for (final ServiceListenerStatus listener : listCopy) { _executor.submit(new Runnable() { /** {@inheritDoc} */ @Override public void run() { listener.serviceResolved(localEvent); } }); } } } } /** * {@inheritDoc} */ @Override public void addServiceTypeListener(ServiceTypeListener listener) throws IOException { ServiceTypeListenerStatus status = new ServiceTypeListenerStatus(listener, ListenerStatus.ASYNCHONEOUS); _typeListeners.add(status); // report cached service types for (String type : _serviceTypes.keySet()) { status.serviceTypeAdded(new ServiceEventImpl(this, type, "", null)); } this.startTypeResolver(); } /** * {@inheritDoc} */ @Override public void removeServiceTypeListener(ServiceTypeListener listener) { ServiceTypeListenerStatus status = new ServiceTypeListenerStatus(listener, ListenerStatus.ASYNCHONEOUS); _typeListeners.remove(status); } /** * {@inheritDoc} */ @Override public void addServiceListener(String type, ServiceListener listener) { this.addServiceListener(type, listener, ListenerStatus.ASYNCHONEOUS); } private void addServiceListener(String type, ServiceListener listener, boolean synch) { ServiceListenerStatus status = new ServiceListenerStatus(listener, synch); final String loType = type.toLowerCase(); List list = _serviceListeners.get(loType); if (list == null) { if (_serviceListeners.putIfAbsent(loType, new LinkedList()) == null) { if (_serviceCollectors.putIfAbsent(loType, new ServiceCollector(type)) == null) { // We have a problem here. The service collectors must be called synchronously so that their cache get cleaned up immediately or we will report . this.addServiceListener(loType, _serviceCollectors.get(loType), ListenerStatus.SYNCHONEOUS); } } list = _serviceListeners.get(loType); } if (list != null) { synchronized (list) { if (!list.contains(listener)) { list.add(status); } } } // report cached service types final List serviceEvents = new ArrayList(); Collection dnsEntryLits = this.getCache().allValues(); for (DNSEntry entry : dnsEntryLits) { final DNSRecord record = (DNSRecord) entry; if (record.getRecordType() == DNSRecordType.TYPE_SRV) { if (record.getKey().endsWith(loType)) { // Do not used the record embedded method for generating event this will not work. // serviceEvents.add(record.getServiceEvent(this)); serviceEvents.add(new ServiceEventImpl(this, record.getType(), toUnqualifiedName(record.getType(), record.getName()), record.getServiceInfo())); } } } // Actually call listener with all service events added above for (ServiceEvent serviceEvent : serviceEvents) { status.serviceAdded(serviceEvent); } // Create/start ServiceResolver this.startServiceResolver(type); } /** * {@inheritDoc} */ @Override public void removeServiceListener(String type, ServiceListener listener) { String loType = type.toLowerCase(); List list = _serviceListeners.get(loType); if (list != null) { synchronized (list) { ServiceListenerStatus status = new ServiceListenerStatus(listener, ListenerStatus.ASYNCHONEOUS); list.remove(status); if (list.isEmpty()) { _serviceListeners.remove(loType, list); } } } } /** * {@inheritDoc} */ @Override public void registerService(ServiceInfo infoAbstract) throws IOException { if (this.isClosing() || this.isClosed()) { throw new IllegalStateException("This DNS is closed."); } final ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract; if (info.getDns() != null) { if (info.getDns() != this) { throw new IllegalStateException("A service information can only be registered with a single instamce of JmDNS."); } else if (_services.get(info.getKey()) != null) { throw new IllegalStateException("A service information can only be registered once."); } } info.setDns(this); this.registerServiceType(info.getTypeWithSubtype()); // bind the service to this address info.recoverState(); info.setServer(_localHost.getName()); info.addAddress(_localHost.getInet4Address()); info.addAddress(_localHost.getInet6Address()); this.waitForAnnounced(DNSConstants.SERVICE_INFO_TIMEOUT); this.makeServiceNameUnique(info); while (_services.putIfAbsent(info.getKey(), info) != null) { this.makeServiceNameUnique(info); } this.startProber(); info.waitForAnnounced(DNSConstants.SERVICE_INFO_TIMEOUT); if (logger.isLoggable(Level.FINE)) { logger.fine("registerService() JmDNS registered service as " + info); } } /** * {@inheritDoc} */ @Override public void unregisterService(ServiceInfo infoAbstract) { final ServiceInfoImpl info = (ServiceInfoImpl) _services.get(infoAbstract.getKey()); if (info != null) { info.cancelState(); this.startCanceler(); info.waitForCanceled(DNSConstants.CLOSE_TIMEOUT); _services.remove(info.getKey(), info); if (logger.isLoggable(Level.FINE)) { logger.fine("unregisterService() JmDNS unregistered service as " + info); } } else { logger.warning("Removing unregistered service info: " + infoAbstract.getKey()); } } /** * {@inheritDoc} */ @Override public void unregisterAllServices() { if (logger.isLoggable(Level.FINER)) { logger.finer("unregisterAllServices()"); } for (String name : _services.keySet()) { ServiceInfoImpl info = (ServiceInfoImpl) _services.get(name); if (info != null) { if (logger.isLoggable(Level.FINER)) { logger.finer("Cancelling service info: " + info); } info.cancelState(); } } this.startCanceler(); for (String name : _services.keySet()) { ServiceInfoImpl info = (ServiceInfoImpl) _services.get(name); if (info != null) { if (logger.isLoggable(Level.FINER)) { logger.finer("Wait for service info cancel: " + info); } info.waitForCanceled(DNSConstants.CLOSE_TIMEOUT); _services.remove(name, info); } } } /** * {@inheritDoc} */ @Override public boolean registerServiceType(String type) { boolean typeAdded = false; Map map = ServiceInfoImpl.decodeQualifiedNameMapForType(type); String domain = map.get(Fields.Domain); String protocol = map.get(Fields.Protocol); String application = map.get(Fields.Application); String subtype = map.get(Fields.Subtype); final String name = (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; final String loname = name.toLowerCase(); if (logger.isLoggable(Level.FINE)) { logger.fine(this.getName() + ".registering service type: " + type + " as: " + name + (subtype.length() > 0 ? " subtype: " + subtype : "")); } if (!_serviceTypes.containsKey(loname) && !application.toLowerCase().equals("dns-sd") && !domain.toLowerCase().endsWith("in-addr.arpa") && !domain.toLowerCase().endsWith("ip6.arpa")) { typeAdded = _serviceTypes.putIfAbsent(loname, new ServiceTypeEntry(name)) == null; if (typeAdded) { final ServiceTypeListenerStatus[] list = _typeListeners.toArray(new ServiceTypeListenerStatus[_typeListeners.size()]); final ServiceEvent event = new ServiceEventImpl(this, name, "", null); for (final ServiceTypeListenerStatus status : list) { _executor.submit(new Runnable() { /** {@inheritDoc} */ @Override public void run() { status.serviceTypeAdded(event); } }); } } } if (subtype.length() > 0) { ServiceTypeEntry subtypes = _serviceTypes.get(loname); if ((subtypes != null) && (!subtypes.contains(subtype))) { synchronized (subtypes) { if (!subtypes.contains(subtype)) { typeAdded = true; subtypes.add(subtype); final ServiceTypeListenerStatus[] list = _typeListeners.toArray(new ServiceTypeListenerStatus[_typeListeners.size()]); final ServiceEvent event = new ServiceEventImpl(this, "_" + subtype + "._sub." + name, "", null); for (final ServiceTypeListenerStatus status : list) { _executor.submit(new Runnable() { /** {@inheritDoc} */ @Override public void run() { status.subTypeForServiceTypeAdded(event); } }); } } } } } return typeAdded; } /** * 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.getKey(); final long now = System.currentTimeMillis(); boolean collision; do { collision = false; // Check for collision in cache for (DNSEntry dnsEntry : this.getCache().getDNSEntryList(info.getKey())) { if (DNSRecordType.TYPE_SRV.equals(dnsEntry.getRecordType()) && !dnsEntry.isExpired(now)) { final DNSRecord.Service s = (DNSRecord.Service) dnsEntry; if (s.getPort() != info.getPort() || !s.getServer().equals(_localHost.getName())) { if (logger.isLoggable(Level.FINER)) { logger.finer("makeServiceNameUnique() JmDNS.makeServiceNameUnique srv collision:" + dnsEntry + " s.server=" + s.getServer() + " " + _localHost.getName() + " equals:" + (s.getServer().equals(_localHost.getName()))); } info.setName(incrementName(info.getName())); collision = true; break; } } } // Check for collision with other service infos published by JmDNS final ServiceInfo selfService = _services.get(info.getKey()); if (selfService != null && selfService != info) { info.setName(incrementName(info.getName())); collision = true; } } while (collision); return !(originalQualifiedName.equals(info.getKey())); } String incrementName(String name) { String aName = name; try { final int l = aName.lastIndexOf('('); final int r = aName.lastIndexOf(')'); if ((l >= 0) && (l < r)) { aName = aName.substring(0, l) + "(" + (Integer.parseInt(aName.substring(l + 1, r)) + 1) + ")"; } else { aName += " (2)"; } } catch (final NumberFormatException e) { aName += " (2)"; } return aName; } /** * 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. * * @param listener * DSN listener * @param question * DNS query */ public void addListener(DNSListener listener, DNSQuestion question) { final long now = System.currentTimeMillis(); // add the new listener _listeners.add(listener); // report existing matched records if (question != null) { for (DNSEntry dnsEntry : this.getCache().getDNSEntryList(question.getName().toLowerCase())) { if (question.answeredBy(dnsEntry) && !dnsEntry.isExpired(now)) { listener.updateRecord(this.getCache(), now, dnsEntry); } } } } /** * Remove a listener from all outstanding questions. The listener will no longer receive any updates. * * @param listener * DSN listener */ public void removeListener(DNSListener listener) { _listeners.remove(listener); } /** * Renew a service when the record become stale. If there is no service collector for the type this method does nothing. * * @param record * DNS record */ public void renewServiceCollector(DNSRecord record) { ServiceInfo info = record.getServiceInfo(); if (_serviceCollectors.containsKey(info.getType().toLowerCase())) { // Create/start ServiceResolver this.startServiceResolver(info.getType()); } } // Remind: Method updateRecord should receive a better name. /** * Notify all listeners that a record was updated. * * @param now * update date * @param rec * DNS record * @param operation * DNS cache operation */ public void updateRecord(long now, DNSRecord rec, Operation operation) { // We do not want to block the entire DNS while we are updating the record for each listener (service info) { List listenerList = null; synchronized (_listeners) { listenerList = new ArrayList(_listeners); } for (DNSListener listener : listenerList) { listener.updateRecord(this.getCache(), now, rec); } } if (DNSRecordType.TYPE_PTR.equals(rec.getRecordType())) // if (DNSRecordType.TYPE_PTR.equals(rec.getRecordType()) || DNSRecordType.TYPE_SRV.equals(rec.getRecordType())) { ServiceEvent event = rec.getServiceEvent(this); if ((event.getInfo() == null) || !event.getInfo().hasData()) { // We do not care about the subtype because the info is only used if complete and the subtype will then be included. ServiceInfo info = this.getServiceInfoFromCache(event.getType(), event.getName(), "", false); if (info.hasData()) { event = new ServiceEventImpl(this, event.getType(), event.getName(), info); } } List list = _serviceListeners.get(event.getType().toLowerCase()); final List serviceListenerList; if (list != null) { synchronized (list) { serviceListenerList = new ArrayList(list); } } else { serviceListenerList = Collections.emptyList(); } if (logger.isLoggable(Level.FINEST)) { logger.finest(this.getName() + ".updating record for event: " + event + " list " + serviceListenerList + " operation: " + operation); } if (!serviceListenerList.isEmpty()) { final ServiceEvent localEvent = event; switch (operation) { case Add: for (final ServiceListenerStatus listener : serviceListenerList) { if (listener.isSynchronous()) { listener.serviceAdded(localEvent); } else { _executor.submit(new Runnable() { /** {@inheritDoc} */ @Override public void run() { listener.serviceAdded(localEvent); } }); } } break; case Remove: for (final ServiceListenerStatus listener : serviceListenerList) { if (listener.isSynchronous()) { listener.serviceRemoved(localEvent); } else { _executor.submit(new Runnable() { /** {@inheritDoc} */ @Override public void run() { listener.serviceRemoved(localEvent); } }); } } break; default: break; } } } } void handleRecord(DNSRecord record, long now) { DNSRecord newRecord = record; Operation cacheOperation = Operation.Noop; final boolean expired = newRecord.isExpired(now); if (logger.isLoggable(Level.FINE)) { logger.fine(this.getName() + " handle response: " + newRecord); } // update the cache if (!newRecord.isServicesDiscoveryMetaQuery() && !newRecord.isDomainDiscoveryQuery()) { final boolean unique = newRecord.isUnique(); final DNSRecord cachedRecord = (DNSRecord) this.getCache().getDNSEntry(newRecord); if (logger.isLoggable(Level.FINE)) { logger.fine(this.getName() + " handle response cached record: " + cachedRecord); } if (unique) { for (DNSEntry entry : this.getCache().getDNSEntryList(newRecord.getKey())) { if (newRecord.getRecordType().equals(entry.getRecordType()) && newRecord.getRecordClass().equals(entry.getRecordClass()) && (entry != cachedRecord)) { ((DNSRecord) entry).setWillExpireSoon(now); } } } if (cachedRecord != null) { if (expired) { // if the record has a 0 ttl that means we have a cancel record we need to delay the removal by 1s if (newRecord.getTTL() == 0) { cacheOperation = Operation.Noop; cachedRecord.setWillExpireSoon(now); // the actual record will be disposed of by the record reaper. } else { cacheOperation = Operation.Remove; this.getCache().removeDNSEntry(cachedRecord); } } else { // If the record content has changed we need to inform our listeners. if (!newRecord.sameValue(cachedRecord) || (!newRecord.sameSubtype(cachedRecord) && (newRecord.getSubtype().length() > 0))) { if (newRecord.isSingleValued()) { cacheOperation = Operation.Update; this.getCache().replaceDNSEntry(newRecord, cachedRecord); } else { // Address record can have more than one value on multi-homed machines cacheOperation = Operation.Add; this.getCache().addDNSEntry(newRecord); } } else { cachedRecord.resetTTL(newRecord); newRecord = cachedRecord; } } } else { if (!expired) { cacheOperation = Operation.Add; this.getCache().addDNSEntry(newRecord); } } } // Register new service types if (newRecord.getRecordType() == DNSRecordType.TYPE_PTR) { // handle DNSConstants.DNS_META_QUERY records boolean typeAdded = false; if (newRecord.isServicesDiscoveryMetaQuery()) { // The service names are in the alias. if (!expired) { typeAdded = this.registerServiceType(((DNSRecord.Pointer) newRecord).getAlias()); } return; } typeAdded |= this.registerServiceType(newRecord.getName()); if (typeAdded && (cacheOperation == Operation.Noop)) { cacheOperation = Operation.RegisterServiceType; } } // notify the listeners if (cacheOperation != Operation.Noop) { this.updateRecord(now, newRecord, cacheOperation); } } /** * Handle an incoming response. Cache answers, and pass them on to the appropriate questions. * * @exception IOException */ void handleResponse(DNSIncoming msg) throws IOException { final long now = System.currentTimeMillis(); boolean hostConflictDetected = false; boolean serviceConflictDetected = false; for (DNSRecord newRecord : msg.getAllAnswers()) { this.handleRecord(newRecord, now); if (DNSRecordType.TYPE_A.equals(newRecord.getRecordType()) || DNSRecordType.TYPE_AAAA.equals(newRecord.getRecordType())) { hostConflictDetected |= newRecord.handleResponse(this); } else { serviceConflictDetected |= newRecord.handleResponse(this); } } if (hostConflictDetected || serviceConflictDetected) { this.startProber(); } } /** * Handle an incoming query. See if we can answer any part of it given our service infos. * * @param in * @param addr * @param port * @exception IOException */ void handleQuery(DNSIncoming in, InetAddress addr, int port) throws IOException { if (logger.isLoggable(Level.FINE)) { logger.fine(this.getName() + ".handle query: " + in); } // Track known answers boolean conflictDetected = false; final long expirationTime = System.currentTimeMillis() + DNSConstants.KNOWN_ANSWER_TTL; for (DNSRecord answer : in.getAllAnswers()) { conflictDetected |= answer.handleQuery(this, expirationTime); } this.ioLock(); try { if (_plannedAnswer != null) { _plannedAnswer.append(in); } else { DNSIncoming plannedAnswer = in.clone(); if (in.isTruncated()) { _plannedAnswer = plannedAnswer; } this.startResponder(plannedAnswer, port); } } finally { this.ioUnlock(); } final long now = System.currentTimeMillis(); for (DNSRecord answer : in.getAnswers()) { this.handleRecord(answer, now); } if (conflictDetected) { this.startProber(); } } public void respondToQuery(DNSIncoming in) { this.ioLock(); try { if (_plannedAnswer == in) { _plannedAnswer = null; } } finally { this.ioUnlock(); } } /** * Add an answer to a question. Deal with the case when the outgoing packet overflows * * @param in * @param addr * @param port * @param out * @param rec * @return outgoing answer * @exception IOException */ public DNSOutgoing addAnswer(DNSIncoming in, InetAddress addr, int port, DNSOutgoing out, DNSRecord rec) throws IOException { DNSOutgoing newOut = out; if (newOut == null) { newOut = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false, in.getSenderUDPPayload()); } try { newOut.addAnswer(in, rec); } catch (final IOException e) { newOut.setFlags(newOut.getFlags() | DNSConstants.FLAGS_TC); newOut.setId(in.getId()); send(newOut); newOut = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false, in.getSenderUDPPayload()); newOut.addAnswer(in, rec); } return newOut; } /** * Send an outgoing multicast DNS message. * * @param out * @exception IOException */ public void send(DNSOutgoing out) throws IOException { if (!out.isEmpty()) { byte[] message = out.data(); final DatagramPacket packet = new DatagramPacket(message, message.length, _group, DNSConstants.MDNS_PORT); if (logger.isLoggable(Level.FINEST)) { try { final DNSIncoming msg = new DNSIncoming(packet); if (logger.isLoggable(Level.FINEST)) { logger.finest("send(" + this.getName() + ") JmDNS out:" + msg.print(true)); } } catch (final IOException e) { logger.throwing(getClass().toString(), "send(" + this.getName() + ") - JmDNS can not parse what it sends!!!", e); } } final MulticastSocket ms = _socket; if (ms != null && !ms.isClosed()) { ms.send(packet); } } } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#purgeTimer() */ @Override public void purgeTimer() { DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).purgeTimer(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#purgeStateTimer() */ @Override public void purgeStateTimer() { DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).purgeStateTimer(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#cancelTimer() */ @Override public void cancelTimer() { DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).cancelTimer(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#cancelStateTimer() */ @Override public void cancelStateTimer() { DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).cancelStateTimer(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startProber() */ @Override public void startProber() { DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startProber(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startAnnouncer() */ @Override public void startAnnouncer() { DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startAnnouncer(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startRenewer() */ @Override public void startRenewer() { DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startRenewer(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startCanceler() */ @Override public void startCanceler() { DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startCanceler(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startReaper() */ @Override public void startReaper() { DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startReaper(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startServiceInfoResolver(javax.jmdns.impl.ServiceInfoImpl) */ @Override public void startServiceInfoResolver(ServiceInfoImpl info) { DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startServiceInfoResolver(info); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startTypeResolver() */ @Override public void startTypeResolver() { DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startTypeResolver(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startServiceResolver(java.lang.String) */ @Override public void startServiceResolver(String type) { DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startServiceResolver(type); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startResponder(javax.jmdns.impl.DNSIncoming, int) */ @Override public void startResponder(DNSIncoming in, int port) { DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startResponder(in, port); } // REMIND: Why is this not an anonymous inner class? /** * Shutdown operations. */ protected class Shutdown implements Runnable { /** {@inheritDoc} */ @Override public void run() { try { _shutdown = null; close(); } catch (Throwable exception) { System.err.println("Error while shuting down. " + exception); } } } private final Object _recoverLock = new Object(); /** * Recover jmdns when there is an error. */ public void recover() { logger.finer(this.getName() + "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 (this.isClosing() || this.isClosed() || this.isCanceling() || this.isCanceled()) { return; } // We need some definite lock here as we may have multiple timer running in the same thread that will not be stopped by the reentrant lock // in the state object. This is only a problem in this case as we are going to execute in seperate thread so that the timer can clear. synchronized (_recoverLock) { // Stop JmDNS // This protects against recursive calls if (this.cancelState()) { logger.finer(this.getName() + "recover() thread " + Thread.currentThread().getName()); Thread recover = new Thread(this.getName() + ".recover()") { /** * {@inheritDoc} */ @Override public void run() { __recover(); } }; recover.start(); } } } void __recover() { // Synchronize only if we are not already in process to prevent dead locks // if (logger.isLoggable(Level.FINER)) { logger.finer(this.getName() + "recover() Cleanning up"); } logger.warning("RECOVERING"); // Purge the timer this.purgeTimer(); // We need to keep a copy for reregistration final Collection oldServiceInfos = new ArrayList(getServices().values()); // Cancel all services this.unregisterAllServices(); this.disposeServiceCollectors(); this.waitForCanceled(DNSConstants.CLOSE_TIMEOUT); // Purge the canceler timer this.purgeStateTimer(); // // close multicast socket this.closeMulticastSocket(); // this.getCache().clear(); if (logger.isLoggable(Level.FINER)) { logger.finer(this.getName() + "recover() All is clean"); } if (this.isCanceled()) { // // All is clear now start the services // for (ServiceInfo info : oldServiceInfos) { ((ServiceInfoImpl) info).recoverState(); } this.recoverState(); try { this.openMulticastSocket(this.getLocalHost()); this.start(oldServiceInfos); } catch (final Exception exception) { logger.log(Level.WARNING, this.getName() + "recover() Start services exception ", exception); } logger.log(Level.WARNING, this.getName() + "recover() We are back!"); } else { // We have a problem. We could not clear the state. logger.log(Level.WARNING, this.getName() + "recover() Could not recover we are Down!"); if (this.getDelegate() != null) { this.getDelegate().cannotRecoverFromIOError(this.getDns(), oldServiceInfos); } } } public void cleanCache() { long now = System.currentTimeMillis(); for (DNSEntry entry : this.getCache().allValues()) { try { DNSRecord record = (DNSRecord) entry; if (record.isExpired(now)) { this.updateRecord(now, record, Operation.Remove); this.getCache().removeDNSEntry(record); } else if (record.isStale(now)) { // we should query for the record we care about i.e. those in the service collectors this.renewServiceCollector(record); } } catch (Exception exception) { logger.log(Level.SEVERE, this.getName() + ".Error while reaping records: " + entry, exception); logger.severe(this.toString()); } } } /** * {@inheritDoc} */ @Override public void close() { if (this.isClosing()) { return; } if (logger.isLoggable(Level.FINER)) { logger.finer("Cancelling JmDNS: " + this); } // Stop JmDNS // This protects against recursive calls if (this.closeState()) { // We got the tie break now clean up // Stop the timer logger.finer("Canceling the timer"); this.cancelTimer(); // Cancel all services this.unregisterAllServices(); this.disposeServiceCollectors(); if (logger.isLoggable(Level.FINER)) { logger.finer("Wait for JmDNS cancel: " + this); } this.waitForCanceled(DNSConstants.CLOSE_TIMEOUT); // Stop the canceler timer logger.finer("Canceling the state timer"); this.cancelStateTimer(); // Stop the executor _executor.shutdown(); // close socket this.closeMulticastSocket(); // remove the shutdown hook if (_shutdown != null) { Runtime.getRuntime().removeShutdownHook(_shutdown); } if (logger.isLoggable(Level.FINER)) { logger.finer("JmDNS closed."); } } advanceState(null); } /** * {@inheritDoc} */ @Override @Deprecated public void printServices() { System.err.println(toString()); } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder aLog = new StringBuilder(2048); aLog.append("\t---- Local Host -----"); aLog.append("\n\t"); aLog.append(_localHost); aLog.append("\n\t---- Services -----"); for (String key : _services.keySet()) { aLog.append("\n\t\tService: "); aLog.append(key); aLog.append(": "); aLog.append(_services.get(key)); } aLog.append("\n"); aLog.append("\t---- Types ----"); for (String key : _serviceTypes.keySet()) { ServiceTypeEntry subtypes = _serviceTypes.get(key); aLog.append("\n\t\tType: "); aLog.append(subtypes.getType()); aLog.append(": "); aLog.append(subtypes.isEmpty() ? "no subtypes" : subtypes); } aLog.append("\n"); aLog.append(_cache.toString()); aLog.append("\n"); aLog.append("\t---- Service Collectors ----"); for (String key : _serviceCollectors.keySet()) { aLog.append("\n\t\tService Collector: "); aLog.append(key); aLog.append(": "); aLog.append(_serviceCollectors.get(key)); } aLog.append("\n"); aLog.append("\t---- Service Listeners ----"); for (String key : _serviceListeners.keySet()) { aLog.append("\n\t\tService Listener: "); aLog.append(key); aLog.append(": "); aLog.append(_serviceListeners.get(key)); } return aLog.toString(); } /** * {@inheritDoc} */ @Override public ServiceInfo[] list(String type) { return this.list(type, DNSConstants.SERVICE_INFO_TIMEOUT); } /** * {@inheritDoc} */ @Override public ServiceInfo[] list(String type, long timeout) { this.cleanCache(); // 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. String loType = type.toLowerCase(); boolean newCollectorCreated = false; if (this.isCanceling() || this.isCanceled()) { return new ServiceInfo[0]; } ServiceCollector collector = _serviceCollectors.get(loType); if (collector == null) { newCollectorCreated = _serviceCollectors.putIfAbsent(loType, new ServiceCollector(type)) == null; collector = _serviceCollectors.get(loType); if (newCollectorCreated) { this.addServiceListener(type, collector, ListenerStatus.SYNCHONEOUS); } } if (logger.isLoggable(Level.FINER)) { logger.finer(this.getName() + ".collector: " + collector); } // At this stage the collector should never be null but it keeps findbugs happy. return (collector != null ? collector.list(timeout) : new ServiceInfo[0]); } /** * {@inheritDoc} */ @Override public Map listBySubtype(String type) { return this.listBySubtype(type, DNSConstants.SERVICE_INFO_TIMEOUT); } /** * {@inheritDoc} */ @Override public Map listBySubtype(String type, long timeout) { Map> map = new HashMap>(5); for (ServiceInfo info : this.list(type, timeout)) { String subtype = info.getSubtype().toLowerCase(); if (!map.containsKey(subtype)) { map.put(subtype, new ArrayList(10)); } map.get(subtype).add(info); } Map result = new HashMap(map.size()); for (String subtype : map.keySet()) { List infoForSubType = map.get(subtype); result.put(subtype, infoForSubType.toArray(new ServiceInfo[infoForSubType.size()])); } return result; } /** * This method disposes all ServiceCollector instances which have been created by calls to method list(type). * * @see #list */ private void disposeServiceCollectors() { if (logger.isLoggable(Level.FINER)) { logger.finer("disposeServiceCollectors()"); } for (String type : _serviceCollectors.keySet()) { ServiceCollector collector = _serviceCollectors.get(type); if (collector != null) { this.removeServiceListener(type, collector); _serviceCollectors.remove(type, collector); } } } /** * Instances of ServiceCollector are used internally to speed up the performance of method list(type). * * @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 ConcurrentMap _infos; /** * A set of collected service event waiting to be resolved. */ private final ConcurrentMap _events; /** * This is the type we are listening for (only used for debugging). */ private final String _type; /** * This is used to force a wait on the first invocation of list. */ private volatile boolean _needToWaitForInfos; public ServiceCollector(String type) { super(); _infos = new ConcurrentHashMap(); _events = new ConcurrentHashMap(); _type = type; _needToWaitForInfos = true; } /** * A service has been added. * * @param event * service event */ @Override public void serviceAdded(ServiceEvent event) { synchronized (this) { ServiceInfo info = event.getInfo(); if ((info != null) && (info.hasData())) { _infos.put(event.getName(), info); } else { String subtype = (info != null ? info.getSubtype() : ""); info = ((JmDNSImpl) event.getDNS()).resolveServiceInfo(event.getType(), event.getName(), subtype, true); if (info != null) { _infos.put(event.getName(), info); } else { _events.put(event.getName(), event); } } } } /** * A service has been removed. * * @param event * service event */ @Override public void serviceRemoved(ServiceEvent event) { synchronized (this) { _infos.remove(event.getName()); _events.remove(event.getName()); } } /** * A service has been resolved. Its details are now available in the ServiceInfo record. * * @param event * service event */ @Override public void serviceResolved(ServiceEvent event) { synchronized (this) { _infos.put(event.getName(), event.getInfo()); _events.remove(event.getName()); } } /** * Returns an array of all service infos which have been collected by this ServiceCollector. * * @param timeout * timeout if the info list is empty. * @return Service Info array */ public ServiceInfo[] list(long timeout) { if (_infos.isEmpty() || !_events.isEmpty() || _needToWaitForInfos) { long loops = (timeout / 200L); if (loops < 1) { loops = 1; } for (int i = 0; i < loops; i++) { try { Thread.sleep(200); } catch (final InterruptedException e) { /* Stub */ } if (_events.isEmpty() && !_infos.isEmpty() && !_needToWaitForInfos) { break; } } } _needToWaitForInfos = false; return _infos.values().toArray(new ServiceInfo[_infos.size()]); } /** * {@inheritDoc} */ @Override public String toString() { final StringBuffer aLog = new StringBuffer(); aLog.append("\n\tType: "); aLog.append(_type); if (_infos.isEmpty()) { aLog.append("\n\tNo services collected."); } else { aLog.append("\n\tServices"); for (String key : _infos.keySet()) { aLog.append("\n\t\tService: "); aLog.append(key); aLog.append(": "); aLog.append(_infos.get(key)); } } if (_events.isEmpty()) { aLog.append("\n\tNo event queued."); } else { aLog.append("\n\tEvents"); for (String key : _events.keySet()) { aLog.append("\n\t\tEvent: "); aLog.append(key); aLog.append(": "); aLog.append(_events.get(key)); } } return aLog.toString(); } } static String toUnqualifiedName(String type, String qualifiedName) { String loType = type.toLowerCase(); String loQualifiedName = qualifiedName.toLowerCase(); if (loQualifiedName.endsWith(loType) && !(loQualifiedName.equals(loType))) { return qualifiedName.substring(0, qualifiedName.length() - type.length() - 1); } return qualifiedName; } 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 ioLock() { _ioLock.lock(); } public void ioUnlock() { _ioLock.unlock(); } 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 MulticastSocket getSocket() { return _socket; } public InetAddress getGroup() { return _group; } @Override public Delegate getDelegate() { return this._delegate; } @Override public Delegate setDelegate(Delegate delegate) { Delegate previous = this._delegate; this._delegate = delegate; return previous; } } jmdns-3.4.1/src/javax/jmdns/impl/package-info.java0000644000175000017500000000003311625404056021646 0ustar mathieumathieupackage javax.jmdns.impl; jmdns-3.4.1/src/javax/jmdns/impl/DNSTaskStarter.java0000644000175000017500000003274411625404056022154 0ustar mathieumathieu/** * */ package javax.jmdns.impl; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicReference; import javax.jmdns.impl.tasks.RecordReaper; import javax.jmdns.impl.tasks.Responder; import javax.jmdns.impl.tasks.resolver.ServiceInfoResolver; import javax.jmdns.impl.tasks.resolver.ServiceResolver; import javax.jmdns.impl.tasks.resolver.TypeResolver; import javax.jmdns.impl.tasks.state.Announcer; import javax.jmdns.impl.tasks.state.Canceler; import javax.jmdns.impl.tasks.state.Prober; import javax.jmdns.impl.tasks.state.Renewer; /** * This class is used by JmDNS to start the various task required to run the DNS discovery. This interface is only there in order to support MANET modifications. *

* Note: This is not considered as part of the general public API of JmDNS. *

* * @author Pierre Frisch */ public interface DNSTaskStarter { /** * DNSTaskStarter.Factory enable the creation of new instance of DNSTaskStarter. */ public static final class Factory { private static volatile Factory _instance; private final ConcurrentMap _instances; /** * This interface defines a delegate to the DNSTaskStarter class to enable subclassing. */ public static interface ClassDelegate { /** * Allows the delegate the opportunity to construct and return a different DNSTaskStarter. * * @param jmDNSImpl * jmDNS instance * @return Should return a new DNSTaskStarter Object. * @see #classDelegate() * @see #setClassDelegate(ClassDelegate anObject) */ public DNSTaskStarter newDNSTaskStarter(JmDNSImpl jmDNSImpl); } private static final AtomicReference _databaseClassDelegate = new AtomicReference(); private Factory() { super(); _instances = new ConcurrentHashMap(20); } /** * Assigns delegate as DNSTaskStarter's class delegate. The class delegate is optional. * * @param delegate * The object to set as DNSTaskStarter's class delegate. * @see #classDelegate() * @see DNSTaskStarter.Factory.ClassDelegate */ public static void setClassDelegate(Factory.ClassDelegate delegate) { _databaseClassDelegate.set(delegate); } /** * Returns DNSTaskStarter's class delegate. * * @return DNSTaskStarter's class delegate. * @see #setClassDelegate(ClassDelegate anObject) * @see DNSTaskStarter.Factory.ClassDelegate */ public static Factory.ClassDelegate classDelegate() { return _databaseClassDelegate.get(); } /** * Returns a new instance of DNSTaskStarter using the class delegate if it exists. * * @param jmDNSImpl * jmDNS instance * @return new instance of DNSTaskStarter */ protected static DNSTaskStarter newDNSTaskStarter(JmDNSImpl jmDNSImpl) { DNSTaskStarter instance = null; Factory.ClassDelegate delegate = _databaseClassDelegate.get(); if (delegate != null) { instance = delegate.newDNSTaskStarter(jmDNSImpl); } return (instance != null ? instance : new DNSTaskStarterImpl(jmDNSImpl)); } /** * Return the instance of the DNSTaskStarter Factory. * * @return DNSTaskStarter Factory */ public static Factory getInstance() { if (_instance == null) { synchronized (DNSTaskStarter.Factory.class) { if (_instance == null) { _instance = new Factory(); } } } return _instance; } /** * Return the instance of the DNSTaskStarter for the JmDNS. * * @param jmDNSImpl * jmDNS instance * @return the DNSTaskStarter */ public DNSTaskStarter getStarter(JmDNSImpl jmDNSImpl) { DNSTaskStarter starter = _instances.get(jmDNSImpl); if (starter == null) { _instances.putIfAbsent(jmDNSImpl, newDNSTaskStarter(jmDNSImpl)); starter = _instances.get(jmDNSImpl); } return starter; } } public static final class DNSTaskStarterImpl implements DNSTaskStarter { private final JmDNSImpl _jmDNSImpl; /** * The timer is used to dispatch all outgoing messages of JmDNS. It is also used to dispatch maintenance tasks for the DNS cache. */ private final Timer _timer; /** * The timer is used to dispatch maintenance tasks for the DNS cache. */ private final Timer _stateTimer; public static class StarterTimer extends Timer { // This is needed because in some case we cancel the timers before all the task have finished running and in some case they will try to reschedule private volatile boolean _cancelled; /** * */ public StarterTimer() { super(); _cancelled = false; } /** * @param isDaemon */ public StarterTimer(boolean isDaemon) { super(isDaemon); _cancelled = false; } /** * @param name * @param isDaemon */ public StarterTimer(String name, boolean isDaemon) { super(name, isDaemon); _cancelled = false; } /** * @param name */ public StarterTimer(String name) { super(name); _cancelled = false; } /* * (non-Javadoc) * @see java.util.Timer#cancel() */ @Override public synchronized void cancel() { if (_cancelled) return; _cancelled = true; super.cancel(); } /* * (non-Javadoc) * @see java.util.Timer#schedule(java.util.TimerTask, long) */ @Override public synchronized void schedule(TimerTask task, long delay) { if (_cancelled) return; super.schedule(task, delay); } /* * (non-Javadoc) * @see java.util.Timer#schedule(java.util.TimerTask, java.util.Date) */ @Override public synchronized void schedule(TimerTask task, Date time) { if (_cancelled) return; super.schedule(task, time); } /* * (non-Javadoc) * @see java.util.Timer#schedule(java.util.TimerTask, long, long) */ @Override public synchronized void schedule(TimerTask task, long delay, long period) { if (_cancelled) return; super.schedule(task, delay, period); } /* * (non-Javadoc) * @see java.util.Timer#schedule(java.util.TimerTask, java.util.Date, long) */ @Override public synchronized void schedule(TimerTask task, Date firstTime, long period) { if (_cancelled) return; super.schedule(task, firstTime, period); } /* * (non-Javadoc) * @see java.util.Timer#scheduleAtFixedRate(java.util.TimerTask, long, long) */ @Override public synchronized void scheduleAtFixedRate(TimerTask task, long delay, long period) { if (_cancelled) return; super.scheduleAtFixedRate(task, delay, period); } /* * (non-Javadoc) * @see java.util.Timer#scheduleAtFixedRate(java.util.TimerTask, java.util.Date, long) */ @Override public synchronized void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) { if (_cancelled) return; super.scheduleAtFixedRate(task, firstTime, period); } } public DNSTaskStarterImpl(JmDNSImpl jmDNSImpl) { super(); _jmDNSImpl = jmDNSImpl; _timer = new StarterTimer("JmDNS(" + _jmDNSImpl.getName() + ").Timer", true); _stateTimer = new StarterTimer("JmDNS(" + _jmDNSImpl.getName() + ").State.Timer", false); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#purgeTimer() */ @Override public void purgeTimer() { _timer.purge(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#purgeStateTimer() */ @Override public void purgeStateTimer() { _stateTimer.purge(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#cancelTimer() */ @Override public void cancelTimer() { _timer.cancel(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#cancelStateTimer() */ @Override public void cancelStateTimer() { _stateTimer.cancel(); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startProber() */ @Override public void startProber() { new Prober(_jmDNSImpl).start(_stateTimer); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startAnnouncer() */ @Override public void startAnnouncer() { new Announcer(_jmDNSImpl).start(_stateTimer); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startRenewer() */ @Override public void startRenewer() { new Renewer(_jmDNSImpl).start(_stateTimer); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startCanceler() */ @Override public void startCanceler() { new Canceler(_jmDNSImpl).start(_stateTimer); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startReaper() */ @Override public void startReaper() { new RecordReaper(_jmDNSImpl).start(_timer); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startServiceInfoResolver(javax.jmdns.impl.ServiceInfoImpl) */ @Override public void startServiceInfoResolver(ServiceInfoImpl info) { new ServiceInfoResolver(_jmDNSImpl, info).start(_timer); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startTypeResolver() */ @Override public void startTypeResolver() { new TypeResolver(_jmDNSImpl).start(_timer); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startServiceResolver(java.lang.String) */ @Override public void startServiceResolver(String type) { new ServiceResolver(_jmDNSImpl, type).start(_timer); } /* * (non-Javadoc) * @see javax.jmdns.impl.DNSTaskStarter#startResponder(javax.jmdns.impl.DNSIncoming, int) */ @Override public void startResponder(DNSIncoming in, int port) { new Responder(_jmDNSImpl, in, port).start(_timer); } } /** * Purge the general task timer */ public void purgeTimer(); /** * Purge the state task timer */ public void purgeStateTimer(); /** * Cancel the generals task timer */ public void cancelTimer(); /** * Cancel the state task timer */ public void cancelStateTimer(); /** * Start a new prober task */ public void startProber(); /** * Start a new announcer task */ public void startAnnouncer(); /** * Start a new renewer task */ public void startRenewer(); /** * Start a new canceler task */ public void startCanceler(); /** * Start a new reaper task. There is only supposed to be one reaper running at a time. */ public void startReaper(); /** * Start a new service info resolver task * * @param info * service info to resolve */ public void startServiceInfoResolver(ServiceInfoImpl info); /** * Start a new service type resolver task */ public void startTypeResolver(); /** * Start a new service resolver task * * @param type * service type to resolve */ public void startServiceResolver(String type); /** * Start a new responder task * * @param in * incoming message * @param port * incoming port */ public void startResponder(DNSIncoming in, int port); } jmdns-3.4.1/src/javax/jmdns/impl/DNSStatefulObject.java0000644000175000017500000003760311625404056022622 0ustar mathieumathieu// Licensed under Apache License version 2.0 package javax.jmdns.impl; import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.impl.constants.DNSState; import javax.jmdns.impl.tasks.DNSTask; /** * Sets of methods to manage the state machine.
* Implementation note: This interface is accessed from multiple threads. The implementation must be thread safe. * * @author Pierre Frisch */ public interface DNSStatefulObject { /** * This class define a semaphore. On this multiple threads can wait the arrival of one event. Thread wait for a maximum defined by the timeout. *

* Implementation note: this class is based on {@link java.util.concurrent.Semaphore} so that they can be released by the timeout timer. *

* * @author Pierre Frisch */ public static final class DNSStatefulObjectSemaphore { private static Logger logger = Logger.getLogger(DNSStatefulObjectSemaphore.class.getName()); private final String _name; private final ConcurrentMap _semaphores; /** * @param name * Semaphore name for debugging purposes. */ public DNSStatefulObjectSemaphore(String name) { super(); _name = name; _semaphores = new ConcurrentHashMap(50); } /** * Blocks the current thread until the event arrives or the timeout expires. * * @param timeout * wait period for the event */ public void waitForEvent(long timeout) { Thread thread = Thread.currentThread(); Semaphore semaphore = _semaphores.get(thread); if (semaphore == null) { semaphore = new Semaphore(1, true); semaphore.drainPermits(); _semaphores.putIfAbsent(thread, semaphore); } semaphore = _semaphores.get(thread); try { semaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException exception) { logger.log(Level.FINER, "Exception ", exception); } } /** * Signals the semaphore when the event arrives. */ public void signalEvent() { Collection semaphores = _semaphores.values(); for (Semaphore semaphore : semaphores) { semaphore.release(); semaphores.remove(semaphore); } } @Override public String toString() { StringBuilder aLog = new StringBuilder(1000); aLog.append("Semaphore: "); aLog.append(this._name); if (_semaphores.size() == 0) { aLog.append(" no semaphores."); } else { aLog.append(" semaphores:\n"); for (Thread thread : _semaphores.keySet()) { aLog.append("\tThread: "); aLog.append(thread.getName()); aLog.append(' '); aLog.append(_semaphores.get(thread)); aLog.append('\n'); } } return aLog.toString(); } } public static class DefaultImplementation extends ReentrantLock implements DNSStatefulObject { private static Logger logger = Logger.getLogger(DefaultImplementation.class.getName()); private static final long serialVersionUID = -3264781576883412227L; private volatile JmDNSImpl _dns; protected volatile DNSTask _task; protected volatile DNSState _state; private final DNSStatefulObjectSemaphore _announcing; private final DNSStatefulObjectSemaphore _canceling; public DefaultImplementation() { super(); _dns = null; _task = null; _state = DNSState.PROBING_1; _announcing = new DNSStatefulObjectSemaphore("Announce"); _canceling = new DNSStatefulObjectSemaphore("Cancel"); } /** * {@inheritDoc} */ @Override public JmDNSImpl getDns() { return this._dns; } protected void setDns(JmDNSImpl dns) { this._dns = dns; } /** * {@inheritDoc} */ @Override public void associateWithTask(DNSTask task, DNSState state) { if (this._task == null && this._state == state) { this.lock(); try { if (this._task == null && this._state == state) { this.setTask(task); } } finally { this.unlock(); } } } /** * {@inheritDoc} */ @Override public void removeAssociationWithTask(DNSTask task) { if (this._task == task) { this.lock(); try { if (this._task == task) { this.setTask(null); } } finally { this.unlock(); } } } /** * {@inheritDoc} */ @Override public boolean isAssociatedWithTask(DNSTask task, DNSState state) { this.lock(); try { return this._task == task && this._state == state; } finally { this.unlock(); } } protected void setTask(DNSTask task) { this._task = task; } /** * @param state * the state to set */ protected void setState(DNSState state) { this.lock(); try { this._state = state; if (this.isAnnounced()) { _announcing.signalEvent(); } if (this.isCanceled()) { _canceling.signalEvent(); // clear any waiting announcing _announcing.signalEvent(); } } finally { this.unlock(); } } /** * {@inheritDoc} */ @Override public boolean advanceState(DNSTask task) { boolean result = true; if (this._task == task) { this.lock(); try { if (this._task == task) { this.setState(this._state.advance()); } else { logger.warning("Trying to advance state whhen not the owner. owner: " + this._task + " perpetrator: " + task); } } finally { this.unlock(); } } return result; } /** * {@inheritDoc} */ @Override public boolean revertState() { boolean result = true; if (!this.willCancel()) { this.lock(); try { if (!this.willCancel()) { this.setState(this._state.revert()); this.setTask(null); } } finally { this.unlock(); } } return result; } /** * {@inheritDoc} */ @Override public boolean cancelState() { boolean result = false; if (!this.willCancel()) { this.lock(); try { if (!this.willCancel()) { this.setState(DNSState.CANCELING_1); this.setTask(null); result = true; } } finally { this.unlock(); } } return result; } /** * {@inheritDoc} */ @Override public boolean closeState() { boolean result = false; if (!this.willClose()) { this.lock(); try { if (!this.willClose()) { this.setState(DNSState.CLOSING); this.setTask(null); result = true; } } finally { this.unlock(); } } return result; } /** * {@inheritDoc} */ @Override public boolean recoverState() { boolean result = false; this.lock(); try { this.setState(DNSState.PROBING_1); this.setTask(null); } finally { this.unlock(); } return result; } /** * {@inheritDoc} */ @Override public boolean isProbing() { return this._state.isProbing(); } /** * {@inheritDoc} */ @Override public boolean isAnnouncing() { return this._state.isAnnouncing(); } /** * {@inheritDoc} */ @Override public boolean isAnnounced() { return this._state.isAnnounced(); } /** * {@inheritDoc} */ @Override public boolean isCanceling() { return this._state.isCanceling(); } /** * {@inheritDoc} */ @Override public boolean isCanceled() { return this._state.isCanceled(); } /** * {@inheritDoc} */ @Override public boolean isClosing() { return this._state.isClosing(); } /** * {@inheritDoc} */ @Override public boolean isClosed() { return this._state.isClosed(); } private boolean willCancel() { return this._state.isCanceled() || this._state.isCanceling(); } private boolean willClose() { return this._state.isClosed() || this._state.isClosing(); } /** * {@inheritDoc} */ @Override public boolean waitForAnnounced(long timeout) { if (!this.isAnnounced() && !this.willCancel()) { _announcing.waitForEvent(timeout); } if (!this.isAnnounced()) { if (this.willCancel() || this.willClose()) { logger.fine("Wait for announced cancelled: " + this); } else { logger.warning("Wait for announced timed out: " + this); } } return this.isAnnounced(); } /** * {@inheritDoc} */ @Override public boolean waitForCanceled(long timeout) { if (!this.isCanceled()) { _canceling.waitForEvent(timeout); } if (!this.isCanceled() && !this.willClose()) { logger.warning("Wait for canceled timed out: " + this); } return this.isCanceled(); } /** * {@inheritDoc} */ @Override public String toString() { return (_dns != null ? "DNS: " + _dns.getName() : "NO DNS") + " state: " + _state + " task: " + _task; } } /** * Returns the DNS associated with this object. * * @return DNS resolver */ public JmDNSImpl getDns(); /** * Sets the task associated with this Object. * * @param task * associated task * @param state * state of the task */ public void associateWithTask(DNSTask task, DNSState state); /** * Remove the association of the task with this Object. * * @param task * associated task */ public void removeAssociationWithTask(DNSTask task); /** * Checks if this object is associated with the task and in the same state. * * @param task * associated task * @param state * state of the task * @return true is the task is associated with this object, false otherwise. */ public boolean isAssociatedWithTask(DNSTask task, DNSState state); /** * Sets the state and notifies all objects that wait on the ServiceInfo. * * @param task * associated task * @return truefalse otherwise. * @see DNSState#advance() */ public boolean advanceState(DNSTask task); /** * Sets the state and notifies all objects that wait on the ServiceInfo. * * @return truefalse otherwise. * @see DNSState#revert() */ public boolean revertState(); /** * Sets the state and notifies all objects that wait on the ServiceInfo. * * @return truefalse otherwise. */ public boolean cancelState(); /** * Sets the state and notifies all objects that wait on the ServiceInfo. * * @return truefalse otherwise. */ public boolean closeState(); /** * Sets the state and notifies all objects that wait on the ServiceInfo. * * @return truefalse otherwise. */ public boolean recoverState(); /** * Returns true, if this is a probing state. * * @return true if probing state, false otherwise */ public boolean isProbing(); /** * Returns true, if this is an announcing state. * * @return true if announcing state, false otherwise */ public boolean isAnnouncing(); /** * Returns true, if this is an announced state. * * @return true if announced state, false otherwise */ public boolean isAnnounced(); /** * Returns true, if this is a canceling state. * * @return true if canceling state, false otherwise */ public boolean isCanceling(); /** * Returns true, if this is a canceled state. * * @return true if canceled state, false otherwise */ public boolean isCanceled(); /** * Returns true, if this is a closing state. * * @return true if closing state, false otherwise */ public boolean isClosing(); /** * Returns true, if this is a closed state. * * @return true if closed state, false otherwise */ public boolean isClosed(); /** * Waits for the object to be announced. * * @param timeout * the maximum time to wait in milliseconds. * @return true if the object is announced, false otherwise */ public boolean waitForAnnounced(long timeout); /** * Waits for the object to be canceled. * * @param timeout * the maximum time to wait in milliseconds. * @return true if the object is canceled, false otherwise */ public boolean waitForCanceled(long timeout); } jmdns-3.4.1/src/javax/jmdns/impl/tasks/0000755000175000017500000000000011625404056017610 5ustar mathieumathieujmdns-3.4.1/src/javax/jmdns/impl/tasks/state/0000755000175000017500000000000011625404056020730 5ustar mathieumathieujmdns-3.4.1/src/javax/jmdns/impl/tasks/state/DNSStateTask.java0000644000175000017500000001346511625404056024054 0ustar mathieumathieu// Licensed under Apache License version 2.0 package javax.jmdns.impl.tasks.state; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.ServiceInfo; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.DNSStatefulObject; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.ServiceInfoImpl; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSState; import javax.jmdns.impl.tasks.DNSTask; /** * This is the root class for all state tasks. These tasks work with objects that implements the {@link javax.jmdns.impl.DNSStatefulObject} interface and therefore participate in the state machine. * * @author Pierre Frisch */ public abstract class DNSStateTask extends DNSTask { static Logger logger1 = Logger.getLogger(DNSStateTask.class.getName()); /** * By setting a 0 ttl we effectively expire the record. */ private final int _ttl; private static int _defaultTTL = DNSConstants.DNS_TTL; /** * The state of the task. */ private DNSState _taskState = null; public abstract String getTaskDescription(); public static int defaultTTL() { return _defaultTTL; } /** * For testing only do not use in production. * * @param value */ public static void setDefaultTTL(int value) { _defaultTTL = value; } /** * @param jmDNSImpl * @param ttl */ public DNSStateTask(JmDNSImpl jmDNSImpl, int ttl) { super(jmDNSImpl); _ttl = ttl; } /** * @return the ttl */ public int getTTL() { return _ttl; } /** * Associate the DNS host and the service infos with this task if not already associated and in the same state. * * @param state * target state */ protected void associate(DNSState state) { synchronized (this.getDns()) { this.getDns().associateWithTask(this, state); } for (ServiceInfo serviceInfo : this.getDns().getServices().values()) { ((ServiceInfoImpl) serviceInfo).associateWithTask(this, state); } } /** * Remove the DNS host and service info association with this task. */ protected void removeAssociation() { // Remove association from host to this synchronized (this.getDns()) { this.getDns().removeAssociationWithTask(this); } // Remove associations from services to this for (ServiceInfo serviceInfo : this.getDns().getServices().values()) { ((ServiceInfoImpl) serviceInfo).removeAssociationWithTask(this); } } @Override public void run() { DNSOutgoing out = this.createOugoing(); try { if (!this.checkRunCondition()) { this.cancel(); return; } List stateObjects = new ArrayList(); // send probes for JmDNS itself synchronized (this.getDns()) { if (this.getDns().isAssociatedWithTask(this, this.getTaskState())) { logger1.finer(this.getName() + ".run() JmDNS " + this.getTaskDescription() + " " + this.getDns().getName()); stateObjects.add(this.getDns()); out = this.buildOutgoingForDNS(out); } } // send probes for services for (ServiceInfo serviceInfo : this.getDns().getServices().values()) { ServiceInfoImpl info = (ServiceInfoImpl) serviceInfo; synchronized (info) { if (info.isAssociatedWithTask(this, this.getTaskState())) { logger1.fine(this.getName() + ".run() JmDNS " + this.getTaskDescription() + " " + info.getQualifiedName()); stateObjects.add(info); out = this.buildOutgoingForInfo(info, out); } } } if (!out.isEmpty()) { logger1.finer(this.getName() + ".run() JmDNS " + this.getTaskDescription() + " #" + this.getTaskState()); this.getDns().send(out); // Advance the state of objects. this.advanceObjectsState(stateObjects); } else { // Advance the state of objects. this.advanceObjectsState(stateObjects); // 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) { logger1.log(Level.WARNING, this.getName() + ".run() exception ", e); this.recoverTask(e); } this.advanceTask(); } protected abstract boolean checkRunCondition(); protected abstract DNSOutgoing buildOutgoingForDNS(DNSOutgoing out) throws IOException; protected abstract DNSOutgoing buildOutgoingForInfo(ServiceInfoImpl info, DNSOutgoing out) throws IOException; protected abstract DNSOutgoing createOugoing(); protected void advanceObjectsState(List list) { if (list != null) { for (DNSStatefulObject object : list) { synchronized (object) { object.advanceState(this); } } } } protected abstract void recoverTask(Throwable e); protected abstract void advanceTask(); /** * @return the taskState */ protected DNSState getTaskState() { return this._taskState; } /** * @param taskState * the taskState to set */ protected void setTaskState(DNSState taskState) { this._taskState = taskState; } } jmdns-3.4.1/src/javax/jmdns/impl/tasks/state/Announcer.java0000644000175000017500000001037111625404056023525 0ustar mathieumathieu// Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns.impl.tasks.state; import java.io.IOException; import java.util.Timer; import java.util.logging.Logger; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.DNSRecord; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.ServiceInfoImpl; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSState; /** * 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. *

* When the announcer has run two times, it finishes. */ public class Announcer extends DNSStateTask { static Logger logger = Logger.getLogger(Announcer.class.getName()); public Announcer(JmDNSImpl jmDNSImpl) { super(jmDNSImpl, defaultTTL()); this.setTaskState(DNSState.ANNOUNCING_1); this.associate(DNSState.ANNOUNCING_1); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#getName() */ @Override public String getName() { return "Announcer(" + (this.getDns() != null ? this.getDns().getName() : "") + ")"; } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return super.toString() + " state: " + this.getTaskState(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#start(java.util.Timer) */ @Override public void start(Timer timer) { if (!this.getDns().isCanceling() && !this.getDns().isCanceled()) { timer.schedule(this, DNSConstants.ANNOUNCE_WAIT_INTERVAL, DNSConstants.ANNOUNCE_WAIT_INTERVAL); } } @Override public boolean cancel() { this.removeAssociation(); return super.cancel(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#getTaskDescription() */ @Override public String getTaskDescription() { return "announcing"; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#checkRunCondition() */ @Override protected boolean checkRunCondition() { return !this.getDns().isCanceling() && !this.getDns().isCanceled(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#createOugoing() */ @Override protected DNSOutgoing createOugoing() { return new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#buildOutgoingForDNS(javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing buildOutgoingForDNS(DNSOutgoing out) throws IOException { DNSOutgoing newOut = out; for (DNSRecord answer : this.getDns().getLocalHost().answers(DNSRecordClass.UNIQUE, this.getTTL())) { newOut = this.addAnswer(newOut, null, answer); } return newOut; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#buildOutgoingForInfo(javax.jmdns.impl.ServiceInfoImpl, javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing buildOutgoingForInfo(ServiceInfoImpl info, DNSOutgoing out) throws IOException { DNSOutgoing newOut = out; for (DNSRecord answer : info.answers(DNSRecordClass.UNIQUE, this.getTTL(), this.getDns().getLocalHost())) { newOut = this.addAnswer(newOut, null, answer); } return newOut; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#recoverTask(java.lang.Throwable) */ @Override protected void recoverTask(Throwable e) { this.getDns().recover(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#advanceTask() */ @Override protected void advanceTask() { this.setTaskState(this.getTaskState().advance()); if (!this.getTaskState().isAnnouncing()) { this.cancel(); this.getDns().startRenewer(); } } }jmdns-3.4.1/src/javax/jmdns/impl/tasks/state/Canceler.java0000644000175000017500000000760111625404056023313 0ustar mathieumathieu// Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns.impl.tasks.state; import java.io.IOException; import java.util.Timer; import java.util.logging.Logger; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.DNSRecord; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.ServiceInfoImpl; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSState; /** * The Canceler sends two announces with TTL=0 for the specified services. */ public class Canceler extends DNSStateTask { static Logger logger = Logger.getLogger(Canceler.class.getName()); public Canceler(JmDNSImpl jmDNSImpl) { super(jmDNSImpl, 0); this.setTaskState(DNSState.CANCELING_1); this.associate(DNSState.CANCELING_1); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#getName() */ @Override public String getName() { return "Canceler(" + (this.getDns() != null ? this.getDns().getName() : "") + ")"; } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return super.toString() + " state: " + this.getTaskState(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#start(java.util.Timer) */ @Override public void start(Timer timer) { timer.schedule(this, 0, DNSConstants.ANNOUNCE_WAIT_INTERVAL); } /* * (non-Javadoc) * @see java.util.TimerTask#cancel() */ @Override public boolean cancel() { this.removeAssociation(); return super.cancel(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#getTaskDescription() */ @Override public String getTaskDescription() { return "canceling"; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#checkRunCondition() */ @Override protected boolean checkRunCondition() { return true; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#createOugoing() */ @Override protected DNSOutgoing createOugoing() { return new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#buildOutgoingForDNS(javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing buildOutgoingForDNS(DNSOutgoing out) throws IOException { DNSOutgoing newOut = out; for (DNSRecord answer : this.getDns().getLocalHost().answers(DNSRecordClass.UNIQUE, this.getTTL())) { newOut = this.addAnswer(newOut, null, answer); } return newOut; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#buildOutgoingForInfo(javax.jmdns.impl.ServiceInfoImpl, javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing buildOutgoingForInfo(ServiceInfoImpl info, DNSOutgoing out) throws IOException { DNSOutgoing newOut = out; for (DNSRecord answer : info.answers(DNSRecordClass.UNIQUE, this.getTTL(), this.getDns().getLocalHost())) { newOut = this.addAnswer(newOut, null, answer); } return newOut; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#recoverTask(java.lang.Throwable) */ @Override protected void recoverTask(Throwable e) { this.getDns().recover(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#advanceTask() */ @Override protected void advanceTask() { this.setTaskState(this.getTaskState().advance()); if (!this.getTaskState().isCanceling()) { cancel(); } } }jmdns-3.4.1/src/javax/jmdns/impl/tasks/state/Prober.java0000644000175000017500000001322311625404056023025 0ustar mathieumathieu// Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns.impl.tasks.state; import java.io.IOException; import java.util.Timer; import java.util.logging.Logger; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.DNSQuestion; import javax.jmdns.impl.DNSRecord; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.ServiceInfoImpl; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; import javax.jmdns.impl.constants.DNSState; /** * 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. *

* 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 to cancel itself. */ public class Prober extends DNSStateTask { static Logger logger = Logger.getLogger(Prober.class.getName()); public Prober(JmDNSImpl jmDNSImpl) { super(jmDNSImpl, defaultTTL()); this.setTaskState(DNSState.PROBING_1); this.associate(DNSState.PROBING_1); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#getName() */ @Override public String getName() { return "Prober(" + (this.getDns() != null ? this.getDns().getName() : "") + ")"; } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return super.toString() + " state: " + this.getTaskState(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#start(java.util.Timer) */ @Override public void start(Timer timer) { long now = System.currentTimeMillis(); if (now - this.getDns().getLastThrottleIncrement() < DNSConstants.PROBE_THROTTLE_COUNT_INTERVAL) { this.getDns().setThrottle(this.getDns().getThrottle() + 1); } else { this.getDns().setThrottle(1); } this.getDns().setLastThrottleIncrement(now); if (this.getDns().isAnnounced() && this.getDns().getThrottle() < DNSConstants.PROBE_THROTTLE_COUNT) { timer.schedule(this, JmDNSImpl.getRandom().nextInt(1 + DNSConstants.PROBE_WAIT_INTERVAL), DNSConstants.PROBE_WAIT_INTERVAL); } else if (!this.getDns().isCanceling() && !this.getDns().isCanceled()) { timer.schedule(this, DNSConstants.PROBE_CONFLICT_INTERVAL, DNSConstants.PROBE_CONFLICT_INTERVAL); } } @Override public boolean cancel() { this.removeAssociation(); return super.cancel(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#getTaskDescription() */ @Override public String getTaskDescription() { return "probing"; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#checkRunCondition() */ @Override protected boolean checkRunCondition() { return !this.getDns().isCanceling() && !this.getDns().isCanceled(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#createOugoing() */ @Override protected DNSOutgoing createOugoing() { return new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#buildOutgoingForDNS(javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing buildOutgoingForDNS(DNSOutgoing out) throws IOException { DNSOutgoing newOut = out; newOut.addQuestion(DNSQuestion.newQuestion(this.getDns().getLocalHost().getName(), DNSRecordType.TYPE_ANY, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); for (DNSRecord answer : this.getDns().getLocalHost().answers(DNSRecordClass.NOT_UNIQUE, this.getTTL())) { newOut = this.addAuthoritativeAnswer(newOut, answer); } return newOut; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#buildOutgoingForInfo(javax.jmdns.impl.ServiceInfoImpl, javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing buildOutgoingForInfo(ServiceInfoImpl info, DNSOutgoing out) throws IOException { DNSOutgoing newOut = out; newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(info.getQualifiedName(), DNSRecordType.TYPE_ANY, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); // 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 newOut = this.addAuthoritativeAnswer(newOut, new DNSRecord.Service(info.getQualifiedName(), DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, this.getTTL(), info.getPriority(), info.getWeight(), info.getPort(), this.getDns().getLocalHost() .getName())); return newOut; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#recoverTask(java.lang.Throwable) */ @Override protected void recoverTask(Throwable e) { this.getDns().recover(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#advanceTask() */ @Override protected void advanceTask() { this.setTaskState(this.getTaskState().advance()); if (!this.getTaskState().isProbing()) { cancel(); this.getDns().startAnnouncer(); } } }jmdns-3.4.1/src/javax/jmdns/impl/tasks/state/package-info.java0000644000175000017500000000004711625404056024120 0ustar mathieumathieupackage javax.jmdns.impl.tasks.state; jmdns-3.4.1/src/javax/jmdns/impl/tasks/state/Renewer.java0000644000175000017500000001001111625404056023173 0ustar mathieumathieu// Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns.impl.tasks.state; import java.io.IOException; import java.util.Timer; import java.util.logging.Logger; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.DNSRecord; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.ServiceInfoImpl; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSState; /** * The Renewer is there to send renewal announcement when the record expire for ours infos. */ public class Renewer extends DNSStateTask { static Logger logger = Logger.getLogger(Renewer.class.getName()); public Renewer(JmDNSImpl jmDNSImpl) { super(jmDNSImpl, defaultTTL()); this.setTaskState(DNSState.ANNOUNCED); this.associate(DNSState.ANNOUNCED); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#getName() */ @Override public String getName() { return "Renewer(" + (this.getDns() != null ? this.getDns().getName() : "") + ")"; } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return super.toString() + " state: " + this.getTaskState(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#start(java.util.Timer) */ @Override public void start(Timer timer) { if (!this.getDns().isCanceling() && !this.getDns().isCanceled()) { timer.schedule(this, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL); } } @Override public boolean cancel() { this.removeAssociation(); return super.cancel(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#getTaskDescription() */ @Override public String getTaskDescription() { return "renewing"; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#checkRunCondition() */ @Override protected boolean checkRunCondition() { return !this.getDns().isCanceling() && !this.getDns().isCanceled(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#createOugoing() */ @Override protected DNSOutgoing createOugoing() { return new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#buildOutgoingForDNS(javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing buildOutgoingForDNS(DNSOutgoing out) throws IOException { DNSOutgoing newOut = out; for (DNSRecord answer : this.getDns().getLocalHost().answers(DNSRecordClass.UNIQUE, this.getTTL())) { newOut = this.addAnswer(newOut, null, answer); } return newOut; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#buildOutgoingForInfo(javax.jmdns.impl.ServiceInfoImpl, javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing buildOutgoingForInfo(ServiceInfoImpl info, DNSOutgoing out) throws IOException { DNSOutgoing newOut = out; for (DNSRecord answer : info.answers(DNSRecordClass.UNIQUE, this.getTTL(), this.getDns().getLocalHost())) { newOut = this.addAnswer(newOut, null, answer); } return newOut; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#recoverTask(java.lang.Throwable) */ @Override protected void recoverTask(Throwable e) { this.getDns().recover(); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.state.DNSStateTask#advanceTask() */ @Override protected void advanceTask() { this.setTaskState(this.getTaskState().advance()); if (!this.getTaskState().isAnnounced()) { cancel(); } } }jmdns-3.4.1/src/javax/jmdns/impl/tasks/RecordReaper.java0000644000175000017500000000314411625404056023032 0ustar mathieumathieu// 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.logging.Level; import java.util.logging.Logger; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.constants.DNSConstants; /** * Periodically removes expired entries from the cache. */ public class RecordReaper extends DNSTask { static Logger logger = Logger.getLogger(RecordReaper.class.getName()); /** * @param jmDNSImpl */ public RecordReaper(JmDNSImpl jmDNSImpl) { super(jmDNSImpl); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#getName() */ @Override public String getName() { return "RecordReaper(" + (this.getDns() != null ? this.getDns().getName() : "") + ")"; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#start(java.util.Timer) */ @Override public void start(Timer timer) { if (!this.getDns().isCanceling() && !this.getDns().isCanceled()) { timer.schedule(this, DNSConstants.RECORD_REAPER_INTERVAL, DNSConstants.RECORD_REAPER_INTERVAL); } } @Override public void run() { if (this.getDns().isCanceling() || this.getDns().isCanceled()) { return; } if (logger.isLoggable(Level.FINEST)) { logger.finest(this.getName() + ".run() JmDNS reaping cache"); } // Remove expired answers from the cache // ------------------------------------- this.getDns().cleanCache(); } }jmdns-3.4.1/src/javax/jmdns/impl/tasks/DNSTask.java0000644000175000017500000001427311625404056021731 0ustar mathieumathieu// Licensed under Apache License version 2.0 package javax.jmdns.impl.tasks; import java.io.IOException; import java.util.Timer; import java.util.TimerTask; import javax.jmdns.impl.DNSIncoming; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.DNSQuestion; import javax.jmdns.impl.DNSRecord; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.constants.DNSConstants; /** * This is the root class for all task scheduled by the timer in JmDNS. * * @author Pierre Frisch */ public abstract class DNSTask extends TimerTask { /** * */ private final JmDNSImpl _jmDNSImpl; /** * @param jmDNSImpl */ protected DNSTask(JmDNSImpl jmDNSImpl) { super(); this._jmDNSImpl = jmDNSImpl; } /** * Return the DNS associated with this task. * * @return associated DNS */ public JmDNSImpl getDns() { return _jmDNSImpl; } /** * Start this task. * * @param timer * task timer. */ public abstract void start(Timer timer); /** * Return this task name. * * @return task name */ public abstract String getName(); /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return this.getName(); } /** * Add a question to the message. * * @param out * outgoing message * @param rec * DNS question * @return outgoing message for the next question * @exception IOException */ public DNSOutgoing addQuestion(DNSOutgoing out, DNSQuestion rec) throws IOException { DNSOutgoing newOut = out; try { newOut.addQuestion(rec); } catch (final IOException e) { int flags = newOut.getFlags(); boolean multicast = newOut.isMulticast(); int maxUDPPayload = newOut.getMaxUDPPayload(); int id = newOut.getId(); newOut.setFlags(flags | DNSConstants.FLAGS_TC); newOut.setId(id); this._jmDNSImpl.send(newOut); newOut = new DNSOutgoing(flags, multicast, maxUDPPayload); newOut.addQuestion(rec); } return newOut; } /** * Add an answer if it is not suppressed. * * @param out * outgoing message * @param in * incoming request * @param rec * DNS record answer * @return outgoing message for the next answer * @exception IOException */ public DNSOutgoing addAnswer(DNSOutgoing out, DNSIncoming in, DNSRecord rec) throws IOException { DNSOutgoing newOut = out; try { newOut.addAnswer(in, rec); } catch (final IOException e) { int flags = newOut.getFlags(); boolean multicast = newOut.isMulticast(); int maxUDPPayload = newOut.getMaxUDPPayload(); int id = newOut.getId(); newOut.setFlags(flags | DNSConstants.FLAGS_TC); newOut.setId(id); this._jmDNSImpl.send(newOut); newOut = new DNSOutgoing(flags, multicast, maxUDPPayload); newOut.addAnswer(in, rec); } return newOut; } /** * Add an answer to the message. * * @param out * outgoing message * @param rec * DNS record answer * @param now * @return outgoing message for the next answer * @exception IOException */ public DNSOutgoing addAnswer(DNSOutgoing out, DNSRecord rec, long now) throws IOException { DNSOutgoing newOut = out; try { newOut.addAnswer(rec, now); } catch (final IOException e) { int flags = newOut.getFlags(); boolean multicast = newOut.isMulticast(); int maxUDPPayload = newOut.getMaxUDPPayload(); int id = newOut.getId(); newOut.setFlags(flags | DNSConstants.FLAGS_TC); newOut.setId(id); this._jmDNSImpl.send(newOut); newOut = new DNSOutgoing(flags, multicast, maxUDPPayload); newOut.addAnswer(rec, now); } return newOut; } /** * Add an authoritative answer to the message. * * @param out * outgoing message * @param rec * DNS record answer * @return outgoing message for the next answer * @exception IOException */ public DNSOutgoing addAuthoritativeAnswer(DNSOutgoing out, DNSRecord rec) throws IOException { DNSOutgoing newOut = out; try { newOut.addAuthorativeAnswer(rec); } catch (final IOException e) { int flags = newOut.getFlags(); boolean multicast = newOut.isMulticast(); int maxUDPPayload = newOut.getMaxUDPPayload(); int id = newOut.getId(); newOut.setFlags(flags | DNSConstants.FLAGS_TC); newOut.setId(id); this._jmDNSImpl.send(newOut); newOut = new DNSOutgoing(flags, multicast, maxUDPPayload); newOut.addAuthorativeAnswer(rec); } return newOut; } /** * Add an additional answer to the record. Omit if there is no room. * * @param out * outgoing message * @param in * incoming request * @param rec * DNS record answer * @return outgoing message for the next answer * @exception IOException */ public DNSOutgoing addAdditionalAnswer(DNSOutgoing out, DNSIncoming in, DNSRecord rec) throws IOException { DNSOutgoing newOut = out; try { newOut.addAdditionalAnswer(in, rec); } catch (final IOException e) { int flags = newOut.getFlags(); boolean multicast = newOut.isMulticast(); int maxUDPPayload = newOut.getMaxUDPPayload(); int id = newOut.getId(); newOut.setFlags(flags | DNSConstants.FLAGS_TC); newOut.setId(id); this._jmDNSImpl.send(newOut); newOut = new DNSOutgoing(flags, multicast, maxUDPPayload); newOut.addAdditionalAnswer(in, rec); } return newOut; } } jmdns-3.4.1/src/javax/jmdns/impl/tasks/Responder.java0000644000175000017500000001315411625404056022420 0ustar mathieumathieu// 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.HashSet; import java.util.Set; import java.util.Timer; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.impl.DNSIncoming; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.DNSQuestion; import javax.jmdns.impl.DNSRecord; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.constants.DNSConstants; /** * The Responder sends a single answer for the specified service infos and for the host name. */ public class Responder extends DNSTask { static Logger logger = Logger.getLogger(Responder.class.getName()); /** * */ private final DNSIncoming _in; /** * */ private final boolean _unicast; public Responder(JmDNSImpl jmDNSImpl, DNSIncoming in, int port) { super(jmDNSImpl); this._in = in; this._unicast = (port != DNSConstants.MDNS_PORT); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#getName() */ @Override public String getName() { return "Responder(" + (this.getDns() != null ? this.getDns().getName() : "") + ")"; } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return super.toString() + " incomming: " + _in; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#start(java.util.Timer) */ @Override public void start(Timer timer) { // According to draft-cheshire-dnsext-multicastdns.txt chapter "7 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 "6.2 Multi-Packet Known Answer Suppression": // We respond after 20-120 ms if the query is truncated. boolean iAmTheOnlyOne = true; for (DNSQuestion question : _in.getQuestions()) { if (logger.isLoggable(Level.FINEST)) { logger.finest(this.getName() + "start() question=" + question); } iAmTheOnlyOne = question.iAmTheOnlyOne(this.getDns()); 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; } if (logger.isLoggable(Level.FINEST)) { logger.finest(this.getName() + "start() Responder chosen delay=" + delay); } if (!this.getDns().isCanceling() && !this.getDns().isCanceled()) { timer.schedule(this, delay); } } @Override public void run() { this.getDns().respondToQuery(_in); // We use these sets to prevent duplicate records Set questions = new HashSet(); Set answers = new HashSet(); if (this.getDns().isAnnounced()) { try { // Answer questions for (DNSQuestion question : _in.getQuestions()) { if (logger.isLoggable(Level.FINER)) { logger.finer(this.getName() + "run() JmDNS responding to: " + question); } // for unicast responses the question must be included if (_unicast) { // out.addQuestion(q); questions.add(question); } question.addAnswers(this.getDns(), answers); } // remove known answers, if the ttl is at least half of the correct value. (See Draft Cheshire chapter 7.1.). long now = System.currentTimeMillis(); for (DNSRecord knownAnswer : _in.getAnswers()) { if (knownAnswer.isStale(now)) { answers.remove(knownAnswer); if (logger.isLoggable(Level.FINER)) { logger.finer(this.getName() + "JmDNS Responder Known Answer Removed"); } } } // respond if we have answers if (!answers.isEmpty()) { if (logger.isLoggable(Level.FINER)) { logger.finer(this.getName() + "run() JmDNS responding"); } DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, !_unicast, _in.getSenderUDPPayload()); out.setId(_in.getId()); for (DNSQuestion question : questions) { if (question != null) { out = this.addQuestion(out, question); } } for (DNSRecord answer : answers) { if (answer != null) { out = this.addAnswer(out, _in, answer); } } if (!out.isEmpty()) this.getDns().send(out); } // this.cancel(); } catch (Throwable e) { logger.log(Level.WARNING, this.getName() + "run() exception ", e); this.getDns().close(); } } } }jmdns-3.4.1/src/javax/jmdns/impl/tasks/package-info.java0000644000175000017500000000004111625404056022772 0ustar mathieumathieupackage javax.jmdns.impl.tasks; jmdns-3.4.1/src/javax/jmdns/impl/tasks/resolver/0000755000175000017500000000000011625404056021451 5ustar mathieumathieujmdns-3.4.1/src/javax/jmdns/impl/tasks/resolver/DNSResolverTask.java0000644000175000017500000000667111625404056025317 0ustar mathieumathieu// Licensed under Apache License version 2.0 package javax.jmdns.impl.tasks.resolver; import java.io.IOException; import java.util.Timer; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.tasks.DNSTask; /** * This is the root class for all resolver tasks. * * @author Pierre Frisch */ public abstract class DNSResolverTask extends DNSTask { private static Logger logger = Logger.getLogger(DNSResolverTask.class.getName()); /** * Counts the number of queries being sent. */ protected int _count = 0; /** * @param jmDNSImpl */ public DNSResolverTask(JmDNSImpl jmDNSImpl) { super(jmDNSImpl); } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return super.toString() + " count: " + _count; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#start(java.util.Timer) */ @Override public void start(Timer timer) { if (!this.getDns().isCanceling() && !this.getDns().isCanceled()) { timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL); } } /* * (non-Javadoc) * @see java.util.TimerTask#run() */ @Override public void run() { try { if (this.getDns().isCanceling() || this.getDns().isCanceled()) { this.cancel(); } else { if (_count++ < 3) { if (logger.isLoggable(Level.FINER)) { logger.finer(this.getName() + ".run() JmDNS " + this.description()); } DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); out = this.addQuestions(out); if (this.getDns().isAnnounced()) { out = this.addAnswers(out); } if (!out.isEmpty()) { this.getDns().send(out); } } else { // After three queries, we can quit. this.cancel(); } } } catch (Throwable e) { logger.log(Level.WARNING, this.getName() + ".run() exception ", e); this.getDns().recover(); } } /** * Overridden by subclasses to add questions to the message.
* Note: Because of message size limitation the returned message may be different than the message parameter. * * @param out * outgoing message * @return the outgoing message. * @exception IOException */ protected abstract DNSOutgoing addQuestions(DNSOutgoing out) throws IOException; /** * Overridden by subclasses to add questions to the message.
* Note: Because of message size limitation the returned message may be different than the message parameter. * * @param out * outgoing message * @return the outgoing message. * @exception IOException */ protected abstract DNSOutgoing addAnswers(DNSOutgoing out) throws IOException; /** * Returns a description of the resolver for debugging * * @return resolver description */ protected abstract String description(); } jmdns-3.4.1/src/javax/jmdns/impl/tasks/resolver/TypeResolver.java0000644000175000017500000000462611625404056024767 0ustar mathieumathieu// Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns.impl.tasks.resolver; import java.io.IOException; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.DNSQuestion; import javax.jmdns.impl.DNSRecord; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.JmDNSImpl.ServiceTypeEntry; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; /** * Helper class to resolve service types. *

* The TypeResolver queries three times consecutively for service types, and then removes itself from the timer. *

* The TypeResolver will run only if JmDNS is in state ANNOUNCED. */ public class TypeResolver extends DNSResolverTask { /** * @param jmDNSImpl */ public TypeResolver(JmDNSImpl jmDNSImpl) { super(jmDNSImpl); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#getName() */ @Override public String getName() { return "TypeResolver(" + (this.getDns() != null ? this.getDns().getName() : "") + ")"; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.Resolver#addAnswers(javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing addAnswers(DNSOutgoing out) throws IOException { DNSOutgoing newOut = out; long now = System.currentTimeMillis(); for (String type : this.getDns().getServiceTypes().keySet()) { ServiceTypeEntry typeEntry = this.getDns().getServiceTypes().get(type); newOut = this.addAnswer(newOut, new DNSRecord.Pointer("_services._dns-sd._udp.local.", DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, DNSConstants.DNS_TTL, typeEntry.getType()), now); } return newOut; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.Resolver#addQuestions(javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing addQuestions(DNSOutgoing out) throws IOException { return this.addQuestion(out, DNSQuestion.newQuestion("_services._dns-sd._udp.local.", DNSRecordType.TYPE_PTR, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.Resolver#description() */ @Override protected String description() { return "querying type"; } }jmdns-3.4.1/src/javax/jmdns/impl/tasks/resolver/ServiceInfoResolver.java0000644000175000017500000001023611625404056026254 0ustar mathieumathieu// Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns.impl.tasks.resolver; import java.io.IOException; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.DNSQuestion; import javax.jmdns.impl.DNSRecord; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.ServiceInfoImpl; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; /** * The ServiceInfoResolver queries up to three times consecutively for a service info, and then removes itself from the timer. *

* 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 DNSResolverTask { private final ServiceInfoImpl _info; public ServiceInfoResolver(JmDNSImpl jmDNSImpl, ServiceInfoImpl info) { super(jmDNSImpl); this._info = info; info.setDns(this.getDns()); this.getDns().addListener(info, DNSQuestion.newQuestion(info.getQualifiedName(), DNSRecordType.TYPE_ANY, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#getName() */ @Override public String getName() { return "ServiceInfoResolver(" + (this.getDns() != null ? this.getDns().getName() : "") + ")"; } /* * (non-Javadoc) * @see java.util.TimerTask#cancel() */ @Override public boolean cancel() { // We should not forget to remove the listener boolean result = super.cancel(); if (!_info.isPersistent()) { this.getDns().removeListener(_info); } return result; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.Resolver#addAnswers(javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing addAnswers(DNSOutgoing out) throws IOException { DNSOutgoing newOut = out; if (!_info.hasData()) { long now = System.currentTimeMillis(); newOut = this.addAnswer(newOut, (DNSRecord) this.getDns().getCache().getDNSEntry(_info.getQualifiedName(), DNSRecordType.TYPE_SRV, DNSRecordClass.CLASS_IN), now); newOut = this.addAnswer(newOut, (DNSRecord) this.getDns().getCache().getDNSEntry(_info.getQualifiedName(), DNSRecordType.TYPE_TXT, DNSRecordClass.CLASS_IN), now); if (_info.getServer().length() > 0) { newOut = this.addAnswer(newOut, (DNSRecord) this.getDns().getCache().getDNSEntry(_info.getServer(), DNSRecordType.TYPE_A, DNSRecordClass.CLASS_IN), now); newOut = this.addAnswer(newOut, (DNSRecord) this.getDns().getCache().getDNSEntry(_info.getServer(), DNSRecordType.TYPE_AAAA, DNSRecordClass.CLASS_IN), now); } } return newOut; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.Resolver#addQuestions(javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing addQuestions(DNSOutgoing out) throws IOException { DNSOutgoing newOut = out; if (!_info.hasData()) { newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getQualifiedName(), DNSRecordType.TYPE_SRV, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getQualifiedName(), DNSRecordType.TYPE_TXT, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); if (_info.getServer().length() > 0) { newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getServer(), DNSRecordType.TYPE_A, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getServer(), DNSRecordType.TYPE_AAAA, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); } } return newOut; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.Resolver#description() */ @Override protected String description() { return "querying service info: " + (_info != null ? _info.getQualifiedName() : "null"); } }jmdns-3.4.1/src/javax/jmdns/impl/tasks/resolver/package-info.java0000644000175000017500000000005211625404056024635 0ustar mathieumathieupackage javax.jmdns.impl.tasks.resolver; jmdns-3.4.1/src/javax/jmdns/impl/tasks/resolver/ServiceResolver.java0000644000175000017500000000554011625404056025442 0ustar mathieumathieu// Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns.impl.tasks.resolver; import java.io.IOException; import javax.jmdns.ServiceInfo; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.DNSQuestion; import javax.jmdns.impl.DNSRecord; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; /** * The ServiceResolver queries three times consecutively for services of a given type, and then removes itself from the timer. *

* 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 DNSResolverTask { private final String _type; public ServiceResolver(JmDNSImpl jmDNSImpl, String type) { super(jmDNSImpl); this._type = type; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#getName() */ @Override public String getName() { return "ServiceResolver(" + (this.getDns() != null ? this.getDns().getName() : "") + ")"; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.Resolver#addAnswers(javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing addAnswers(DNSOutgoing out) throws IOException { DNSOutgoing newOut = out; long now = System.currentTimeMillis(); for (ServiceInfo info : this.getDns().getServices().values()) { newOut = this.addAnswer(newOut, new DNSRecord.Pointer(info.getType(), DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, DNSConstants.DNS_TTL, info.getQualifiedName()), now); // newOut = this.addAnswer(newOut, new DNSRecord.Service(info.getQualifiedName(), DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, DNSConstants.DNS_TTL, info.getPriority(), info.getWeight(), info.getPort(), // this.getDns().getLocalHost().getName()), now); } return newOut; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.Resolver#addQuestions(javax.jmdns.impl.DNSOutgoing) */ @Override protected DNSOutgoing addQuestions(DNSOutgoing out) throws IOException { DNSOutgoing newOut = out; newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_type, DNSRecordType.TYPE_PTR, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); // newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_type, DNSRecordType.TYPE_SRV, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); return newOut; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.Resolver#description() */ @Override protected String description() { return "querying service"; } }jmdns-3.4.1/src/javax/jmdns/test/0000755000175000017500000000000011625404056016501 5ustar mathieumathieujmdns-3.4.1/src/javax/jmdns/test/ServiceInfoTest.java0000644000175000017500000001661511625404056022431 0ustar mathieumathieu/** * */ package javax.jmdns.test; import static junit.framework.Assert.assertEquals; import java.util.Map; import javax.jmdns.ServiceInfo.Fields; import javax.jmdns.impl.ServiceInfoImpl; import org.junit.Before; import org.junit.Test; /** * */ public class ServiceInfoTest { @Before public void setup() { // Placeholder } @Test public void testDecodeQualifiedNameMap() { String domain = "test.com"; String protocol = "udp"; String application = "ftp"; String name = "My Service"; String subtype = "printer"; String type = "_" + application + "._" + protocol + "." + domain + "."; Map map = ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype); assertEquals("We did not get the right domain:", domain, map.get(Fields.Domain)); assertEquals("We did not get the right protocol:", protocol, map.get(Fields.Protocol)); assertEquals("We did not get the right application:", application, map.get(Fields.Application)); assertEquals("We did not get the right name:", name, map.get(Fields.Instance)); assertEquals("We did not get the right subtype:", subtype, map.get(Fields.Subtype)); } @Test public void testDecodeQualifiedNameMapDefaults() { String domain = "local"; String protocol = "tcp"; String application = "ftp"; String name = "My Service"; String subtype = ""; Map map = ServiceInfoImpl.decodeQualifiedNameMap(application, name, subtype); assertEquals("We did not get the right domain:", domain, map.get(Fields.Domain)); assertEquals("We did not get the right protocol:", protocol, map.get(Fields.Protocol)); assertEquals("We did not get the right application:", application, map.get(Fields.Application)); assertEquals("We did not get the right name:", name, map.get(Fields.Instance)); assertEquals("We did not get the right subtype:", subtype, map.get(Fields.Subtype)); } @Test public void testDecodeServiceType() { String type = "_home-sharing._tcp.local."; Map map = ServiceInfoImpl.decodeQualifiedNameMapForType(type); assertEquals("We did not get the right domain:", "local", map.get(Fields.Domain)); assertEquals("We did not get the right protocol:", "tcp", map.get(Fields.Protocol)); assertEquals("We did not get the right application:", "home-sharing", map.get(Fields.Application)); assertEquals("We did not get the right name:", "", map.get(Fields.Instance)); assertEquals("We did not get the right subtype:", "", map.get(Fields.Subtype)); } @Test public void testDecodeServiceTCPType() { String type = "_afpovertcp._tcp.local."; Map map = ServiceInfoImpl.decodeQualifiedNameMapForType(type); assertEquals("We did not get the right domain:", "local", map.get(Fields.Domain)); assertEquals("We did not get the right protocol:", "tcp", map.get(Fields.Protocol)); assertEquals("We did not get the right application:", "afpovertcp", map.get(Fields.Application)); assertEquals("We did not get the right name:", "", map.get(Fields.Instance)); assertEquals("We did not get the right subtype:", "", map.get(Fields.Subtype)); } @Test public void testDecodeServiceTypeWithSubType() { String type = "_00000000-0b44-f234-48c8-071c565644b3._sub._home-sharing._tcp.local."; Map map = ServiceInfoImpl.decodeQualifiedNameMapForType(type); assertEquals("We did not get the right domain:", "local", map.get(Fields.Domain)); assertEquals("We did not get the right protocol:", "tcp", map.get(Fields.Protocol)); assertEquals("We did not get the right application:", "home-sharing", map.get(Fields.Application)); assertEquals("We did not get the right name:", "", map.get(Fields.Instance)); assertEquals("We did not get the right subtype:", "00000000-0b44-f234-48c8-071c565644b3", map.get(Fields.Subtype)); } @Test public void testDecodeServiceName() { String type = "My New Itunes Service._home-sharing._tcp.local."; Map map = ServiceInfoImpl.decodeQualifiedNameMapForType(type); assertEquals("We did not get the right domain:", "local", map.get(Fields.Domain)); assertEquals("We did not get the right protocol:", "tcp", map.get(Fields.Protocol)); assertEquals("We did not get the right application:", "home-sharing", map.get(Fields.Application)); assertEquals("We did not get the right name:", "My New Itunes Service", map.get(Fields.Instance)); assertEquals("We did not get the right subtype:", "", map.get(Fields.Subtype)); } @Test public void testDecodeDNSMetaQuery() { String type = "_services._dns-sd._udp.local."; Map map = ServiceInfoImpl.decodeQualifiedNameMapForType(type); assertEquals("We did not get the right domain:", "local", map.get(Fields.Domain)); assertEquals("We did not get the right protocol:", "udp", map.get(Fields.Protocol)); assertEquals("We did not get the right application:", "dns-sd", map.get(Fields.Application)); assertEquals("We did not get the right name:", "_services", map.get(Fields.Instance)); assertEquals("We did not get the right subtype:", "", map.get(Fields.Subtype)); } @Test public void testReverseDNSQuery() { String type = "100.50.168.192.in-addr.arpa."; Map map = ServiceInfoImpl.decodeQualifiedNameMapForType(type); assertEquals("We did not get the right domain:", "in-addr.arpa", map.get(Fields.Domain)); assertEquals("We did not get the right protocol:", "", map.get(Fields.Protocol)); assertEquals("We did not get the right application:", "", map.get(Fields.Application)); assertEquals("We did not get the right name:", "100.50.168.192", map.get(Fields.Instance)); assertEquals("We did not get the right subtype:", "", map.get(Fields.Subtype)); } @Test public void testAddress() { String type = "panoramix.local."; Map map = ServiceInfoImpl.decodeQualifiedNameMapForType(type); assertEquals("We did not get the right domain:", "local", map.get(Fields.Domain)); assertEquals("We did not get the right protocol:", "", map.get(Fields.Protocol)); assertEquals("We did not get the right application:", "", map.get(Fields.Application)); assertEquals("We did not get the right name:", "panoramix", map.get(Fields.Instance)); assertEquals("We did not get the right subtype:", "", map.get(Fields.Subtype)); } @Test public void testCasePreserving() { String type = "My New Itunes Service._Home-Sharing._TCP.Panoramix.local."; Map map = ServiceInfoImpl.decodeQualifiedNameMapForType(type); assertEquals("We did not get the right domain:", "Panoramix.local", map.get(Fields.Domain)); assertEquals("We did not get the right protocol:", "TCP", map.get(Fields.Protocol)); assertEquals("We did not get the right application:", "Home-Sharing", map.get(Fields.Application)); assertEquals("We did not get the right name:", "My New Itunes Service", map.get(Fields.Instance)); assertEquals("We did not get the right subtype:", "", map.get(Fields.Subtype)); } } jmdns-3.4.1/src/javax/jmdns/test/DNSCacheTest.java0000644000175000017500000000066411625404056021562 0ustar mathieumathieu/** * */ package javax.jmdns.test; import static junit.framework.Assert.assertNotNull; import javax.jmdns.impl.DNSCache; import org.junit.Before; import org.junit.Test; /** * */ public class DNSCacheTest { @Before public void setup() { // } @Test public void testCacheCreation() { DNSCache cache = new DNSCache(); assertNotNull("Could not create a new DNS cache.", cache); } } jmdns-3.4.1/src/javax/jmdns/test/JmDNSTest.java0000644000175000017500000006256011625404056021130 0ustar mathieumathieupackage javax.jmdns.test; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.*; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.createNiceMock; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.Inet6Address; import java.net.MulticastSocket; import java.net.NetworkInterface; import java.net.UnknownHostException; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.LogManager; 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.constants.DNSConstants; import junit.framework.Assert; import org.easymock.Capture; import org.easymock.EasyMock; import org.junit.Before; import org.junit.Test; public class JmDNSTest { @SuppressWarnings("unused") private ServiceTypeListener typeListenerMock; private ServiceListener serviceListenerMock; private ServiceInfo service; private final static String serviceKey = "srvname"; // Max 9 chars @Before public void setup() { boolean log = false; if (log) { ConsoleHandler handler = new ConsoleHandler(); handler.setLevel(Level.FINEST); for (Enumeration enumerator = LogManager.getLogManager().getLoggerNames(); enumerator.hasMoreElements();) { String loggerName = enumerator.nextElement(); Logger logger = Logger.getLogger(loggerName); logger.addHandler(handler); logger.setLevel(Level.FINEST); } } String text = "Test hypothetical web server"; Map properties = new HashMap(); properties.put(serviceKey, text.getBytes()); service = ServiceInfo.create("_html._tcp.local.", "apache-someuniqueid", 80, 0, 0, true, properties); typeListenerMock = createMock(ServiceTypeListener.class); serviceListenerMock = createNiceMock("ServiceListener", ServiceListener.class); } @Test public void testCreate() throws IOException { System.out.println("Unit Test: testCreate()"); JmDNS registry = JmDNS.create(); registry.close(); } @Test public void testCreateINet() throws IOException { System.out.println("Unit Test: testCreateINet()"); JmDNS registry = JmDNS.create(InetAddress.getLocalHost()); // assertEquals("We did not register on the local host inet:", InetAddress.getLocalHost(), registry.getInterface()); registry.close(); } @Test public void testRegisterService() throws IOException { System.out.println("Unit Test: testRegisterService()"); JmDNS registry = null; try { registry = JmDNS.create(); registry.registerService(service); } finally { if (registry != null) registry.close(); } } @Test public void testUnregisterService() throws IOException, InterruptedException { System.out.println("Unit Test: testUnregisterService()"); JmDNS registry = null; try { registry = JmDNS.create(); registry.registerService(service); ServiceInfo[] services = registry.list(service.getType()); assertEquals("We should see the service we just registered: ", 1, services.length); assertEquals(service, services[0]); // now unregister and make sure it's gone registry.unregisterService(services[0]); // According to the spec the record disappears from the cache 1s after it has been unregistered // without sleeping for a while, the service would not be unregistered fully Thread.sleep(1500); services = registry.list(service.getType()); assertTrue("We should not see the service we just unregistered: ", services == null || services.length == 0); } finally { if (registry != null) registry.close(); } } @Test public void testRegisterServiceTwice() throws IOException { System.out.println("Unit Test: testRegisterService()"); JmDNS registry = null; try { registry = JmDNS.create(); registry.registerService(service); // This should cause an exception registry.registerService(service); fail("Registering the same service info should fail."); } catch (IllegalStateException exception) { // Expected exception. } finally { if (registry != null) registry.close(); } } @Test public void testUnregisterAndReregisterService() throws IOException, InterruptedException { System.out.println("Unit Test: testUnregisterAndReregisterService()"); JmDNS registry = null; try { registry = JmDNS.create(); registry.registerService(service); ServiceInfo[] services = registry.list(service.getType()); assertEquals("We should see the service we just registered: ", 1, services.length); assertEquals(service, services[0]); // now unregister and make sure it's gone registry.unregisterService(services[0]); // According to the spec the record disappears from the cache 1s after it has been unregistered // without sleeping for a while, the service would not be unregistered fully Thread.sleep(1500); services = registry.list(service.getType()); assertTrue("We should not see the service we just unregistered: ", services == null || services.length == 0); registry.registerService(service); Thread.sleep(5000); services = registry.list(service.getType()); assertTrue("We should see the service we just reregistered: ", services != null && services.length > 0); } finally { if (registry != null) registry.close(); } } @Test public void testQueryMyService() throws IOException { System.out.println("Unit Test: testQueryMyService()"); JmDNS registry = null; try { registry = JmDNS.create(); registry.registerService(service); ServiceInfo queriedService = registry.getServiceInfo(service.getType(), service.getName()); assertEquals(service, queriedService); } finally { if (registry != null) registry.close(); } } @Test public void testListMyService() throws IOException { System.out.println("Unit Test: testListMyService()"); JmDNS registry = null; try { registry = JmDNS.create(); registry.registerService(service); ServiceInfo[] services = registry.list(service.getType()); assertEquals("We should see the service we just registered: ", 1, services.length); assertEquals(service, services[0]); } finally { if (registry != null) registry.close(); } } @Test public void testListMyServiceIPV6() throws IOException { System.out.println("Unit Test: testListMyServiceIPV6()"); JmDNS registry = null; try { InetAddress address = InetAddress.getLocalHost(); NetworkInterface interfaze = NetworkInterface.getByInetAddress(address); for (Enumeration iaenum = interfaze.getInetAddresses(); iaenum.hasMoreElements();) { InetAddress interfaceAddress = iaenum.nextElement(); if (interfaceAddress instanceof Inet6Address) { address = interfaceAddress; } } registry = JmDNS.create(address); registry.registerService(service); ServiceInfo[] services = registry.list(service.getType()); assertEquals("We should see the service we just registered: ", 1, services.length); assertEquals(service, services[0]); } finally { if (registry != null) registry.close(); } } @Test public void testListenForMyService() throws IOException { System.out.println("Unit Test: testListenForMyService()"); JmDNS registry = null; try { Capture capServiceAddedEvent = new Capture(); Capture capServiceResolvedEvent = new Capture(); // Add an expectation that the listener interface will be called once capture the object so I can verify it separately. serviceListenerMock.serviceAdded(capture(capServiceAddedEvent)); serviceListenerMock.serviceResolved(capture(capServiceResolvedEvent)); EasyMock.replay(serviceListenerMock); // EasyMock.makeThreadSafe(serviceListenerMock, false); registry = JmDNS.create(); registry.addServiceListener(service.getType(), serviceListenerMock); registry.registerService(service); // We get the service added event when we register the service. However the service has not been resolved at this point. // The info associated with the event only has the minimum information i.e. name and type. assertTrue("We did not get the service added event.", capServiceAddedEvent.hasCaptured()); ServiceInfo info = capServiceAddedEvent.getValue().getInfo(); assertEquals("We did not get the right name for the added service:", service.getName(), info.getName()); assertEquals("We did not get the right type for the added service:", service.getType(), info.getType()); assertEquals("We did not get the right fully qualified name for the added service:", service.getQualifiedName(), info.getQualifiedName()); // assertEquals("We should not get the server for the added service:", "", info.getServer()); // assertEquals("We should not get the address for the added service:", null, info.getAddress()); // assertEquals("We should not get the HostAddress for the added service:", "", info.getHostAddress()); // assertEquals("We should not get the InetAddress for the added service:", null, info.getInetAddress()); // assertEquals("We should not get the NiceTextString for the added service:", "", info.getNiceTextString()); // assertEquals("We should not get the Priority for the added service:", 0, info.getPriority()); // assertFalse("We should not get the PropertyNames for the added service:", info.getPropertyNames().hasMoreElements()); // assertEquals("We should not get the TextBytes for the added service:", 0, info.getTextBytes().length); // assertEquals("We should not get the TextString for the added service:", null, info.getTextString()); // assertEquals("We should not get the Weight for the added service:", 0, info.getWeight()); // assertNotSame("We should not get the URL for the added service:", "", info.getURL()); registry.requestServiceInfo(service.getType(), service.getName()); assertTrue("We did not get the service resolved event.", capServiceResolvedEvent.hasCaptured()); verify(serviceListenerMock); ServiceInfo resolvedInfo = capServiceResolvedEvent.getValue().getInfo(); assertEquals("Did not get the expected service info: ", service, resolvedInfo); } finally { if (registry != null) registry.close(); } } @Test public void testListenForMyServiceAndList() throws IOException { System.out.println("Unit Test: testListenForMyServiceAndList()"); JmDNS registry = null; try { Capture capServiceAddedEvent = new Capture(); Capture capServiceResolvedEvent = new Capture(); // Expect the listener to be called once and capture the result serviceListenerMock.serviceAdded(capture(capServiceAddedEvent)); serviceListenerMock.serviceResolved(capture(capServiceResolvedEvent)); replay(serviceListenerMock); registry = JmDNS.create(); registry.addServiceListener(service.getType(), serviceListenerMock); registry.registerService(service); // We get the service added event when we register the service. However the service has not been resolved at this point. // The info associated with the event only has the minimum information i.e. name and type. assertTrue("We did not get the service added event.", capServiceAddedEvent.hasCaptured()); ServiceInfo info = capServiceAddedEvent.getValue().getInfo(); assertEquals("We did not get the right name for the resolved service:", service.getName(), info.getName()); assertEquals("We did not get the right type for the resolved service:", service.getType(), info.getType()); // This will force the resolution of the service which in turn will get the listener called with a service resolved event. // The info associated with a service resolved event has all the information available. // Which in turn populates the ServiceInfo opbjects returned by JmDNS.list. ServiceInfo[] services = registry.list(info.getType()); assertEquals("We did not get the expected number of services: ", 1, services.length); assertEquals("The service returned was not the one expected", service, services[0]); assertTrue("We did not get the service resolved event.", capServiceResolvedEvent.hasCaptured()); verify(serviceListenerMock); ServiceInfo resolvedInfo = capServiceResolvedEvent.getValue().getInfo(); assertEquals("Did not get the expected service info: ", service, resolvedInfo); } finally { if (registry != null) registry.close(); } } @Test public void testListenForServiceOnOtherRegistry() throws IOException { System.out.println("Unit Test: testListenForServiceOnOtherRegistry()"); JmDNS registry = null; JmDNS newServiceRegistry = null; try { Capture capServiceAddedEvent = new Capture(); Capture capServiceResolvedEvent = new Capture(); // Expect the listener to be called once and capture the result serviceListenerMock.serviceAdded(capture(capServiceAddedEvent)); serviceListenerMock.serviceResolved(capture(capServiceResolvedEvent)); replay(serviceListenerMock); registry = JmDNS.create(); registry.addServiceListener(service.getType(), serviceListenerMock); // newServiceRegistry = JmDNS.create(); newServiceRegistry.registerService(service); // We get the service added event when we register the service. However the service has not been resolved at this point. // The info associated with the event only has the minimum information i.e. name and type. assertTrue("We did not get the service added event.", capServiceAddedEvent.hasCaptured()); ServiceInfo info = capServiceAddedEvent.getValue().getInfo(); assertEquals("We did not get the right name for the resolved service:", service.getName(), info.getName()); assertEquals("We did not get the right type for the resolved service:", service.getType(), info.getType()); // We get the service added event when we register the service. However the service has not been resolved at this point. // The info associated with the event only has the minimum information i.e. name and type. assertTrue("We did not get the service resolved event.", capServiceResolvedEvent.hasCaptured()); verify(serviceListenerMock); Object result = capServiceResolvedEvent.getValue().getInfo(); assertEquals("Did not get the expected service info: ", service, result); } finally { if (registry != null) registry.close(); if (newServiceRegistry != null) newServiceRegistry.close(); } } @Test public void testWaitAndQueryForServiceOnOtherRegistry() throws IOException { System.out.println("Unit Test: testWaitAndQueryForServiceOnOtherRegistry()"); JmDNS registry = null; JmDNS newServiceRegistry = null; try { newServiceRegistry = JmDNS.create(); registry = JmDNS.create(); registry.registerService(service); ServiceInfo fetchedService = newServiceRegistry.getServiceInfo(service.getType(), service.getName()); assertEquals("Did not get the expected service info: ", service, fetchedService); } finally { if (registry != null) registry.close(); if (newServiceRegistry != null) newServiceRegistry.close(); } } @Test public void testRegisterAndListServiceOnOtherRegistry() throws IOException, InterruptedException { System.out.println("Unit Test: testRegisterAndListServiceOnOtherRegistry()"); JmDNS registry = null; JmDNS newServiceRegistry = null; try { registry = JmDNS.create("Registry"); registry.registerService(service); newServiceRegistry = JmDNS.create("Listener"); Thread.sleep(6000); ServiceInfo[] fetchedServices = newServiceRegistry.list(service.getType()); assertEquals("Did not get the expected services listed:", 1, fetchedServices.length); assertEquals("Did not get the expected service type:", service.getType(), fetchedServices[0].getType()); assertEquals("Did not get the expected service name:", service.getName(), fetchedServices[0].getName()); assertEquals("Did not get the expected service fully qualified name:", service.getQualifiedName(), fetchedServices[0].getQualifiedName()); newServiceRegistry.getServiceInfo(service.getType(), service.getName()); assertEquals("Did not get the expected service info: ", service, fetchedServices[0]); registry.close(); registry = null; // According to the spec the record disappears from the cache 1s after it has been unregistered // without sleeping for a while, the service would not be unregistered fully Thread.sleep(1500); fetchedServices = newServiceRegistry.list(service.getType()); assertEquals("The service was not cancelled after the close:", 0, fetchedServices.length); } finally { if (registry != null) registry.close(); if (newServiceRegistry != null) newServiceRegistry.close(); } } public static final class Receive extends Thread { MulticastSocket _socket; DatagramPacket _in; public Receive(MulticastSocket socket, DatagramPacket in) { super("Test Receive Multicast"); _socket = socket; _in = in; } @Override public void run() { try { _socket.receive(_in); } catch (IOException exception) { // Ignore } } public boolean waitForReceive() { try { this.join(1000); } catch (InterruptedException exception) { // Ignore } return this.isAlive(); } } private final static int MPORT = 8053; @Test public void testTwoMulticastPortsAtOnce() throws UnknownHostException, IOException { System.out.println("Unit Test: testTwoMulticastPortsAtOnce()"); MulticastSocket firstSocket = null; MulticastSocket secondSocket = null; try { String firstMessage = "ping"; String secondMessage = "pong"; InetAddress someInet = InetAddress.getByName(DNSConstants.MDNS_GROUP); firstSocket = new MulticastSocket(MPORT); secondSocket = new MulticastSocket(MPORT); firstSocket.joinGroup(someInet); secondSocket.joinGroup(someInet); // DatagramPacket out = new DatagramPacket(firstMessage.getBytes("UTF-8"), firstMessage.length(), someInet, MPORT); DatagramPacket inFirst = new DatagramPacket(firstMessage.getBytes("UTF-8"), firstMessage.length(), someInet, MPORT); DatagramPacket inSecond = new DatagramPacket(firstMessage.getBytes("UTF-8"), firstMessage.length(), someInet, MPORT); Receive receiveSecond = new Receive(secondSocket, inSecond); receiveSecond.start(); Receive receiveFirst = new Receive(firstSocket, inSecond); receiveFirst.start(); firstSocket.send(out); if (receiveSecond.waitForReceive()) { Assert.fail("We did not receive the data in the second socket"); } String fromFirst = new String(inSecond.getData(), "UTF-8"); assertEquals("Expected the second socket to recieve the same message the first socket sent", firstMessage, fromFirst); // Make sure the first socket had read its own message if (receiveSecond.waitForReceive()) { Assert.fail("We did not receive the data in the first socket"); } // Reverse the roles out = new DatagramPacket(secondMessage.getBytes("UTF-8"), secondMessage.length(), someInet, MPORT); inFirst = new DatagramPacket(secondMessage.getBytes("UTF-8"), secondMessage.length(), someInet, MPORT); receiveFirst = new Receive(firstSocket, inSecond); receiveFirst.start(); secondSocket.send(out); if (receiveFirst.waitForReceive()) { Assert.fail("We did not receive the data in the first socket"); } String fromSecond = new String(inFirst.getData(), "UTF-8"); assertEquals("Expected the first socket to recieve the same message the second socket sent", secondMessage, fromSecond); } finally { if (firstSocket != null) firstSocket.close(); if (secondSocket != null) secondSocket.close(); } } @Test public void testListMyServiceWithToLowerCase() throws IOException, InterruptedException { System.out.println("Unit Test: testListMyServiceWithToLowerCase()"); String text = "Test hypothetical web server"; Map properties = new HashMap(); properties.put(serviceKey, text.getBytes()); service = ServiceInfo.create("_HtmL._TcP.lOcAl.", "apache-someUniqueid", 80, 0, 0, true, properties); JmDNS registry = null; try { registry = JmDNS.create(); registry.registerService(service); // with toLowerCase ServiceInfo[] services = registry.list(service.getType().toLowerCase()); assertEquals("We should see the service we just registered: ", 1, services.length); assertEquals(service, services[0]); // now unregister and make sure it's gone registry.unregisterService(services[0]); // According to the spec the record disappears from the cache 1s after it has been unregistered // without sleeping for a while, the service would not be unregistered fully Thread.sleep(1500); services = registry.list(service.getType().toLowerCase()); assertTrue("We should not see the service we just unregistered: ", services == null || services.length == 0); } finally { if (registry != null) registry.close(); } } @Test public void testListMyServiceWithoutLowerCase() throws IOException, InterruptedException { System.out.println("Unit Test: testListMyServiceWithoutLowerCase()"); String text = "Test hypothetical web server"; Map properties = new HashMap(); properties.put(serviceKey, text.getBytes()); service = ServiceInfo.create("_HtmL._TcP.lOcAl.", "apache-someUniqueid", 80, 0, 0, true, properties); JmDNS registry = null; try { registry = JmDNS.create(); registry.registerService(service); // without toLowerCase ServiceInfo[] services = registry.list(service.getType()); assertEquals("We should see the service we just registered: ", 1, services.length); assertEquals(service, services[0]); // now unregister and make sure it's gone registry.unregisterService(services[0]); // According to the spec the record disappears from the cache 1s after it has been unregistered // without sleeping for a while, the service would not be unregistered fully Thread.sleep(1500); services = registry.list(service.getType()); assertTrue("We should not see the service we just unregistered: ", services == null || services.length == 0); } finally { if (registry != null) registry.close(); } } } jmdns-3.4.1/src/javax/jmdns/test/DNSStatefulObjectTest.java0000644000175000017500000000411711625404056023472 0ustar mathieumathieu/** * */ package javax.jmdns.test; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import javax.jmdns.impl.DNSStatefulObject.DNSStatefulObjectSemaphore; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * */ public class DNSStatefulObjectTest { public static final class WaitingThread extends Thread { private final DNSStatefulObjectSemaphore _semaphore; private final long _timeout; private boolean _hasFinished; public WaitingThread(DNSStatefulObjectSemaphore semaphore, long timeout) { super("Waiting thread"); _semaphore = semaphore; _timeout = timeout; _hasFinished = false; } @Override public void run() { _semaphore.waitForEvent(_timeout); _hasFinished = true; } /** * @return the hasFinished */ public boolean hasFinished() { return _hasFinished; } } DNSStatefulObjectSemaphore _semaphore; @Before public void setup() { _semaphore = new DNSStatefulObjectSemaphore("test"); } @After public void teardown() { _semaphore = null; } @Test public void testWaitAndSignal() throws InterruptedException { WaitingThread thread = new WaitingThread(_semaphore, Long.MAX_VALUE); thread.start(); Thread.sleep(1); assertFalse("The thread should be waiting.", thread.hasFinished()); _semaphore.signalEvent(); Thread.sleep(1); assertTrue("The thread should have finished.", thread.hasFinished()); } @Test public void testWaitAndTimeout() throws InterruptedException { WaitingThread thread = new WaitingThread(_semaphore, 100); thread.start(); Thread.sleep(1); assertFalse("The thread should be waiting.", thread.hasFinished()); Thread.sleep(150); assertTrue("The thread should have finished.", thread.hasFinished()); } } jmdns-3.4.1/src/javax/jmdns/test/TextUpdateTest.java0000644000175000017500000004631011625404056022277 0ustar mathieumathieu/** * */ package javax.jmdns.test; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; import javax.jmdns.JmDNS; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceListener; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.tasks.state.DNSStateTask; import org.junit.Before; import org.junit.Test; /** * */ public class TextUpdateTest { private ServiceInfo service; private ServiceInfo printer; private MockListener serviceListenerMock; private final static String serviceKey = "srvname"; // Max 9 chars public static class MockListener implements ServiceListener { private final List _serviceAdded = Collections.synchronizedList(new ArrayList(2)); private final List _serviceRemoved = Collections.synchronizedList(new ArrayList(2)); private final List _serviceResolved = Collections.synchronizedList(new ArrayList(2)); /* * (non-Javadoc) * @see javax.jmdns.ServiceListener#serviceAdded(javax.jmdns.ServiceEvent) */ @Override public void serviceAdded(ServiceEvent event) { _serviceAdded.add(event.clone()); } /* * (non-Javadoc) * @see javax.jmdns.ServiceListener#serviceRemoved(javax.jmdns.ServiceEvent) */ @Override public void serviceRemoved(ServiceEvent event) { _serviceRemoved.add(event.clone()); } /* * (non-Javadoc) * @see javax.jmdns.ServiceListener#serviceResolved(javax.jmdns.ServiceEvent) */ @Override public void serviceResolved(ServiceEvent event) { _serviceResolved.add(event.clone()); } public List servicesAdded() { return _serviceAdded; } public List servicesRemoved() { return _serviceRemoved; } public List servicesResolved() { return _serviceResolved; } public synchronized void reset() { _serviceAdded.clear(); _serviceRemoved.clear(); _serviceResolved.clear(); } @Override public String toString() { StringBuilder aLog = new StringBuilder(); aLog.append("Services Added: " + _serviceAdded.size()); for (ServiceEvent event : _serviceAdded) { aLog.append("\n\tevent name: '"); aLog.append(event.getName()); aLog.append("' type: '"); aLog.append(event.getType()); aLog.append("' info: '"); aLog.append(event.getInfo()); } aLog.append("\nServices Removed: " + _serviceRemoved.size()); for (ServiceEvent event : _serviceRemoved) { aLog.append("\n\tevent name: '"); aLog.append(event.getName()); aLog.append("' type: '"); aLog.append(event.getType()); aLog.append("' info: '"); aLog.append(event.getInfo()); } aLog.append("\nServices Resolved: " + _serviceResolved.size()); for (ServiceEvent event : _serviceResolved) { aLog.append("\n\tevent name: '"); aLog.append(event.getName()); aLog.append("' type: '"); aLog.append(event.getType()); aLog.append("' info: '"); aLog.append(event.getInfo()); } return aLog.toString(); } } @Before public void setup() { boolean log = false; if (log) { ConsoleHandler handler = new ConsoleHandler(); handler.setLevel(Level.FINEST); for (Enumeration enumerator = LogManager.getLogManager().getLoggerNames(); enumerator.hasMoreElements();) { String loggerName = enumerator.nextElement(); Logger logger = Logger.getLogger(loggerName); logger.addHandler(handler); logger.setLevel(Level.FINEST); } } String text = "Test hypothetical web server"; Map properties = new HashMap(); properties.put(serviceKey, text.getBytes()); service = ServiceInfo.create("_html._tcp.local.", "apache-someuniqueid", 80, 0, 0, true, properties); text = "Test hypothetical print server"; properties.clear(); properties.put(serviceKey, text.getBytes()); printer = ServiceInfo.create("_html._tcp.local.", "printer-someuniqueid", "_printer", 80, 0, 0, true, properties); serviceListenerMock = new MockListener(); } @Test public void testListenForTextUpdateOnOtherRegistry() throws IOException, InterruptedException { System.out.println("Unit Test: testListenForTextUpdateOnOtherRegistry()"); JmDNS registry = null; JmDNS newServiceRegistry = null; try { registry = JmDNS.create("Listener"); registry.addServiceListener(service.getType(), serviceListenerMock); // newServiceRegistry = JmDNS.create("Registry"); newServiceRegistry.registerService(service); // We get the service added event when we register the service. However the service has not been resolved at this point. // The info associated with the event only has the minimum information i.e. name and type. List servicesAdded = serviceListenerMock.servicesAdded(); assertEquals("We did not get the service added event.", 1, servicesAdded.size()); ServiceInfo info = servicesAdded.get(servicesAdded.size() - 1).getInfo(); assertEquals("We did not get the right name for the resolved service:", service.getName(), info.getName()); assertEquals("We did not get the right type for the resolved service:", service.getType(), info.getType()); // We get the service added event when we register the service. However the service has not been resolved at this point. // The info associated with the event only has the minimum information i.e. name and type. List servicesResolved = serviceListenerMock.servicesResolved(); assertEquals("We did not get the service resolved event.", 1, servicesResolved.size()); ServiceInfo result = servicesResolved.get(servicesResolved.size() - 1).getInfo(); assertNotNull("Did not get the expected service info: ", result); assertEquals("Did not get the expected service info: ", service, result); assertEquals("Did not get the expected service info text: ", service.getPropertyString(serviceKey), result.getPropertyString(serviceKey)); serviceListenerMock.reset(); String text = "Test improbable web server"; Map properties = new HashMap(); properties.put(serviceKey, text.getBytes()); service.setText(properties); Thread.sleep(3000); servicesResolved = serviceListenerMock.servicesResolved(); assertEquals("We did not get the service text updated event.", 1, servicesResolved.size()); result = servicesResolved.get(servicesResolved.size() - 1).getInfo(); assertEquals("Did not get the expected service info text: ", text, result.getPropertyString(serviceKey)); serviceListenerMock.reset(); text = "Test more improbable web server"; properties = new HashMap(); properties.put(serviceKey, text.getBytes()); service.setText(properties); Thread.sleep(3000); servicesResolved = serviceListenerMock.servicesResolved(); assertEquals("We did not get the service text updated event.", 1, servicesResolved.size()); result = servicesResolved.get(servicesResolved.size() - 1).getInfo(); assertEquals("Did not get the expected service info text: ", text, result.getPropertyString(serviceKey)); serviceListenerMock.reset(); text = "Test even more improbable web server"; properties = new HashMap(); properties.put(serviceKey, text.getBytes()); service.setText(properties); Thread.sleep(3000); servicesResolved = serviceListenerMock.servicesResolved(); assertEquals("We did not get the service text updated event.", 1, servicesResolved.size()); result = servicesResolved.get(servicesResolved.size() - 1).getInfo(); assertEquals("Did not get the expected service info text: ", text, result.getPropertyString(serviceKey)); } finally { if (registry != null) registry.close(); if (newServiceRegistry != null) newServiceRegistry.close(); } } @Test public void testRegisterEmptyTXTField() throws IOException, InterruptedException { System.out.println("Unit Test: testRegisterEmptyTXTField()"); JmDNS registry = null; JmDNS newServiceRegistry = null; try { registry = JmDNS.create("Listener"); registry.addServiceListener(service.getType(), serviceListenerMock); // newServiceRegistry = JmDNS.create("Registry"); newServiceRegistry.registerService(service); // We get the service added event when we register the service. However the service has not been resolved at this point. // The info associated with the event only has the minimum information i.e. name and type. List servicesAdded = serviceListenerMock.servicesAdded(); assertEquals("We did not get the service added event.", 1, servicesAdded.size()); ServiceInfo info = servicesAdded.get(servicesAdded.size() - 1).getInfo(); assertEquals("We did not get the right name for the resolved service:", service.getName(), info.getName()); assertEquals("We did not get the right type for the resolved service:", service.getType(), info.getType()); // We get the service added event when we register the service. However the service has not been resolved at this point. // The info associated with the event only has the minimum information i.e. name and type. List servicesResolved = serviceListenerMock.servicesResolved(); assertEquals("We did not get the service resolved event.", 1, servicesResolved.size()); ServiceInfo result = servicesResolved.get(servicesResolved.size() - 1).getInfo(); assertNotNull("Did not get the expected service info: ", result); assertEquals("Did not get the expected service info: ", service, result); assertEquals("Did not get the expected service info text: ", service.getPropertyString(serviceKey), result.getPropertyString(serviceKey)); serviceListenerMock.reset(); Map properties = new HashMap(); service.setText(properties); Thread.sleep(3000); servicesResolved = serviceListenerMock.servicesResolved(); assertEquals("We did not get the service text updated event.", 1, servicesResolved.size()); result = servicesResolved.get(servicesResolved.size() - 1).getInfo(); assertNull("Did not get the expected service info text: ", result.getPropertyString(serviceKey)); } finally { if (registry != null) registry.close(); if (newServiceRegistry != null) newServiceRegistry.close(); } } @Test public void testRegisterCaseSensitiveField() throws IOException { System.out.println("Unit Test: testRegisterCaseSensitiveField()"); JmDNS registry = null; JmDNS newServiceRegistry = null; try { String text = "Test hypothetical Web Server"; Map properties = new HashMap(); properties.put(serviceKey, text.getBytes()); service = ServiceInfo.create("_Html._Tcp.local.", "Apache-SomeUniqueId", 80, 0, 0, true, properties); registry = JmDNS.create("Listener"); registry.addServiceListener(service.getType(), serviceListenerMock); // newServiceRegistry = JmDNS.create("Registry"); newServiceRegistry.registerService(service); // We get the service added event when we register the service. However the service has not been resolved at this point. // The info associated with the event only has the minimum information i.e. name and type. List servicesAdded = serviceListenerMock.servicesAdded(); assertEquals("We did not get the service added event.", 1, servicesAdded.size()); ServiceInfo info = servicesAdded.get(servicesAdded.size() - 1).getInfo(); assertEquals("We did not get the right name for the resolved service:", service.getName(), info.getName()); assertEquals("We did not get the right type for the resolved service:", service.getType(), info.getType()); // We get the service added event when we register the service. However the service has not been resolved at this point. // The info associated with the event only has the minimum information i.e. name and type. List servicesResolved = serviceListenerMock.servicesResolved(); assertEquals("We did not get the service resolved event.", 1, servicesResolved.size()); ServiceInfo result = servicesResolved.get(servicesResolved.size() - 1).getInfo(); assertNotNull("Did not get the expected service info: ", result); assertEquals("Did not get the expected service info: ", service, result); assertEquals("Did not get the expected service info text: ", service.getPropertyString(serviceKey), result.getPropertyString(serviceKey)); ServiceInfo[] infos = registry.list(service.getType()); assertEquals("We did not get the right list of service info.", 1, infos.length); assertEquals("Did not get the expected service info: ", service, infos[0]); assertEquals("Did not get the expected service info text: ", service.getPropertyString(serviceKey), infos[0].getPropertyString(serviceKey)); infos = registry.list(service.getType().toLowerCase()); assertEquals("We did not get the right list of service info.", 1, infos.length); assertEquals("Did not get the expected service info: ", service, infos[0]); assertEquals("Did not get the expected service info text: ", service.getPropertyString(serviceKey), infos[0].getPropertyString(serviceKey)); } finally { if (registry != null) registry.close(); if (newServiceRegistry != null) newServiceRegistry.close(); } } @Test public void testRenewExpiringRequests() throws IOException, InterruptedException { System.out.println("Unit Test: testRenewExpiringRequests()"); JmDNS registry = null; JmDNS newServiceRegistry = null; try { // To test for expiring TTL DNSStateTask.setDefaultTTL(1 * 60); registry = JmDNS.create("Listener"); registry.addServiceListener(service.getType(), serviceListenerMock); // newServiceRegistry = JmDNS.create("Registry"); newServiceRegistry.registerService(service); List servicesAdded = serviceListenerMock.servicesAdded(); assertTrue("We did not get the service added event.", servicesAdded.size() == 1); ServiceInfo[] services = registry.list(service.getType()); assertEquals("We should see the service we just registered: ", 1, services.length); assertEquals(service, services[0]); // wait for the TTL Thread.sleep(2 * 60 * 1000); services = registry.list(service.getType()); assertEquals("We should see the service after the renewal: ", 1, services.length); assertEquals(service, services[0]); } finally { if (registry != null) registry.close(); if (newServiceRegistry != null) newServiceRegistry.close(); DNSStateTask.setDefaultTTL(DNSConstants.DNS_TTL); } } @Test public void testSubtype() throws IOException { System.out.println("Unit Test: testSubtype()"); JmDNS registry = null; JmDNS newServiceRegistry = null; try { registry = JmDNS.create("Listener"); registry.addServiceListener(service.getType(), serviceListenerMock); // newServiceRegistry = JmDNS.create("Registry"); newServiceRegistry.registerService(printer); // We get the service added event when we register the service. However the service has not been resolved at this point. // The info associated with the event only has the minimum information i.e. name and type. List servicesAdded = serviceListenerMock.servicesAdded(); assertEquals("We did not get the service added event.", 1, servicesAdded.size()); ServiceInfo info = servicesAdded.get(servicesAdded.size() - 1).getInfo(); assertEquals("We did not get the right name for the resolved service:", printer.getName(), info.getName()); assertEquals("We did not get the right type for the resolved service:", printer.getType(), info.getType()); // We get the service added event when we register the service. However the service has not been resolved at this point. // The info associated with the event only has the minimum information i.e. name and type. List servicesResolved = serviceListenerMock.servicesResolved(); assertEquals("We did not get the service resolved event.", 1, servicesResolved.size()); ServiceInfo result = servicesResolved.get(servicesResolved.size() - 1).getInfo(); assertNotNull("Did not get the expected service info: ", result); assertEquals("Did not get the expected service info: ", printer, result); assertEquals("Did not get the expected service info subtype: ", printer.getSubtype(), result.getSubtype()); assertEquals("Did not get the expected service info text: ", printer.getPropertyString(serviceKey), result.getPropertyString(serviceKey)); serviceListenerMock.reset(); } finally { if (registry != null) registry.close(); if (newServiceRegistry != null) newServiceRegistry.close(); } } } jmdns-3.4.1/src/javax/jmdns/test/DNSMessageTest.java0000644000175000017500000001240511625404056022137 0ustar mathieumathieu/** * */ package javax.jmdns.test; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import java.io.IOException; import java.net.DatagramPacket; import java.util.Date; import javax.jmdns.impl.DNSIncoming; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.DNSQuestion; import javax.jmdns.impl.DNSRecord; import javax.jmdns.impl.constants.DNSConstants; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; import org.junit.Before; import org.junit.Test; /** * */ public class DNSMessageTest { @Before public void setup() { // } @Test public void testCreateQuery() throws IOException { String serviceName = "_00000000-0b44-f234-48c8-071c565644b3._sub._home-sharing._tcp.local."; DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); assertNotNull("Could not create the outgoing message", out); out.addQuestion(DNSQuestion.newQuestion(serviceName, DNSRecordType.TYPE_ANY, DNSRecordClass.CLASS_IN, true)); byte[] data = out.data(); assertNotNull("Could not encode the outgoing message", data); byte[] expected = new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x25, 0x5f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30, 0x62, 0x34, 0x34, 0x2d, 0x66, 0x32, 0x33, 0x34, 0x2d, 0x34, 0x38, 0x63, 0x38, 0x2d, 0x30, 0x37, 0x31, 0x63, 0x35, 0x36, 0x35, 0x36, 0x34, 0x34, 0x62, 0x33, 0x4, 0x5f, 0x73, 0x75, 0x62, 0xd, 0x5f, 0x68, 0x6f, 0x6d, 0x65, 0x2d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4, 0x5f, 0x74, 0x63, 0x70, 0x5, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x0, 0x0, (byte) 0xff, 0x0, 0x1 }; for (int i = 0; i < data.length; i++) { assertEquals("the encoded message is not what is expected at index " + i, expected[i], data[i]); } DatagramPacket packet = new DatagramPacket(data, 0, data.length); DNSIncoming in = new DNSIncoming(packet); assertTrue("Wrong packet type.", in.isQuery()); assertEquals("Wrong number of questions.", 1, in.getNumberOfQuestions()); for (DNSQuestion question : in.getQuestions()) { assertEquals("Wrong question name.", serviceName, question.getName()); } } @Test public void testCreateAnswer() throws IOException { String serviceType = "_home-sharing._tcp.local."; String serviceName = "Pierre." + serviceType; DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false); assertNotNull("Could not create the outgoing message", out); out.addQuestion(DNSQuestion.newQuestion(serviceName, DNSRecordType.TYPE_ANY, DNSRecordClass.CLASS_IN, true)); long now = (new Date()).getTime(); out.addAnswer(new DNSRecord.Pointer(serviceType, DNSRecordClass.CLASS_IN, true, DNSConstants.DNS_TTL, serviceName), now); out.addAuthorativeAnswer(new DNSRecord.Service(serviceType, DNSRecordClass.CLASS_IN, true, DNSConstants.DNS_TTL, 1, 20, 8080, "panoramix.local.")); byte[] data = out.data(); assertNotNull("Could not encode the outgoing message", data); // byte[] expected = new byte[] { 0x0, 0x0, (byte) 0x84, 0x0, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x6, 0x50, 0x69, 0x65, 0x72, 0x72, 0x65, 0xd, 0x5f, 0x68, 0x6f, 0x6d, 0x65, 0x2d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4, 0x5f, 0x74, // 0x63, 0x70, 0x5, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x0, 0x0, (byte) 0xff, 0x0, 0x1, (byte) 0xc0, 0x13, 0x0, 0xc, 0x0, 0x1, 0x0, 0x0, 0xe, 0xf, 0x0, 0x21, 0x6, 0x50, 0x69, 0x65, 0x72, 0x72, 0x65, 0xd, 0x5f, 0x68, 0x6f, 0x6d, 0x65, 0x2d, // 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4, 0x5f, 0x74, 0x63, 0x70, 0x5, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x0, (byte) 0xc0, 0x13, 0x0, 0x21, 0x0, 0x1, 0x0, 0x0, 0xe, 0xf, 0x0, 0x17, 0x0, 0x1, 0x0, 0x14, 0x1f, (byte) 0x90, 0x9, 0x70, // 0x61, 0x6e, 0x6f, 0x72, 0x61, 0x6d, 0x69, 0x78, 0x5, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x0 }; // for (int i = 0; i < data.length; i++) // { // assertEquals("the encoded message is not what is expected at index " + i, expected[i], data[i]); // } DatagramPacket packet = new DatagramPacket(data, 0, data.length); DNSIncoming in = new DNSIncoming(packet); assertTrue("Wrong packet type.", in.isResponse()); assertEquals("Wrong number of questions.", 1, in.getNumberOfQuestions()); assertEquals("Wrong number of answers.", 1, in.getNumberOfAnswers()); assertEquals("Wrong number of authorities.", 1, in.getNumberOfAuthorities()); for (DNSQuestion question : in.getQuestions()) { assertEquals("Wrong question name.", serviceName, question.getName()); } } protected void print(byte[] data) { System.out.print("{"); for (int i = 0; i < data.length; i++) { int value = data[i] & 0xFF; if (i > 0) { System.out.print(","); } System.out.print(" 0x"); System.out.print(Integer.toHexString(value)); if (i % 20 == 0) { System.out.print("\n"); } } System.out.print("}\n"); } } jmdns-3.4.1/src/javax/jmdns/NetworkTopologyEvent.java0000644000175000017500000000172511625404056022562 0ustar mathieumathieu/** * */ package javax.jmdns; import java.net.InetAddress; import java.util.EventObject; /** * @author Cédrik Lime, Pierre Frisch */ public abstract class NetworkTopologyEvent extends EventObject { /** * */ private static final long serialVersionUID = -8630033521752540987L; /** * Constructs a Service Event. * * @param eventSource * The DNS on which the Event initially occurred. * @exception IllegalArgumentException * if source is null. */ protected NetworkTopologyEvent(final Object eventSource) { super(eventSource); } /** * Returns the JmDNS instance associated with the event or null if it is a generic event. * * @return JmDNS instance */ public abstract JmDNS getDNS(); /** * The Internet address affected by this event. * * @return InetAddress */ public abstract InetAddress getInetAddress(); } jmdns-3.4.1/src/javax/jmdns/ServiceEvent.java0000644000175000017500000000345211625404056020773 0ustar mathieumathieu// 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 implements Cloneable { /** * */ private static final long serialVersionUID = -8558445644541006271L; /** * Constructs a Service Event. * * @param eventSource * The object on which the Event initially occurred. * @exception IllegalArgumentException * if source is null. */ public ServiceEvent(final Object eventSource) { super(eventSource); } /** * Returns the JmDNS instance which originated the event. * * @return JmDNS instance */ public abstract JmDNS getDNS(); /** * Returns the fully qualified type of the service. * * @return 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. * * @return name of the service */ 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. * * @return service info record * @see javax.jmdns.ServiceEvent#getInfo() */ public abstract ServiceInfo getInfo(); /* * (non-Javadoc) * @see java.lang.Object#clone() */ @Override public ServiceEvent clone() { try { return (ServiceEvent) super.clone(); } catch (CloneNotSupportedException exception) { // clone is supported return null; } } }jmdns-3.4.1/src/javax/jmdns/ServiceTypeListener.java0000644000175000017500000000174611625404056022345 0ustar mathieumathieu// 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. * * @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); /** * A new subtype for the service type was discovered. * *

     * <sub>._sub.<app>.<protocol>.<servicedomain>.<parentdomain>.
     * 
* * @param event * The service event providing the fully qualified type of the service with subtype. * @since 3.2.0 */ void subTypeForServiceTypeAdded(ServiceEvent event); } jmdns-3.4.1/src/javax/jmdns/package-info.java0000644000175000017500000000110011625404056020701 0ustar mathieumathieupackage javax.jmdns; /** * JmDNS is a Java implementation of multi-cast DNS and can be used for service registration and discovery in local area networks. JmDNS is fully compatible with Apple's Bonjour. The project was originally started in December 2002 by Arthur van Hoff * at Strangeberry. In November 2003 the project was moved to SourceForge, and the name was changed from JRendezvous to JmDNS for legal reasons. Many thanks to Stuart Cheshire for help and moral support. *

* http://jmdns.sourceforge.net/ *

**/ jmdns-3.4.1/src/javax/jmdns/NetworkTopologyListener.java0000644000175000017500000000133111625404056023257 0ustar mathieumathieu/** * */ package javax.jmdns; import java.util.EventListener; /** * Listener for network topology updates. * * @author Cédrik Lime, Pierre Frisch */ public interface NetworkTopologyListener extends EventListener { /** * A network address has been added.
* * @param event * The NetworkTopologyEvent providing the name and fully qualified type of the service. */ void inetAddressAdded(NetworkTopologyEvent event); /** * A network address has been removed. * * @param event * The NetworkTopologyEvent providing the name and fully qualified type of the service. */ void inetAddressRemoved(NetworkTopologyEvent event); } jmdns-3.4.1/build.xml0000644000175000017500000001641611625404056014340 0ustar mathieumathieu Bundle-Version: ${version} Bundle-Name: JmDNS is a java implementation of the IETF draft RFP multicast extensions for DNS Bundle-SymbolicName: javax.jmdns Bundle-Vendor: jmdns.sourceforge.net Bundle-RequiredExecutionEnvironment: J2SE-${jdk} Private-Package: com.strangeberry.jmdns.tools,samples Import-Package: Export-Package: javax.jmdns;version=${version} JmDNS]]> jmdns-3.4.1/NOTICE.txt0000644000175000017500000000124211625404056014230 0ustar mathieumathieu=============================================================== == NOTICE File for JmDNS == =============================================================== Java Multicast Domain Name Server (JmDNS) This project was originally developed by Arthur van Hoff under the GNU Lesser General Public License as jRendevous. It was moved to Sourceforge by Rick Blair and renamed to JmDNS with the Arthur's kind permission. Currently it has been re-released under the Apache License, Version 2.0. Details of the Apache License, Version 2.0 can be found at: http://www.apache.org/licenses/ For other details please see the README.txt file. jmdns-3.4.1/LICENSE0000644000175000017500000002613611625404056013524 0ustar mathieumathieu Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. jmdns-3.4.1/lib/0000755000175000017500000000000011741065626013262 5ustar mathieumathieu