xref: /titanic_44/usr/src/lib/libdns_sd/java/com/apple/dnssd/DNSSD.java (revision 4b22b9337f359bfd063322244f5336cc7c6ffcfa)
1 /* -*- Mode: Java; tab-width: 4 -*-
2  *
3  * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16 
17     Change History (most recent first):
18 
19 $Log: DNSSD.java,v $
20 Revision 1.11  2006/08/14 23:25:08  cheshire
21 Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0
22 
23 Revision 1.10  2006/06/20 23:05:55  rpantos
24 <rdar://problem/3839132> Java needs to implement DNSServiceRegisterRecord equivalent
25 
26 Revision 1.9  2005/10/26 01:52:24  cheshire
27 <rdar://problem/4316286> Race condition in Java code (doesn't work at all on Linux)
28 
29 Revision 1.8  2005/07/11 01:55:21  cheshire
30 <rdar://problem/4175511> Race condition in Java API
31 
32 Revision 1.7  2005/07/05 13:01:52  cheshire
33 <rdar://problem/4169791> If mDNSResponder daemon is stopped, Java API spins, burning CPU time
34 
35 Revision 1.6  2005/07/05 00:02:25  cheshire
36 Add missing comma
37 
38 Revision 1.5  2005/07/04 21:13:47  cheshire
39 Add missing error message strings
40 
41 Revision 1.4  2004/12/11 03:00:59  rpantos
42 <rdar://problem/3907498> Java DNSRecord API should be cleaned up
43 
44 Revision 1.3  2004/11/12 03:23:08  rpantos
45 rdar://problem/3809541 implement getIfIndexForName, getNameForIfIndex.
46 
47 Revision 1.2  2004/05/20 17:43:18  cheshire
48 Fix invalid UTF-8 characters in file
49 
50 Revision 1.1  2004/04/30 16:32:34  rpantos
51 First checked in.
52 
53 
54 	This file declares and implements DNSSD, the central Java factory class
55 	for doing DNS Service Discovery. It includes the mostly-abstract public
56 	interface, as well as the Apple* implementation subclasses.
57  */
58 
59 /*
60  * ident	"%Z%%M%	%I%	%E% SMI"
61  */
62 
63 package	com.apple.dnssd;
64 
65 
66 /**
67 	DNSSD provides access to DNS Service Discovery features of ZeroConf networking.<P>
68 
69 	It is a factory class that is used to invoke registration and discovery-related
70 	operations. Most operations are non-blocking; clients are called back through an interface
71 	with the result of an operation. Callbacks are made from a separate worker thread.<P>
72 
73 	For example, in this program<P>
74 	<PRE><CODE>
75     class   MyClient implements BrowseListener {
76         void    lookForWebServers() {
77             myBrowser = DNSSD.browse("_http_.tcp", this);
78         }
79 
80         public void serviceFound(DNSSDService browser, int flags, int ifIndex,
81                             String serviceName, String regType, String domain) {}
82         ...
83     }</CODE></PRE>
84 	<CODE>MyClient.serviceFound()</CODE> would be called for every HTTP server discovered in the
85 	default browse domain(s).
86 */
87 
88 abstract public class	DNSSD
89 {
90 	/**	Flag indicates to a {@link BrowseListener} that another result is
91 		queued.  Applications should not update their UI to display browse
92 		results if the MORE_COMING flag is set; they will be called at least once
93 		more with the flag clear.
94 	*/
95 	public static final int		MORE_COMING = ( 1 << 0 );
96 
97 	/** If flag is set in a {@link DomainListener} callback, indicates that the result is the default domain. */
98 	public static final int		DEFAULT = ( 1 << 2 );
99 
100     /**	If flag is set, a name conflict will trigger an exception when registering non-shared records.<P>
101     	A name must be explicitly specified when registering a service if this bit is set
102     	(i.e. the default name may not not be used).
103      */
104 	public static final int		NO_AUTO_RENAME = ( 1 << 3 );
105 
106 	/**	If flag is set, allow multiple records with this name on the network (e.g. PTR records)
107 		when registering individual records on a {@link DNSSDRegistration}.
108 	*/
109 	public static final int		SHARED = ( 1 << 4 );
110 
111 	/**	If flag is set, records with this name must be unique on the network (e.g. SRV records). */
112 	public static final int		UNIQUE = ( 1 << 5 );
113 
114 	/** Set flag when calling enumerateDomains() to restrict results to domains recommended for browsing. */
115 	public static final int		BROWSE_DOMAINS = ( 1 << 6 );
116 	/** Set flag when calling enumerateDomains() to restrict results to domains recommended for registration. */
117 	public static final int		REGISTRATION_DOMAINS = ( 1 << 7 );
118 
119 	/** Maximum length, in bytes, of a domain name represented as an escaped C-String. */
120     public static final int     MAX_DOMAIN_NAME = 1005;
121 
122 	/** Pass for ifIndex to specify all available interfaces. */
123     public static final int     ALL_INTERFACES = 0;
124 
125 	/** Pass for ifIndex to specify the localhost interface. */
126     public static final int     LOCALHOST_ONLY = -1;
127 
128 	/** Browse for instances of a service.<P>
129 
130 		Note: browsing consumes network bandwidth. Call {@link DNSSDService#stop} when you have finished browsing.<P>
131 
132 		@param	flags
133 					Currently ignored, reserved for future use.
134 		<P>
135 		@param	ifIndex
136 					If non-zero, specifies the interface on which to browse for services
137 					(the index for a given interface is determined via the if_nametoindex()
138 					family of calls.)  Most applications will pass 0 to browse on all available
139 					interfaces.  Pass -1 to only browse for services provided on the local host.
140 		<P>
141 		@param	regType
142 					The registration type being browsed for followed by the protocol, separated by a
143 					dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp".
144 		<P>
145 		@param	domain
146 					If non-null, specifies the domain on which to browse for services.
147 					Most applications will not specify a domain, instead browsing on the
148 					default domain(s).
149 		<P>
150 		@param	listener
151 					This object will get called when instances of the service are discovered (or disappear).
152 		<P>
153 		@return		A {@link DNSSDService} that represents the active browse operation.
154 
155 		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
156 		@see    RuntimePermission
157 	*/
browse( int flags, int ifIndex, String regType, String domain, BrowseListener listener)158 	public static DNSSDService	browse( int flags, int ifIndex, String regType, String domain, BrowseListener listener)
159 	throws DNSSDException
160 	{ return getInstance()._makeBrowser( flags, ifIndex, regType, domain, listener); }
161 
162 	/** Browse for instances of a service. Use default flags, ifIndex and domain.<P>
163 
164 		@param	regType
165 					The registration type being browsed for followed by the protocol, separated by a
166 					dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp".
167 		<P>
168 		@param	listener
169 					This object will get called when instances of the service are discovered (or disappear).
170 		<P>
171 		@return		A {@link DNSSDService} that represents the active browse operation.
172 
173 		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
174 		@see    RuntimePermission
175 	*/
browse( String regType, BrowseListener listener)176 	public static DNSSDService	browse( String regType, BrowseListener listener)
177 	throws DNSSDException
178 	{ return browse( 0, 0, regType, "", listener); }
179 
180 	/** Resolve a service name discovered via browse() to a target host name, port number, and txt record.<P>
181 
182 		Note: Applications should NOT use resolve() solely for txt record monitoring - use
183 		queryRecord() instead, as it is more efficient for this task.<P>
184 
185 		Note: When the desired results have been returned, the client MUST terminate the resolve by
186 		calling {@link DNSSDService#stop}.<P>
187 
188 		Note: resolve() behaves correctly for typical services that have a single SRV record and
189  		a single TXT record (the TXT record may be empty.)  To resolve non-standard services with
190  		multiple SRV or TXT records, use queryRecord().<P>
191 
192 		@param	flags
193 					Currently ignored, reserved for future use.
194 		<P>
195 		@param	ifIndex
196 					The interface on which to resolve the service.  The client should
197 					pass the interface on which the serviceName was discovered (i.e.
198 					the ifIndex passed to the serviceFound() callback)
199 					or 0 to resolve the named service on all available interfaces.
200 		<P>
201 		@param	serviceName
202 					The servicename to be resolved.
203 		<P>
204 		@param	regType
205 					The registration type being resolved followed by the protocol, separated by a
206 					dot (e.g. "_ftp._tcp").  The transport protocol must be "_tcp" or "_udp".
207 		<P>
208 		@param	domain
209 					The domain on which the service is registered, i.e. the domain passed
210 					to the serviceFound() callback.
211 		<P>
212 		@param	listener
213 					This object will get called when the service is resolved.
214 		<P>
215 		@return		A {@link DNSSDService} that represents the active resolve operation.
216 
217 		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
218 		@see    RuntimePermission
219 	*/
resolve( int flags, int ifIndex, String serviceName, String regType, String domain, ResolveListener listener)220 	public static DNSSDService	resolve( int flags, int ifIndex, String serviceName, String regType,
221 										String domain, ResolveListener listener)
222 	throws DNSSDException
223 	{ return getInstance()._resolve( flags, ifIndex, serviceName, regType, domain, listener); }
224 
225 	/** Register a service, to be discovered via browse() and resolve() calls.<P>
226 		@param	flags
227 					Possible values are: NO_AUTO_RENAME.
228 		<P>
229 		@param	ifIndex
230 					If non-zero, specifies the interface on which to register the service
231 					(the index for a given interface is determined via the if_nametoindex()
232 					family of calls.)  Most applications will pass 0 to register on all
233 					available interfaces.  Pass -1 to register a service only on the local
234 					machine (service will not be visible to remote hosts).
235 		<P>
236 		@param	serviceName
237 					If non-null, specifies the service name to be registered.
238 					Applications need not specify a name, in which case the
239 					computer name is used (this name is communicated to the client via
240 					the serviceRegistered() callback).
241 		<P>
242 		@param	regType
243 					The registration type being registered followed by the protocol, separated by a
244 					dot (e.g. "_ftp._tcp").  The transport protocol must be "_tcp" or "_udp".
245 		<P>
246 		@param	domain
247 					If non-null, specifies the domain on which to advertise the service.
248 					Most applications will not specify a domain, instead automatically
249 					registering in the default domain(s).
250 		<P>
251 		@param	host
252 					If non-null, specifies the SRV target host name.  Most applications
253 					will not specify a host, instead automatically using the machine's
254 					default host name(s).  Note that specifying a non-null host does NOT
255 					create an address record for that host - the application is responsible
256 					for ensuring that the appropriate address record exists, or creating it
257 					via {@link DNSSDRegistration#addRecord}.
258 		<P>
259 		@param	port
260 					The port on which the service accepts connections.  Pass 0 for a
261 					"placeholder" service (i.e. a service that will not be discovered by
262 					browsing, but will cause a name conflict if another client tries to
263 					register that same name.)  Most clients will not use placeholder services.
264 		<P>
265 		@param	txtRecord
266 					The txt record rdata.  May be null.  Note that a non-null txtRecord
267 					MUST be a properly formatted DNS TXT record, i.e. &lt;length byte&gt; &lt;data&gt;
268 					&lt;length byte&gt; &lt;data&gt; ...
269 		<P>
270 		@param	listener
271 					This object will get called when the service is registered.
272 		<P>
273 		@return		A {@link DNSSDRegistration} that controls the active registration.
274 
275 		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
276 		@see    RuntimePermission
277 	*/
register( int flags, int ifIndex, String serviceName, String regType, String domain, String host, int port, TXTRecord txtRecord, RegisterListener listener)278 	public static DNSSDRegistration	register( int flags, int ifIndex, String serviceName, String regType,
279 									String domain, String host, int port, TXTRecord txtRecord, RegisterListener listener)
280 	throws DNSSDException
281 	{ return getInstance()._register( flags, ifIndex, serviceName, regType, domain, host, port, txtRecord, listener); }
282 
283 	/** Register a service, to be discovered via browse() and resolve() calls. Use default flags, ifIndex, domain, host and txtRecord.<P>
284 		@param	serviceName
285 					If non-null, specifies the service name to be registered.
286 					Applications need not specify a name, in which case the
287 					computer name is used (this name is communicated to the client via
288 					the serviceRegistered() callback).
289 		<P>
290 		@param	regType
291 					The registration type being registered followed by the protocol, separated by a
292 					dot (e.g. "_ftp._tcp").  The transport protocol must be "_tcp" or "_udp".
293 		<P>
294 		@param	port
295 					The port on which the service accepts connections.  Pass 0 for a
296 					"placeholder" service (i.e. a service that will not be discovered by
297 					browsing, but will cause a name conflict if another client tries to
298 					register that same name.)  Most clients will not use placeholder services.
299 		<P>
300 		@param	listener
301 					This object will get called when the service is registered.
302 		<P>
303 		@return		A {@link DNSSDRegistration} that controls the active registration.
304 
305 		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
306 		@see    RuntimePermission
307 	*/
register( String serviceName, String regType, int port, RegisterListener listener)308 	public static DNSSDRegistration	register( String serviceName, String regType, int port, RegisterListener listener)
309 	throws DNSSDException
310 	{ return register( 0, 0, serviceName, regType, null, null, port, null, listener); }
311 
312 	/** Create a {@link DNSSDRecordRegistrar} allowing efficient registration of
313 		multiple individual records.<P>
314 		<P>
315 		@return		A {@link DNSSDRecordRegistrar} that can be used to register records.
316 
317 		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
318 		@see    RuntimePermission
319 	*/
createRecordRegistrar( RegisterRecordListener listener)320 	public static DNSSDRecordRegistrar	createRecordRegistrar( RegisterRecordListener listener)
321 	throws DNSSDException
322 	{ return getInstance()._createRecordRegistrar( listener); }
323 
324 	/** Query for an arbitrary DNS record.<P>
325 		@param	flags
326 					Possible values are: MORE_COMING.
327 		<P>
328 		@param	ifIndex
329 					If non-zero, specifies the interface on which to issue the query
330 					(the index for a given interface is determined via the if_nametoindex()
331 					family of calls.)  Passing 0 causes the name to be queried for on all
332 					interfaces.  Passing -1 causes the name to be queried for only on the
333 					local host.
334 		<P>
335 		@param	serviceName
336 					The full domain name of the resource record to be queried for.
337 		<P>
338 		@param	rrtype
339 					The numerical type of the resource record to be queried for (e.g. PTR, SRV, etc)
340 					as defined in nameser.h.
341 		<P>
342 		@param	rrclass
343 					The class of the resource record, as defined in nameser.h
344 					(usually 1 for the Internet class).
345 		<P>
346 		@param	listener
347 					This object will get called when the query completes.
348 		<P>
349 		@return		A {@link DNSSDService} that controls the active query.
350 
351 		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
352 		@see    RuntimePermission
353 	*/
queryRecord( int flags, int ifIndex, String serviceName, int rrtype, int rrclass, QueryListener listener)354 	public static DNSSDService	queryRecord( int flags, int ifIndex, String serviceName, int rrtype,
355 										int rrclass, QueryListener listener)
356 	throws DNSSDException
357 	{ return getInstance()._queryRecord( flags, ifIndex, serviceName, rrtype, rrclass, listener); }
358 
359 	/** Asynchronously enumerate domains available for browsing and registration.<P>
360 
361 		Currently, the only domain returned is "local.", but other domains will be returned in future.<P>
362 
363 		The enumeration MUST be cancelled by calling {@link DNSSDService#stop} when no more domains
364 		are to be found.<P>
365 		@param	flags
366 					Possible values are: BROWSE_DOMAINS, REGISTRATION_DOMAINS.
367 		<P>
368 		@param	ifIndex
369 					If non-zero, specifies the interface on which to look for domains.
370 					(the index for a given interface is determined via the if_nametoindex()
371 					family of calls.)  Most applications will pass 0 to enumerate domains on
372 					all interfaces.
373 		<P>
374 		@param	listener
375 					This object will get called when domains are found.
376 		<P>
377 		@return		A {@link DNSSDService} that controls the active enumeration.
378 
379 		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
380 		@see    RuntimePermission
381 	*/
enumerateDomains( int flags, int ifIndex, DomainListener listener)382 	public static DNSSDService	enumerateDomains( int flags, int ifIndex, DomainListener listener)
383 	throws DNSSDException
384 	{ return getInstance()._enumerateDomains( flags, ifIndex, listener); }
385 
386 	/**	Concatenate a three-part domain name (as provided to the listeners) into a
387 		properly-escaped full domain name. Note that strings passed to listeners are
388 		ALREADY ESCAPED where necessary.<P>
389 		@param	serviceName
390 					The service name - any dots or slashes must NOT be escaped.
391 					May be null (to construct a PTR record name, e.g. "_ftp._tcp.apple.com").
392 		<P>
393 		@param	regType
394 					The registration type followed by the protocol, separated by a dot (e.g. "_ftp._tcp").
395 		<P>
396 		@param	domain
397 					The domain name, e.g. "apple.com".  Any literal dots or backslashes must be escaped.
398 		<P>
399 		@return		The full domain name.
400 
401 		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
402 		@see    RuntimePermission
403 	*/
constructFullName( String serviceName, String regType, String domain)404 	public static String		constructFullName( String serviceName, String regType, String domain)
405 	throws DNSSDException
406 	{ return getInstance()._constructFullName( serviceName, regType, domain); }
407 
408 	/** Instruct the daemon to verify the validity of a resource record that appears to
409 		be out of date. (e.g. because tcp connection to a service's target failed.) <P>
410 
411 		Causes the record to be flushed from the daemon's cache (as well as all other
412 		daemons' caches on the network) if the record is determined to be invalid.<P>
413 		@param	flags
414 					Currently unused, reserved for future use.
415 		<P>
416 		@param	ifIndex
417 					If non-zero, specifies the interface on which to reconfirm the record
418 					(the index for a given interface is determined via the if_nametoindex()
419 					family of calls.)  Passing 0 causes the name to be reconfirmed on all
420 					interfaces.  Passing -1 causes the name to be reconfirmed only on the
421 					local host.
422 		<P>
423 		@param	fullName
424 					The resource record's full domain name.
425 		<P>
426 		@param	rrtype
427 					The resource record's type (e.g. PTR, SRV, etc) as defined in nameser.h.
428 		<P>
429 		@param	rrclass
430 					The class of the resource record, as defined in nameser.h (usually 1).
431 		<P>
432 		@param	rdata
433 					The raw rdata of the resource record.
434 
435 		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
436 		@see    RuntimePermission
437 	*/
reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype, int rrclass, byte[] rdata)438 	public static void		reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype,
439 										int rrclass, byte[] rdata)
440 	{ getInstance()._reconfirmRecord( flags, ifIndex, fullName, rrtype, rrclass, rdata); }
441 
442 	/** Return the canonical name of a particular interface index.<P>
443 		@param	ifIndex
444 					A valid interface index. Must not be ALL_INTERFACES.
445 		<P>
446 		@return		The name of the interface, which should match java.net.NetworkInterface.getName().
447 
448 		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
449 		@see    RuntimePermission
450 	*/
getNameForIfIndex( int ifIndex)451 	public static String	getNameForIfIndex( int ifIndex)
452 	{ return getInstance()._getNameForIfIndex( ifIndex); }
453 
454 	/** Return the index of a named interface.<P>
455 		@param	ifName
456 					A valid interface name. An example is java.net.NetworkInterface.getName().
457 		<P>
458 		@return		The interface index.
459 
460 		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
461 		@see    RuntimePermission
462 	*/
getIfIndexForName( String ifName)463 	public static int		getIfIndexForName( String ifName)
464 	{ return getInstance()._getIfIndexForName( ifName); }
465 
DNSSD()466 	protected						DNSSD() {}	// prevent direct instantiation
467 
468 	/** Return the single instance of DNSSD. */
getInstance()469 	static protected final DNSSD	getInstance()
470 	{
471 		SecurityManager sm = System.getSecurityManager();
472         if ( sm != null)
473             sm.checkPermission( new RuntimePermission( "getDNSSDInstance"));
474 		 return fInstance;
475 	}
476 
_makeBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener listener)477 	abstract protected DNSSDService	_makeBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener listener)
478 	throws DNSSDException;
479 
_resolve( int flags, int ifIndex, String serviceName, String regType, String domain, ResolveListener listener)480 	abstract protected DNSSDService	_resolve( int flags, int ifIndex, String serviceName, String regType,
481 										String domain, ResolveListener listener)
482 	throws DNSSDException;
483 
_register( int flags, int ifIndex, String serviceName, String regType, String domain, String host, int port, TXTRecord txtRecord, RegisterListener listener)484 	abstract protected DNSSDRegistration	_register( int flags, int ifIndex, String serviceName, String regType,
485 									String domain, String host, int port, TXTRecord txtRecord, RegisterListener listener)
486 	throws DNSSDException;
487 
_createRecordRegistrar( RegisterRecordListener listener)488 	abstract protected DNSSDRecordRegistrar	_createRecordRegistrar( RegisterRecordListener listener)
489 	throws DNSSDException;
490 
_queryRecord( int flags, int ifIndex, String serviceName, int rrtype, int rrclass, QueryListener listener)491 	abstract protected DNSSDService	_queryRecord( int flags, int ifIndex, String serviceName, int rrtype,
492 										int rrclass, QueryListener listener)
493 	throws DNSSDException;
494 
_enumerateDomains( int flags, int ifIndex, DomainListener listener)495 	abstract protected DNSSDService	_enumerateDomains( int flags, int ifIndex, DomainListener listener)
496 	throws DNSSDException;
497 
_constructFullName( String serviceName, String regType, String domain)498 	abstract protected String		_constructFullName( String serviceName, String regType, String domain)
499 	throws DNSSDException;
500 
_reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype, int rrclass, byte[] rdata)501 	abstract protected void			_reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype,
502 										int rrclass, byte[] rdata);
503 
_getNameForIfIndex( int ifIndex)504 	abstract protected String		_getNameForIfIndex( int ifIndex);
505 
_getIfIndexForName( String ifName)506 	abstract protected int			_getIfIndexForName( String ifName);
507 
508 	protected static DNSSD			fInstance;
509 
510 	static
511 	{
512 		try
513 		{
514 			String name = System.getProperty( "com.apple.dnssd.DNSSD" );
515 			if( name == null )
516 				name = "com.apple.dnssd.AppleDNSSD";	// Fall back to Apple-provided class.
517 			fInstance = (DNSSD) Class.forName( name ).newInstance();
518 		}
519 		catch( Exception e )
520 		{
521 			throw new InternalError( "cannot instantiate DNSSD" + e );
522 		}
523 	}
524 }
525 
526 
527 // Concrete implementation of DNSSDException
528 class	AppleDNSSDException extends DNSSDException
529 {
AppleDNSSDException( int errorCode)530 	public						AppleDNSSDException( int errorCode) { fErrorCode = errorCode; }
531 
getErrorCode()532 	public int					getErrorCode() { return fErrorCode; }
533 
getMessage()534 	public String				getMessage()
535 	{
536 		final String	kMessages[] = {		// should probably be put into a resource or something
537 			"UNKNOWN",
538 			"NO_SUCH_NAME",
539 			"NO_MEMORY",
540 			"BAD_PARAM",
541 			"BAD_REFERENCE",
542 			"BAD_STATE",
543 			"BAD_FLAGS",
544 			"UNSUPPORTED",
545 			"NOT_INITIALIZED",
546 			"NO_CACHE",
547 			"ALREADY_REGISTERED",
548 			"NAME_CONFLICT",
549 			"INVALID",
550 			"FIREWALL",
551 			"INCOMPATIBLE",
552 			"BAD_INTERFACE_INDEX",
553 			"REFUSED",
554 			"NOSUCHRECORD",
555 			"NOAUTH",
556 			"NOSUCHKEY",
557 			"NATTRAVERSAL",
558 			"DOUBLENAT",
559 			"BADTIME",
560 			"BADSIG",
561 			"BADKEY",
562 			"TRANSIENT"
563 		};
564 
565 		if ( fErrorCode <= UNKNOWN && fErrorCode > ( UNKNOWN - kMessages.length))
566 		{
567 			return "DNS-SD Error " + String.valueOf( fErrorCode) + ": " + kMessages[ UNKNOWN - fErrorCode];
568 		}
569 		else
570 			return super.getMessage() + "(" + String.valueOf( fErrorCode) + ")";
571 	}
572 
573 	protected int			fErrorCode;
574 }
575 
576 // The concrete, default implementation.
577 class	AppleDNSSD extends DNSSD
578 {
579 	static
580 	{
581 		System.loadLibrary( "jdns_sd");
582 
583 		int		libInitResult = InitLibrary( 1);
584 
585 		if ( libInitResult != DNSSDException.NO_ERROR)
586 			throw new InternalError( "cannot instantiate DNSSD: " + new AppleDNSSDException( libInitResult).getMessage());
587 	}
588 
589 	static public boolean	hasAutoCallbacks;	// Set by InitLibrary() to value of AUTO_CALLBACKS
590 
_makeBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener client)591 	protected DNSSDService	_makeBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener client)
592 	throws DNSSDException
593 	{
594 		return new AppleBrowser( flags, ifIndex, regType, domain, client);
595 	}
596 
_resolve( int flags, int ifIndex, String serviceName, String regType, String domain, ResolveListener client)597 	protected DNSSDService	_resolve( int flags, int ifIndex, String serviceName, String regType,
598 										String domain, ResolveListener client)
599 	throws DNSSDException
600 	{
601 		return new AppleResolver( flags, ifIndex, serviceName, regType, domain, client);
602 	}
603 
_register( int flags, int ifIndex, String serviceName, String regType, String domain, String host, int port, TXTRecord txtRecord, RegisterListener client)604 	protected DNSSDRegistration	_register( int flags, int ifIndex, String serviceName, String regType,
605 									String domain, String host, int port, TXTRecord txtRecord, RegisterListener client)
606 	throws DNSSDException
607 	{
608 		return new AppleRegistration( flags, ifIndex, serviceName, regType, domain, host, port,
609 										( txtRecord != null) ? txtRecord.getRawBytes() : null, client);
610 	}
611 
_createRecordRegistrar( RegisterRecordListener listener)612 	protected DNSSDRecordRegistrar	_createRecordRegistrar( RegisterRecordListener listener)
613 	throws DNSSDException
614 	{
615 		return new AppleRecordRegistrar( listener);
616 	}
617 
_queryRecord( int flags, int ifIndex, String serviceName, int rrtype, int rrclass, QueryListener client)618 	protected DNSSDService		_queryRecord( int flags, int ifIndex, String serviceName, int rrtype,
619 										int rrclass, QueryListener client)
620 	throws DNSSDException
621 	{
622 		return new AppleQuery( flags, ifIndex, serviceName, rrtype, rrclass, client);
623 	}
624 
_enumerateDomains( int flags, int ifIndex, DomainListener listener)625 	protected DNSSDService		_enumerateDomains( int flags, int ifIndex, DomainListener listener)
626 	throws DNSSDException
627 	{
628 		return new AppleDomainEnum( flags, ifIndex, listener);
629 	}
630 
_constructFullName( String serviceName, String regType, String domain)631 	protected String			_constructFullName( String serviceName, String regType, String domain)
632 	throws DNSSDException
633 	{
634 		String[]	responseHolder = new String[1];	// lame maneuver to get around Java's lack of reference parameters
635 
636 		int rc = ConstructName( serviceName, regType, domain, responseHolder);
637 		if ( rc != 0)
638 			throw new AppleDNSSDException( rc);
639 
640 		return responseHolder[0];
641 	}
642 
_reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype, int rrclass, byte[] rdata)643 	protected void				_reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype,
644 										int rrclass, byte[] rdata)
645 	{
646 		ReconfirmRecord( flags, ifIndex, fullName, rrtype, rrclass, rdata);
647 	}
648 
_getNameForIfIndex( int ifIndex)649 	protected String			_getNameForIfIndex( int ifIndex)
650 	{
651 		return GetNameForIfIndex( ifIndex);
652 	}
653 
_getIfIndexForName( String ifName)654 	protected int				_getIfIndexForName( String ifName)
655 	{
656 		return GetIfIndexForName( ifName);
657 	}
658 
659 
ConstructName( String serviceName, String regType, String domain, String[] pOut)660 	protected native int	ConstructName( String serviceName, String regType, String domain, String[] pOut);
661 
ReconfirmRecord( int flags, int ifIndex, String fullName, int rrtype, int rrclass, byte[] rdata)662 	protected native void	ReconfirmRecord( int flags, int ifIndex, String fullName, int rrtype,
663 										int rrclass, byte[] rdata);
664 
GetNameForIfIndex( int ifIndex)665 	protected native String	GetNameForIfIndex( int ifIndex);
666 
GetIfIndexForName( String ifName)667 	protected native int	GetIfIndexForName( String ifName);
668 
InitLibrary( int callerVersion)669 	protected static native int	InitLibrary( int callerVersion);
670 }
671 
672 class	AppleService implements DNSSDService, Runnable
673 {
AppleService(BaseListener listener)674 	public					AppleService(BaseListener listener)	{ fNativeContext = 0; fListener = listener; }
675 
stop()676 	public void				stop() { this.HaltOperation(); }
677 
678 	/* Block until data arrives, or one second passes. Returns 1 if data present, 0 otherwise. */
BlockForData()679 	protected native int	BlockForData();
680 
681 	/* Call ProcessResults when data appears on socket descriptor. */
ProcessResults()682 	protected native int	ProcessResults();
683 
HaltOperation()684 	protected synchronized native void HaltOperation();
685 
ThrowOnErr( int rc)686 	protected void			ThrowOnErr( int rc) throws DNSSDException
687 	{
688 		if ( rc != 0)
689 			throw new AppleDNSSDException( rc);
690 	}
691 
692 	protected long	/* warning */	fNativeContext;		// Private storage for native side
693 
run()694 	public void		run()
695 	{
696 		while ( true )
697 		{
698 			// Note: We want to allow our DNS-SD operation to be stopped from other threads, so we have to
699 			// block waiting for data *outside* the synchronized section. Because we're doing this unsynchronized
700 			// we have to write some careful code. Suppose our DNS-SD operation is stopped from some other thread,
701 			// and then immediately afterwards that thread (or some third, unrelated thread) starts a new DNS-SD
702 			// operation. The Unix kernel always allocates the lowest available file descriptor to a new socket,
703 			// so the same file descriptor is highly likely to be reused for the new operation, and if our old
704 			// stale ServiceThread accidentally consumes bytes off that new socket we'll get really messed up.
705 			// To guard against that, before calling ProcessResults we check to ensure that our
706 			// fNativeContext has not been deleted, which is a telltale sign that our operation was stopped.
707 			// After calling ProcessResults we check again, because it's extremely common for callback
708 			// functions to stop their own operation and start others. For example, a resolveListener callback
709 			// may well stop the resolve and then start a QueryRecord call to monitor the TXT record.
710 			//
711 			// The remaining risk is that between our checking fNativeContext and calling ProcessResults(),
712 			// some other thread could stop the operation and start a new one using same file descriptor, and
713 			// we wouldn't know. To prevent this, the AppleService object's HaltOperation() routine is declared
714 			// synchronized and we perform our checks synchronized on the AppleService object, which ensures
715 			// that HaltOperation() can't execute while we're doing it. Because Java locks are re-entrant this
716 			// locking DOESN'T prevent the callback routine from stopping its own operation, but DOES prevent
717 			// any other thread from stopping it until after the callback has completed and returned to us here.
718 
719 			int result = BlockForData();
720 			synchronized (this)
721 			{
722 				if (fNativeContext == 0) break;	// Some other thread stopped our DNSSD operation; time to terminate this thread
723 				if (result == 0) continue;		// If BlockForData() said there was no data, go back and block again
724 				result = ProcessResults();
725 				if (fNativeContext == 0) break;	// Event listener stopped its own DNSSD operation; terminate this thread
726 				if (result != 0) { fListener.operationFailed(this, result); break; }	// If error, notify listener
727 			}
728 		}
729 	}
730 
731 	protected BaseListener fListener;
732 }
733 
734 
735 class	AppleBrowser extends AppleService
736 {
AppleBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener client)737 	public			AppleBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener client)
738 	throws DNSSDException
739 	{
740 		super(client);
741 		this.ThrowOnErr( this.CreateBrowser( flags, ifIndex, regType, domain));
742 		if ( !AppleDNSSD.hasAutoCallbacks)
743 			new Thread(this).start();
744 	}
745 
746 	// Sets fNativeContext. Returns non-zero on error.
CreateBrowser( int flags, int ifIndex, String regType, String domain)747 	protected native int	CreateBrowser( int flags, int ifIndex, String regType, String domain);
748 }
749 
750 class	AppleResolver extends AppleService
751 {
AppleResolver( int flags, int ifIndex, String serviceName, String regType, String domain, ResolveListener client)752 	public			AppleResolver( int flags, int ifIndex, String serviceName, String regType,
753 									String domain, ResolveListener client)
754 	throws DNSSDException
755 	{
756 		super(client);
757 		this.ThrowOnErr( this.CreateResolver( flags, ifIndex, serviceName, regType, domain));
758 		if ( !AppleDNSSD.hasAutoCallbacks)
759 			new Thread(this).start();
760 	}
761 
762 	// Sets fNativeContext. Returns non-zero on error.
CreateResolver( int flags, int ifIndex, String serviceName, String regType, String domain)763 	protected native int	CreateResolver( int flags, int ifIndex, String serviceName, String regType,
764 											String domain);
765 }
766 
767 // An AppleDNSRecord is a simple wrapper around a dns_sd DNSRecord.
768 class	AppleDNSRecord implements DNSRecord
769 {
AppleDNSRecord( AppleService owner)770 	public			AppleDNSRecord( AppleService owner)
771 	{
772 		fOwner = owner;
773 		fRecord = 0; 		// record always starts out empty
774 	}
775 
update( int flags, byte[] rData, int ttl)776 	public void			update( int flags, byte[] rData, int ttl)
777 	throws DNSSDException
778 	{
779 		this.ThrowOnErr( this.Update( flags, rData, ttl));
780 	}
781 
remove()782 	public void			remove()
783 	throws DNSSDException
784 	{
785 		this.ThrowOnErr( this.Remove());
786 	}
787 
788 	protected long			fRecord;		// Really a DNSRecord; sizeof(int) == sizeof(void*) ?
789 	protected AppleService	fOwner;
790 
ThrowOnErr( int rc)791 	protected void			ThrowOnErr( int rc) throws DNSSDException
792 	{
793 		if ( rc != 0)
794 			throw new AppleDNSSDException( rc);
795 	}
796 
Update( int flags, byte[] rData, int ttl)797 	protected native int	Update( int flags, byte[] rData, int ttl);
798 
Remove()799 	protected native int	Remove();
800 }
801 
802 class	AppleRegistration extends AppleService implements DNSSDRegistration
803 {
AppleRegistration( int flags, int ifIndex, String serviceName, String regType, String domain, String host, int port, byte[] txtRecord, RegisterListener client)804 	public			AppleRegistration( int flags, int ifIndex, String serviceName, String regType, String domain,
805 								String host, int port, byte[] txtRecord, RegisterListener client)
806 	throws DNSSDException
807 	{
808 		super(client);
809 		this.ThrowOnErr( this.BeginRegister( ifIndex, flags, serviceName, regType, domain, host, port, txtRecord));
810 		if ( !AppleDNSSD.hasAutoCallbacks)
811 			new Thread(this).start();
812 	}
813 
addRecord( int flags, int rrType, byte[] rData, int ttl)814 	public DNSRecord	addRecord( int flags, int rrType, byte[] rData, int ttl)
815 	throws DNSSDException
816 	{
817 		AppleDNSRecord	newRecord = new AppleDNSRecord( this);
818 
819 		this.ThrowOnErr( this.AddRecord( flags, rrType, rData, ttl, newRecord));
820 		return newRecord;
821 	}
822 
getTXTRecord()823 	public DNSRecord	getTXTRecord()
824 	throws DNSSDException
825 	{
826 		return new AppleDNSRecord( this);	// A record with ref 0 is understood to be primary TXT record
827 	}
828 
829 	// Sets fNativeContext. Returns non-zero on error.
BeginRegister( int ifIndex, int flags, String serviceName, String regType, String domain, String host, int port, byte[] txtRecord)830 	protected native int	BeginRegister( int ifIndex, int flags, String serviceName, String regType,
831 											String domain, String host, int port, byte[] txtRecord);
832 
833 	// Sets fNativeContext. Returns non-zero on error.
AddRecord( int flags, int rrType, byte[] rData, int ttl, AppleDNSRecord destObj)834 	protected native int	AddRecord( int flags, int rrType, byte[] rData, int ttl, AppleDNSRecord destObj);
835 }
836 
837 class	AppleRecordRegistrar extends AppleService implements DNSSDRecordRegistrar
838 {
AppleRecordRegistrar( RegisterRecordListener listener)839 	public			AppleRecordRegistrar( RegisterRecordListener listener)
840 	throws DNSSDException
841 	{
842 		super(listener);
843 		this.ThrowOnErr( this.CreateConnection());
844 		if ( !AppleDNSSD.hasAutoCallbacks)
845 			new Thread(this).start();
846 	}
847 
registerRecord( int flags, int ifIndex, String fullname, int rrtype, int rrclass, byte[] rdata, int ttl)848 	public DNSRecord	registerRecord( int flags, int ifIndex, String fullname, int rrtype,
849 									int rrclass, byte[] rdata, int ttl)
850 	throws DNSSDException
851 	{
852 		AppleDNSRecord	newRecord = new AppleDNSRecord( this);
853 
854 		this.ThrowOnErr( this.RegisterRecord( flags, ifIndex, fullname, rrtype, rrclass, rdata, ttl, newRecord));
855 		return newRecord;
856 	}
857 
858 	// Sets fNativeContext. Returns non-zero on error.
CreateConnection()859 	protected native int	CreateConnection();
860 
861 	// Sets fNativeContext. Returns non-zero on error.
RegisterRecord( int flags, int ifIndex, String fullname, int rrtype, int rrclass, byte[] rdata, int ttl, AppleDNSRecord destObj)862 	protected native int	RegisterRecord( int flags, int ifIndex, String fullname, int rrtype,
863 										int rrclass, byte[] rdata, int ttl, AppleDNSRecord destObj);
864 }
865 
866 class	AppleQuery extends AppleService
867 {
AppleQuery( int flags, int ifIndex, String serviceName, int rrtype, int rrclass, QueryListener client)868 	public			AppleQuery( int flags, int ifIndex, String serviceName, int rrtype,
869 										int rrclass, QueryListener client)
870 	throws DNSSDException
871 	{
872 		super(client);
873 		this.ThrowOnErr( this.CreateQuery( flags, ifIndex, serviceName, rrtype, rrclass));
874 		if ( !AppleDNSSD.hasAutoCallbacks)
875 			new Thread(this).start();
876 	}
877 
878 	// Sets fNativeContext. Returns non-zero on error.
CreateQuery( int flags, int ifIndex, String serviceName, int rrtype, int rrclass)879 	protected native int	CreateQuery( int flags, int ifIndex, String serviceName, int rrtype, int rrclass);
880 }
881 
882 class	AppleDomainEnum extends AppleService
883 {
AppleDomainEnum( int flags, int ifIndex, DomainListener client)884 	public			AppleDomainEnum( int flags, int ifIndex, DomainListener client)
885 	throws DNSSDException
886 	{
887 		super(client);
888 		this.ThrowOnErr( this.BeginEnum( flags, ifIndex));
889 		if ( !AppleDNSSD.hasAutoCallbacks)
890 			new Thread(this).start();
891 	}
892 
893 	// Sets fNativeContext. Returns non-zero on error.
BeginEnum( int flags, int ifIndex)894 	protected native int	BeginEnum( int flags, int ifIndex);
895 }
896 
897 
898