| /* -*- Mode: Java; tab-width: 4 -*- |
| * |
| * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. |
| * |
| * 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. |
| |
| Change History (most recent first): |
| |
| $Log: DNSSD.java,v $ |
| Revision 1.16 2008/11/04 20:06:20 cheshire |
| <rdar://problem/6186231> Change MAX_DOMAIN_NAME to 256 |
| |
| Revision 1.15 2007/03/13 00:28:03 vazquez |
| <rdar://problem/4625928> Java: Rename exported symbols in libjdns_sd.jnilib |
| |
| Revision 1.14 2007/03/13 00:10:14 vazquez |
| <rdar://problem/4455206> Java: 64 bit JNI patch |
| |
| Revision 1.13 2007/02/24 23:08:02 mkrochma |
| <rdar://problem/5001673> Typo in Bonjour Java API document |
| |
| Revision 1.12 2007/02/09 00:33:02 cheshire |
| Add missing error codes to kMessages array |
| |
| Revision 1.11 2006/08/14 23:25:08 cheshire |
| Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 |
| |
| Revision 1.10 2006/06/20 23:05:55 rpantos |
| <rdar://problem/3839132> Java needs to implement DNSServiceRegisterRecord equivalent |
| |
| Revision 1.9 2005/10/26 01:52:24 cheshire |
| <rdar://problem/4316286> Race condition in Java code (doesn't work at all on Linux) |
| |
| Revision 1.8 2005/07/11 01:55:21 cheshire |
| <rdar://problem/4175511> Race condition in Java API |
| |
| Revision 1.7 2005/07/05 13:01:52 cheshire |
| <rdar://problem/4169791> If mDNSResponder daemon is stopped, Java API spins, burning CPU time |
| |
| Revision 1.6 2005/07/05 00:02:25 cheshire |
| Add missing comma |
| |
| Revision 1.5 2005/07/04 21:13:47 cheshire |
| Add missing error message strings |
| |
| Revision 1.4 2004/12/11 03:00:59 rpantos |
| <rdar://problem/3907498> Java DNSRecord API should be cleaned up |
| |
| Revision 1.3 2004/11/12 03:23:08 rpantos |
| rdar://problem/3809541 implement getIfIndexForName, getNameForIfIndex. |
| |
| Revision 1.2 2004/05/20 17:43:18 cheshire |
| Fix invalid UTF-8 characters in file |
| |
| Revision 1.1 2004/04/30 16:32:34 rpantos |
| First checked in. |
| |
| |
| This file declares and implements DNSSD, the central Java factory class |
| for doing DNS Service Discovery. It includes the mostly-abstract public |
| interface, as well as the Apple* implementation subclasses. |
| */ |
| |
| |
| package com.apple.dnssd; |
| |
| |
| /** |
| DNSSD provides access to DNS Service Discovery features of ZeroConf networking.<P> |
| |
| It is a factory class that is used to invoke registration and discovery-related |
| operations. Most operations are non-blocking; clients are called back through an interface |
| with the result of an operation. Callbacks are made from a separate worker thread.<P> |
| |
| For example, in this program<P> |
| <PRE><CODE> |
| class MyClient implements BrowseListener { |
| void lookForWebServers() { |
| myBrowser = DNSSD.browse("_http._tcp", this); |
| } |
| |
| public void serviceFound(DNSSDService browser, int flags, int ifIndex, |
| String serviceName, String regType, String domain) {} |
| ... |
| }</CODE></PRE> |
| <CODE>MyClient.serviceFound()</CODE> would be called for every HTTP server discovered in the |
| default browse domain(s). |
| */ |
| |
| abstract public class DNSSD |
| { |
| /** Flag indicates to a {@link BrowseListener} that another result is |
| queued. Applications should not update their UI to display browse |
| results if the MORE_COMING flag is set; they will be called at least once |
| more with the flag clear. |
| */ |
| public static final int MORE_COMING = ( 1 << 0 ); |
| |
| /** If flag is set in a {@link DomainListener} callback, indicates that the result is the default domain. */ |
| public static final int DEFAULT = ( 1 << 2 ); |
| |
| /** If flag is set, a name conflict will trigger an exception when registering non-shared records.<P> |
| A name must be explicitly specified when registering a service if this bit is set |
| (i.e. the default name may not not be used). |
| */ |
| public static final int NO_AUTO_RENAME = ( 1 << 3 ); |
| |
| /** If flag is set, allow multiple records with this name on the network (e.g. PTR records) |
| when registering individual records on a {@link DNSSDRegistration}. |
| */ |
| public static final int SHARED = ( 1 << 4 ); |
| |
| /** If flag is set, records with this name must be unique on the network (e.g. SRV records). */ |
| public static final int UNIQUE = ( 1 << 5 ); |
| |
| /** Set flag when calling enumerateDomains() to restrict results to domains recommended for browsing. */ |
| public static final int BROWSE_DOMAINS = ( 1 << 6 ); |
| /** Set flag when calling enumerateDomains() to restrict results to domains recommended for registration. */ |
| public static final int REGISTRATION_DOMAINS = ( 1 << 7 ); |
| |
| /** Maximum length, in bytes, of a domain name represented as an escaped C-String. */ |
| public static final int MAX_DOMAIN_NAME = 1009; |
| |
| /** Pass for ifIndex to specify all available interfaces. */ |
| public static final int ALL_INTERFACES = 0; |
| |
| /** Pass for ifIndex to specify the localhost interface. */ |
| public static final int LOCALHOST_ONLY = -1; |
| |
| /** Browse for instances of a service.<P> |
| |
| Note: browsing consumes network bandwidth. Call {@link DNSSDService#stop} when you have finished browsing.<P> |
| |
| @param flags |
| Currently ignored, reserved for future use. |
| <P> |
| @param ifIndex |
| If non-zero, specifies the interface on which to browse for services |
| (the index for a given interface is determined via the if_nametoindex() |
| family of calls.) Most applications will pass 0 to browse on all available |
| interfaces. Pass -1 to only browse for services provided on the local host. |
| <P> |
| @param regType |
| The registration type being browsed for followed by the protocol, separated by a |
| dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". |
| <P> |
| @param domain |
| If non-null, specifies the domain on which to browse for services. |
| Most applications will not specify a domain, instead browsing on the |
| default domain(s). |
| <P> |
| @param listener |
| This object will get called when instances of the service are discovered (or disappear). |
| <P> |
| @return A {@link DNSSDService} that represents the active browse operation. |
| |
| @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. |
| @see RuntimePermission |
| */ |
| public static DNSSDService browse( int flags, int ifIndex, String regType, String domain, BrowseListener listener) |
| throws DNSSDException |
| { return getInstance()._makeBrowser( flags, ifIndex, regType, domain, listener); } |
| |
| /** Browse for instances of a service. Use default flags, ifIndex and domain.<P> |
| |
| @param regType |
| The registration type being browsed for followed by the protocol, separated by a |
| dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". |
| <P> |
| @param listener |
| This object will get called when instances of the service are discovered (or disappear). |
| <P> |
| @return A {@link DNSSDService} that represents the active browse operation. |
| |
| @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. |
| @see RuntimePermission |
| */ |
| public static DNSSDService browse( String regType, BrowseListener listener) |
| throws DNSSDException |
| { return browse( 0, 0, regType, "", listener); } |
| |
| /** Resolve a service name discovered via browse() to a target host name, port number, and txt record.<P> |
| |
| Note: Applications should NOT use resolve() solely for txt record monitoring - use |
| queryRecord() instead, as it is more efficient for this task.<P> |
| |
| Note: When the desired results have been returned, the client MUST terminate the resolve by |
| calling {@link DNSSDService#stop}.<P> |
| |
| Note: resolve() behaves correctly for typical services that have a single SRV record and |
| a single TXT record (the TXT record may be empty.) To resolve non-standard services with |
| multiple SRV or TXT records, use queryRecord().<P> |
| |
| @param flags |
| Currently ignored, reserved for future use. |
| <P> |
| @param ifIndex |
| The interface on which to resolve the service. The client should |
| pass the interface on which the serviceName was discovered (i.e. |
| the ifIndex passed to the serviceFound() callback) |
| or 0 to resolve the named service on all available interfaces. |
| <P> |
| @param serviceName |
| The servicename to be resolved. |
| <P> |
| @param regType |
| The registration type being resolved followed by the protocol, separated by a |
| dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". |
| <P> |
| @param domain |
| The domain on which the service is registered, i.e. the domain passed |
| to the serviceFound() callback. |
| <P> |
| @param listener |
| This object will get called when the service is resolved. |
| <P> |
| @return A {@link DNSSDService} that represents the active resolve operation. |
| |
| @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. |
| @see RuntimePermission |
| */ |
| public static DNSSDService resolve( int flags, int ifIndex, String serviceName, String regType, |
| String domain, ResolveListener listener) |
| throws DNSSDException |
| { return getInstance()._resolve( flags, ifIndex, serviceName, regType, domain, listener); } |
| |
| /** Register a service, to be discovered via browse() and resolve() calls.<P> |
| @param flags |
| Possible values are: NO_AUTO_RENAME. |
| <P> |
| @param ifIndex |
| If non-zero, specifies the interface on which to register the service |
| (the index for a given interface is determined via the if_nametoindex() |
| family of calls.) Most applications will pass 0 to register on all |
| available interfaces. Pass -1 to register a service only on the local |
| machine (service will not be visible to remote hosts). |
| <P> |
| @param serviceName |
| If non-null, specifies the service name to be registered. |
| Applications need not specify a name, in which case the |
| computer name is used (this name is communicated to the client via |
| the serviceRegistered() callback). |
| <P> |
| @param regType |
| The registration type being registered followed by the protocol, separated by a |
| dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". |
| <P> |
| @param domain |
| If non-null, specifies the domain on which to advertise the service. |
| Most applications will not specify a domain, instead automatically |
| registering in the default domain(s). |
| <P> |
| @param host |
| If non-null, specifies the SRV target host name. Most applications |
| will not specify a host, instead automatically using the machine's |
| default host name(s). Note that specifying a non-null host does NOT |
| create an address record for that host - the application is responsible |
| for ensuring that the appropriate address record exists, or creating it |
| via {@link DNSSDRegistration#addRecord}. |
| <P> |
| @param port |
| The port on which the service accepts connections. Pass 0 for a |
| "placeholder" service (i.e. a service that will not be discovered by |
| browsing, but will cause a name conflict if another client tries to |
| register that same name.) Most clients will not use placeholder services. |
| <P> |
| @param txtRecord |
| The txt record rdata. May be null. Note that a non-null txtRecord |
| MUST be a properly formatted DNS TXT record, i.e. <length byte> <data> |
| <length byte> <data> ... |
| <P> |
| @param listener |
| This object will get called when the service is registered. |
| <P> |
| @return A {@link DNSSDRegistration} that controls the active registration. |
| |
| @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. |
| @see RuntimePermission |
| */ |
| public static DNSSDRegistration register( int flags, int ifIndex, String serviceName, String regType, |
| String domain, String host, int port, TXTRecord txtRecord, RegisterListener listener) |
| throws DNSSDException |
| { return getInstance()._register( flags, ifIndex, serviceName, regType, domain, host, port, txtRecord, listener); } |
| |
| /** Register a service, to be discovered via browse() and resolve() calls. Use default flags, ifIndex, domain, host and txtRecord.<P> |
| @param serviceName |
| If non-null, specifies the service name to be registered. |
| Applications need not specify a name, in which case the |
| computer name is used (this name is communicated to the client via |
| the serviceRegistered() callback). |
| <P> |
| @param regType |
| The registration type being registered followed by the protocol, separated by a |
| dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". |
| <P> |
| @param port |
| The port on which the service accepts connections. Pass 0 for a |
| "placeholder" service (i.e. a service that will not be discovered by |
| browsing, but will cause a name conflict if another client tries to |
| register that same name.) Most clients will not use placeholder services. |
| <P> |
| @param listener |
| This object will get called when the service is registered. |
| <P> |
| @return A {@link DNSSDRegistration} that controls the active registration. |
| |
| @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. |
| @see RuntimePermission |
| */ |
| public static DNSSDRegistration register( String serviceName, String regType, int port, RegisterListener listener) |
| throws DNSSDException |
| { return register( 0, 0, serviceName, regType, null, null, port, null, listener); } |
| |
| /** Create a {@link DNSSDRecordRegistrar} allowing efficient registration of |
| multiple individual records.<P> |
| <P> |
| @return A {@link DNSSDRecordRegistrar} that can be used to register records. |
| |
| @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. |
| @see RuntimePermission |
| */ |
| public static DNSSDRecordRegistrar createRecordRegistrar( RegisterRecordListener listener) |
| throws DNSSDException |
| { return getInstance()._createRecordRegistrar( listener); } |
| |
| /** Query for an arbitrary DNS record.<P> |
| @param flags |
| Possible values are: MORE_COMING. |
| <P> |
| @param ifIndex |
| If non-zero, specifies the interface on which to issue the query |
| (the index for a given interface is determined via the if_nametoindex() |
| family of calls.) Passing 0 causes the name to be queried for on all |
| interfaces. Passing -1 causes the name to be queried for only on the |
| local host. |
| <P> |
| @param serviceName |
| The full domain name of the resource record to be queried for. |
| <P> |
| @param rrtype |
| The numerical type of the resource record to be queried for (e.g. PTR, SRV, etc) |
| as defined in nameser.h. |
| <P> |
| @param rrclass |
| The class of the resource record, as defined in nameser.h |
| (usually 1 for the Internet class). |
| <P> |
| @param listener |
| This object will get called when the query completes. |
| <P> |
| @return A {@link DNSSDService} that controls the active query. |
| |
| @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. |
| @see RuntimePermission |
| */ |
| public static DNSSDService queryRecord( int flags, int ifIndex, String serviceName, int rrtype, |
| int rrclass, QueryListener listener) |
| throws DNSSDException |
| { return getInstance()._queryRecord( flags, ifIndex, serviceName, rrtype, rrclass, listener); } |
| |
| /** Asynchronously enumerate domains available for browsing and registration.<P> |
| |
| Currently, the only domain returned is "local.", but other domains will be returned in future.<P> |
| |
| The enumeration MUST be cancelled by calling {@link DNSSDService#stop} when no more domains |
| are to be found.<P> |
| @param flags |
| Possible values are: BROWSE_DOMAINS, REGISTRATION_DOMAINS. |
| <P> |
| @param ifIndex |
| If non-zero, specifies the interface on which to look for domains. |
| (the index for a given interface is determined via the if_nametoindex() |
| family of calls.) Most applications will pass 0 to enumerate domains on |
| all interfaces. |
| <P> |
| @param listener |
| This object will get called when domains are found. |
| <P> |
| @return A {@link DNSSDService} that controls the active enumeration. |
| |
| @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. |
| @see RuntimePermission |
| */ |
| public static DNSSDService enumerateDomains( int flags, int ifIndex, DomainListener listener) |
| throws DNSSDException |
| { return getInstance()._enumerateDomains( flags, ifIndex, listener); } |
| |
| /** Concatenate a three-part domain name (as provided to the listeners) into a |
| properly-escaped full domain name. Note that strings passed to listeners are |
| ALREADY ESCAPED where necessary.<P> |
| @param serviceName |
| The service name - any dots or slashes must NOT be escaped. |
| May be null (to construct a PTR record name, e.g. "_ftp._tcp.apple.com"). |
| <P> |
| @param regType |
| The registration type followed by the protocol, separated by a dot (e.g. "_ftp._tcp"). |
| <P> |
| @param domain |
| The domain name, e.g. "apple.com". Any literal dots or backslashes must be escaped. |
| <P> |
| @return The full domain name. |
| |
| @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. |
| @see RuntimePermission |
| */ |
| public static String constructFullName( String serviceName, String regType, String domain) |
| throws DNSSDException |
| { return getInstance()._constructFullName( serviceName, regType, domain); } |
| |
| /** Instruct the daemon to verify the validity of a resource record that appears to |
| be out of date. (e.g. because tcp connection to a service's target failed.) <P> |
| |
| Causes the record to be flushed from the daemon's cache (as well as all other |
| daemons' caches on the network) if the record is determined to be invalid.<P> |
| @param flags |
| Currently unused, reserved for future use. |
| <P> |
| @param ifIndex |
| If non-zero, specifies the interface on which to reconfirm the record |
| (the index for a given interface is determined via the if_nametoindex() |
| family of calls.) Passing 0 causes the name to be reconfirmed on all |
| interfaces. Passing -1 causes the name to be reconfirmed only on the |
| local host. |
| <P> |
| @param fullName |
| The resource record's full domain name. |
| <P> |
| @param rrtype |
| The resource record's type (e.g. PTR, SRV, etc) as defined in nameser.h. |
| <P> |
| @param rrclass |
| The class of the resource record, as defined in nameser.h (usually 1). |
| <P> |
| @param rdata |
| The raw rdata of the resource record. |
| |
| @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. |
| @see RuntimePermission |
| */ |
| public static void reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype, |
| int rrclass, byte[] rdata) |
| { getInstance()._reconfirmRecord( flags, ifIndex, fullName, rrtype, rrclass, rdata); } |
| |
| /** Return the canonical name of a particular interface index.<P> |
| @param ifIndex |
| A valid interface index. Must not be ALL_INTERFACES. |
| <P> |
| @return The name of the interface, which should match java.net.NetworkInterface.getName(). |
| |
| @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. |
| @see RuntimePermission |
| */ |
| public static String getNameForIfIndex( int ifIndex) |
| { return getInstance()._getNameForIfIndex( ifIndex); } |
| |
| /** Return the index of a named interface.<P> |
| @param ifName |
| A valid interface name. An example is java.net.NetworkInterface.getName(). |
| <P> |
| @return The interface index. |
| |
| @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. |
| @see RuntimePermission |
| */ |
| public static int getIfIndexForName( String ifName) |
| { return getInstance()._getIfIndexForName( ifName); } |
| |
| protected DNSSD() {} // prevent direct instantiation |
| |
| /** Return the single instance of DNSSD. */ |
| static protected final DNSSD getInstance() |
| { |
| SecurityManager sm = System.getSecurityManager(); |
| if ( sm != null) |
| sm.checkPermission( new RuntimePermission( "getDNSSDInstance")); |
| return fInstance; |
| } |
| |
| abstract protected DNSSDService _makeBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener listener) |
| throws DNSSDException; |
| |
| abstract protected DNSSDService _resolve( int flags, int ifIndex, String serviceName, String regType, |
| String domain, ResolveListener listener) |
| throws DNSSDException; |
| |
| abstract protected DNSSDRegistration _register( int flags, int ifIndex, String serviceName, String regType, |
| String domain, String host, int port, TXTRecord txtRecord, RegisterListener listener) |
| throws DNSSDException; |
| |
| abstract protected DNSSDRecordRegistrar _createRecordRegistrar( RegisterRecordListener listener) |
| throws DNSSDException; |
| |
| abstract protected DNSSDService _queryRecord( int flags, int ifIndex, String serviceName, int rrtype, |
| int rrclass, QueryListener listener) |
| throws DNSSDException; |
| |
| abstract protected DNSSDService _enumerateDomains( int flags, int ifIndex, DomainListener listener) |
| throws DNSSDException; |
| |
| abstract protected String _constructFullName( String serviceName, String regType, String domain) |
| throws DNSSDException; |
| |
| abstract protected void _reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype, |
| int rrclass, byte[] rdata); |
| |
| abstract protected String _getNameForIfIndex( int ifIndex); |
| |
| abstract protected int _getIfIndexForName( String ifName); |
| |
| protected static DNSSD fInstance; |
| |
| static |
| { |
| try |
| { |
| String name = System.getProperty( "com.apple.dnssd.DNSSD" ); |
| if( name == null ) |
| name = "com.apple.dnssd.AppleDNSSD"; // Fall back to Apple-provided class. |
| fInstance = (DNSSD) Class.forName( name ).newInstance(); |
| } |
| catch( Exception e ) |
| { |
| throw new InternalError( "cannot instantiate DNSSD" + e ); |
| } |
| } |
| } |
| |
| |
| // Concrete implementation of DNSSDException |
| class AppleDNSSDException extends DNSSDException |
| { |
| public AppleDNSSDException( int errorCode) { fErrorCode = errorCode; } |
| |
| public int getErrorCode() { return fErrorCode; } |
| |
| public String getMessage() |
| { |
| final String kMessages[] = { // should probably be put into a resource or something |
| "UNKNOWN", |
| "NO_SUCH_NAME", |
| "NO_MEMORY", |
| "BAD_PARAM", |
| "BAD_REFERENCE", |
| "BAD_STATE", |
| "BAD_FLAGS", |
| "UNSUPPORTED", |
| "NOT_INITIALIZED", |
| "NO_CACHE", |
| "ALREADY_REGISTERED", |
| "NAME_CONFLICT", |
| "INVALID", |
| "FIREWALL", |
| "INCOMPATIBLE", |
| "BAD_INTERFACE_INDEX", |
| "REFUSED", |
| "NOSUCHRECORD", |
| "NOAUTH", |
| "NOSUCHKEY", |
| "NATTRAVERSAL", |
| "DOUBLENAT", |
| "BADTIME", |
| "BADSIG", |
| "BADKEY", |
| "TRANSIENT", |
| "SERVICENOTRUNNING", |
| "NATPORTMAPPINGUNSUPPORTED", |
| "NATPORTMAPPINGDISABLED" |
| }; |
| |
| if ( fErrorCode <= UNKNOWN && fErrorCode > ( UNKNOWN - kMessages.length)) |
| { |
| return "DNS-SD Error " + String.valueOf( fErrorCode) + ": " + kMessages[ UNKNOWN - fErrorCode]; |
| } |
| else |
| return super.getMessage() + "(" + String.valueOf( fErrorCode) + ")"; |
| } |
| |
| protected int fErrorCode; |
| } |
| |
| // The concrete, default implementation. |
| class AppleDNSSD extends DNSSD |
| { |
| static |
| { |
| System.loadLibrary( "jdns_sd"); |
| |
| int libInitResult = InitLibrary( 2); // Current version number (must be sync'd with jnilib version) |
| |
| if ( libInitResult != DNSSDException.NO_ERROR) |
| throw new InternalError( "cannot instantiate DNSSD: " + new AppleDNSSDException( libInitResult).getMessage()); |
| } |
| |
| static public boolean hasAutoCallbacks; // Set by InitLibrary() to value of AUTO_CALLBACKS |
| |
| protected DNSSDService _makeBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener client) |
| throws DNSSDException |
| { |
| return new AppleBrowser( flags, ifIndex, regType, domain, client); |
| } |
| |
| protected DNSSDService _resolve( int flags, int ifIndex, String serviceName, String regType, |
| String domain, ResolveListener client) |
| throws DNSSDException |
| { |
| return new AppleResolver( flags, ifIndex, serviceName, regType, domain, client); |
| } |
| |
| protected DNSSDRegistration _register( int flags, int ifIndex, String serviceName, String regType, |
| String domain, String host, int port, TXTRecord txtRecord, RegisterListener client) |
| throws DNSSDException |
| { |
| return new AppleRegistration( flags, ifIndex, serviceName, regType, domain, host, port, |
| ( txtRecord != null) ? txtRecord.getRawBytes() : null, client); |
| } |
| |
| protected DNSSDRecordRegistrar _createRecordRegistrar( RegisterRecordListener listener) |
| throws DNSSDException |
| { |
| return new AppleRecordRegistrar( listener); |
| } |
| |
| protected DNSSDService _queryRecord( int flags, int ifIndex, String serviceName, int rrtype, |
| int rrclass, QueryListener client) |
| throws DNSSDException |
| { |
| return new AppleQuery( flags, ifIndex, serviceName, rrtype, rrclass, client); |
| } |
| |
| protected DNSSDService _enumerateDomains( int flags, int ifIndex, DomainListener listener) |
| throws DNSSDException |
| { |
| return new AppleDomainEnum( flags, ifIndex, listener); |
| } |
| |
| protected String _constructFullName( String serviceName, String regType, String domain) |
| throws DNSSDException |
| { |
| String[] responseHolder = new String[1]; // lame maneuver to get around Java's lack of reference parameters |
| |
| int rc = ConstructName( serviceName, regType, domain, responseHolder); |
| if ( rc != 0) |
| throw new AppleDNSSDException( rc); |
| |
| return responseHolder[0]; |
| } |
| |
| protected void _reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype, |
| int rrclass, byte[] rdata) |
| { |
| ReconfirmRecord( flags, ifIndex, fullName, rrtype, rrclass, rdata); |
| } |
| |
| protected String _getNameForIfIndex( int ifIndex) |
| { |
| return GetNameForIfIndex( ifIndex); |
| } |
| |
| protected int _getIfIndexForName( String ifName) |
| { |
| return GetIfIndexForName( ifName); |
| } |
| |
| |
| protected native int ConstructName( String serviceName, String regType, String domain, String[] pOut); |
| |
| protected native void ReconfirmRecord( int flags, int ifIndex, String fullName, int rrtype, |
| int rrclass, byte[] rdata); |
| |
| protected native String GetNameForIfIndex( int ifIndex); |
| |
| protected native int GetIfIndexForName( String ifName); |
| |
| protected static native int InitLibrary( int callerVersion); |
| } |
| |
| class AppleService implements DNSSDService, Runnable |
| { |
| public AppleService(BaseListener listener) { fNativeContext = 0; fListener = listener; } |
| |
| public void stop() { this.HaltOperation(); } |
| |
| /* Block until data arrives, or one second passes. Returns 1 if data present, 0 otherwise. */ |
| protected native int BlockForData(); |
| |
| /* Call ProcessResults when data appears on socket descriptor. */ |
| protected native int ProcessResults(); |
| |
| protected synchronized native void HaltOperation(); |
| |
| protected void ThrowOnErr( int rc) throws DNSSDException |
| { |
| if ( rc != 0) |
| throw new AppleDNSSDException( rc); |
| } |
| |
| protected long /* warning */ fNativeContext; // Private storage for native side |
| |
| public void run() |
| { |
| while ( true ) |
| { |
| // Note: We want to allow our DNS-SD operation to be stopped from other threads, so we have to |
| // block waiting for data *outside* the synchronized section. Because we're doing this unsynchronized |
| // we have to write some careful code. Suppose our DNS-SD operation is stopped from some other thread, |
| // and then immediately afterwards that thread (or some third, unrelated thread) starts a new DNS-SD |
| // operation. The Unix kernel always allocates the lowest available file descriptor to a new socket, |
| // so the same file descriptor is highly likely to be reused for the new operation, and if our old |
| // stale ServiceThread accidentally consumes bytes off that new socket we'll get really messed up. |
| // To guard against that, before calling ProcessResults we check to ensure that our |
| // fNativeContext has not been deleted, which is a telltale sign that our operation was stopped. |
| // After calling ProcessResults we check again, because it's extremely common for callback |
| // functions to stop their own operation and start others. For example, a resolveListener callback |
| // may well stop the resolve and then start a QueryRecord call to monitor the TXT record. |
| // |
| // The remaining risk is that between our checking fNativeContext and calling ProcessResults(), |
| // some other thread could stop the operation and start a new one using same file descriptor, and |
| // we wouldn't know. To prevent this, the AppleService object's HaltOperation() routine is declared |
| // synchronized and we perform our checks synchronized on the AppleService object, which ensures |
| // that HaltOperation() can't execute while we're doing it. Because Java locks are re-entrant this |
| // locking DOESN'T prevent the callback routine from stopping its own operation, but DOES prevent |
| // any other thread from stopping it until after the callback has completed and returned to us here. |
| |
| int result = BlockForData(); |
| synchronized (this) |
| { |
| if (fNativeContext == 0) break; // Some other thread stopped our DNSSD operation; time to terminate this thread |
| if (result == 0) continue; // If BlockForData() said there was no data, go back and block again |
| result = ProcessResults(); |
| if (fNativeContext == 0) break; // Event listener stopped its own DNSSD operation; terminate this thread |
| if (result != 0) { fListener.operationFailed(this, result); break; } // If error, notify listener |
| } |
| } |
| } |
| |
| protected BaseListener fListener; |
| } |
| |
| |
| class AppleBrowser extends AppleService |
| { |
| public AppleBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener client) |
| throws DNSSDException |
| { |
| super(client); |
| this.ThrowOnErr( this.CreateBrowser( flags, ifIndex, regType, domain)); |
| if ( !AppleDNSSD.hasAutoCallbacks) |
| new Thread(this).start(); |
| } |
| |
| // Sets fNativeContext. Returns non-zero on error. |
| protected native int CreateBrowser( int flags, int ifIndex, String regType, String domain); |
| } |
| |
| class AppleResolver extends AppleService |
| { |
| public AppleResolver( int flags, int ifIndex, String serviceName, String regType, |
| String domain, ResolveListener client) |
| throws DNSSDException |
| { |
| super(client); |
| this.ThrowOnErr( this.CreateResolver( flags, ifIndex, serviceName, regType, domain)); |
| if ( !AppleDNSSD.hasAutoCallbacks) |
| new Thread(this).start(); |
| } |
| |
| // Sets fNativeContext. Returns non-zero on error. |
| protected native int CreateResolver( int flags, int ifIndex, String serviceName, String regType, |
| String domain); |
| } |
| |
| // An AppleDNSRecord is a simple wrapper around a dns_sd DNSRecord. |
| class AppleDNSRecord implements DNSRecord |
| { |
| public AppleDNSRecord( AppleService owner) |
| { |
| fOwner = owner; |
| fRecord = 0; // record always starts out empty |
| } |
| |
| public void update( int flags, byte[] rData, int ttl) |
| throws DNSSDException |
| { |
| this.ThrowOnErr( this.Update( flags, rData, ttl)); |
| } |
| |
| public void remove() |
| throws DNSSDException |
| { |
| this.ThrowOnErr( this.Remove()); |
| } |
| |
| protected long fRecord; // Really a DNSRecord; sizeof(long) == sizeof(void*) ? |
| protected AppleService fOwner; |
| |
| protected void ThrowOnErr( int rc) throws DNSSDException |
| { |
| if ( rc != 0) |
| throw new AppleDNSSDException( rc); |
| } |
| |
| protected native int Update( int flags, byte[] rData, int ttl); |
| |
| protected native int Remove(); |
| } |
| |
| class AppleRegistration extends AppleService implements DNSSDRegistration |
| { |
| public AppleRegistration( int flags, int ifIndex, String serviceName, String regType, String domain, |
| String host, int port, byte[] txtRecord, RegisterListener client) |
| throws DNSSDException |
| { |
| super(client); |
| this.ThrowOnErr( this.BeginRegister( ifIndex, flags, serviceName, regType, domain, host, port, txtRecord)); |
| if ( !AppleDNSSD.hasAutoCallbacks) |
| new Thread(this).start(); |
| } |
| |
| public DNSRecord addRecord( int flags, int rrType, byte[] rData, int ttl) |
| throws DNSSDException |
| { |
| AppleDNSRecord newRecord = new AppleDNSRecord( this); |
| |
| this.ThrowOnErr( this.AddRecord( flags, rrType, rData, ttl, newRecord)); |
| return newRecord; |
| } |
| |
| public DNSRecord getTXTRecord() |
| throws DNSSDException |
| { |
| return new AppleDNSRecord( this); // A record with ref 0 is understood to be primary TXT record |
| } |
| |
| // Sets fNativeContext. Returns non-zero on error. |
| protected native int BeginRegister( int ifIndex, int flags, String serviceName, String regType, |
| String domain, String host, int port, byte[] txtRecord); |
| |
| // Sets fNativeContext. Returns non-zero on error. |
| protected native int AddRecord( int flags, int rrType, byte[] rData, int ttl, AppleDNSRecord destObj); |
| } |
| |
| class AppleRecordRegistrar extends AppleService implements DNSSDRecordRegistrar |
| { |
| public AppleRecordRegistrar( RegisterRecordListener listener) |
| throws DNSSDException |
| { |
| super(listener); |
| this.ThrowOnErr( this.CreateConnection()); |
| if ( !AppleDNSSD.hasAutoCallbacks) |
| new Thread(this).start(); |
| } |
| |
| public DNSRecord registerRecord( int flags, int ifIndex, String fullname, int rrtype, |
| int rrclass, byte[] rdata, int ttl) |
| throws DNSSDException |
| { |
| AppleDNSRecord newRecord = new AppleDNSRecord( this); |
| |
| this.ThrowOnErr( this.RegisterRecord( flags, ifIndex, fullname, rrtype, rrclass, rdata, ttl, newRecord)); |
| return newRecord; |
| } |
| |
| // Sets fNativeContext. Returns non-zero on error. |
| protected native int CreateConnection(); |
| |
| // Sets fNativeContext. Returns non-zero on error. |
| protected native int RegisterRecord( int flags, int ifIndex, String fullname, int rrtype, |
| int rrclass, byte[] rdata, int ttl, AppleDNSRecord destObj); |
| } |
| |
| class AppleQuery extends AppleService |
| { |
| public AppleQuery( int flags, int ifIndex, String serviceName, int rrtype, |
| int rrclass, QueryListener client) |
| throws DNSSDException |
| { |
| super(client); |
| this.ThrowOnErr( this.CreateQuery( flags, ifIndex, serviceName, rrtype, rrclass)); |
| if ( !AppleDNSSD.hasAutoCallbacks) |
| new Thread(this).start(); |
| } |
| |
| // Sets fNativeContext. Returns non-zero on error. |
| protected native int CreateQuery( int flags, int ifIndex, String serviceName, int rrtype, int rrclass); |
| } |
| |
| class AppleDomainEnum extends AppleService |
| { |
| public AppleDomainEnum( int flags, int ifIndex, DomainListener client) |
| throws DNSSDException |
| { |
| super(client); |
| this.ThrowOnErr( this.BeginEnum( flags, ifIndex)); |
| if ( !AppleDNSSD.hasAutoCallbacks) |
| new Thread(this).start(); |
| } |
| |
| // Sets fNativeContext. Returns non-zero on error. |
| protected native int BeginEnum( int flags, int ifIndex); |
| } |
| |
| |