DNSSD.java revision 4904:cd464a980538
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 $
20Revision 1.11  2006/08/14 23:25:08  cheshire
21Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0
22
23Revision 1.10  2006/06/20 23:05:55  rpantos
24<rdar://problem/3839132> Java needs to implement DNSServiceRegisterRecord equivalent
25
26Revision 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
29Revision 1.8  2005/07/11 01:55:21  cheshire
30<rdar://problem/4175511> Race condition in Java API
31
32Revision 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
35Revision 1.6  2005/07/05 00:02:25  cheshire
36Add missing comma
37
38Revision 1.5  2005/07/04 21:13:47  cheshire
39Add missing error message strings
40
41Revision 1.4  2004/12/11 03:00:59  rpantos
42<rdar://problem/3907498> Java DNSRecord API should be cleaned up
43
44Revision 1.3  2004/11/12 03:23:08  rpantos
45rdar://problem/3809541 implement getIfIndexForName, getNameForIfIndex.
46
47Revision 1.2  2004/05/20 17:43:18  cheshire
48Fix invalid UTF-8 characters in file
49
50Revision 1.1  2004/04/30 16:32:34  rpantos
51First 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
63package	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
88abstract 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	*/
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	*/
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	*/
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	*/
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	*/
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	*/
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	*/
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	*/
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	*/
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	*/
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	*/
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	*/
463	public static int		getIfIndexForName( String ifName)
464	{ return getInstance()._getIfIndexForName( ifName); }
465
466	protected						DNSSD() {}	// prevent direct instantiation
467
468	/** Return the single instance of DNSSD. */
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
477	abstract protected DNSSDService	_makeBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener listener)
478	throws DNSSDException;
479
480	abstract protected DNSSDService	_resolve( int flags, int ifIndex, String serviceName, String regType,
481										String domain, ResolveListener listener)
482	throws DNSSDException;
483
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
488	abstract protected DNSSDRecordRegistrar	_createRecordRegistrar( RegisterRecordListener listener)
489	throws DNSSDException;
490
491	abstract protected DNSSDService	_queryRecord( int flags, int ifIndex, String serviceName, int rrtype,
492										int rrclass, QueryListener listener)
493	throws DNSSDException;
494
495	abstract protected DNSSDService	_enumerateDomains( int flags, int ifIndex, DomainListener listener)
496	throws DNSSDException;
497
498	abstract protected String		_constructFullName( String serviceName, String regType, String domain)
499	throws DNSSDException;
500
501	abstract protected void			_reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype,
502										int rrclass, byte[] rdata);
503
504	abstract protected String		_getNameForIfIndex( int ifIndex);
505
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
528class	AppleDNSSDException extends DNSSDException
529{
530	public						AppleDNSSDException( int errorCode) { fErrorCode = errorCode; }
531
532	public int					getErrorCode() { return fErrorCode; }
533
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.
577class	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
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
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
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
612	protected DNSSDRecordRegistrar	_createRecordRegistrar( RegisterRecordListener listener)
613	throws DNSSDException
614	{
615		return new AppleRecordRegistrar( listener);
616	}
617
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
625	protected DNSSDService		_enumerateDomains( int flags, int ifIndex, DomainListener listener)
626	throws DNSSDException
627	{
628		return new AppleDomainEnum( flags, ifIndex, listener);
629	}
630
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
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
649	protected String			_getNameForIfIndex( int ifIndex)
650	{
651		return GetNameForIfIndex( ifIndex);
652	}
653
654	protected int				_getIfIndexForName( String ifName)
655	{
656		return GetIfIndexForName( ifName);
657	}
658
659
660	protected native int	ConstructName( String serviceName, String regType, String domain, String[] pOut);
661
662	protected native void	ReconfirmRecord( int flags, int ifIndex, String fullName, int rrtype,
663										int rrclass, byte[] rdata);
664
665	protected native String	GetNameForIfIndex( int ifIndex);
666
667	protected native int	GetIfIndexForName( String ifName);
668
669	protected static native int	InitLibrary( int callerVersion);
670}
671
672class	AppleService implements DNSSDService, Runnable
673{
674	public					AppleService(BaseListener listener)	{ fNativeContext = 0; fListener = listener; }
675
676	public void				stop() { this.HaltOperation(); }
677
678	/* Block until data arrives, or one second passes. Returns 1 if data present, 0 otherwise. */
679	protected native int	BlockForData();
680
681	/* Call ProcessResults when data appears on socket descriptor. */
682	protected native int	ProcessResults();
683
684	protected synchronized native void HaltOperation();
685
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
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
735class	AppleBrowser extends AppleService
736{
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.
747	protected native int	CreateBrowser( int flags, int ifIndex, String regType, String domain);
748}
749
750class	AppleResolver extends AppleService
751{
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.
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.
768class	AppleDNSRecord implements DNSRecord
769{
770	public			AppleDNSRecord( AppleService owner)
771	{
772		fOwner = owner;
773		fRecord = 0; 		// record always starts out empty
774	}
775
776	public void			update( int flags, byte[] rData, int ttl)
777	throws DNSSDException
778	{
779		this.ThrowOnErr( this.Update( flags, rData, ttl));
780	}
781
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
791	protected void			ThrowOnErr( int rc) throws DNSSDException
792	{
793		if ( rc != 0)
794			throw new AppleDNSSDException( rc);
795	}
796
797	protected native int	Update( int flags, byte[] rData, int ttl);
798
799	protected native int	Remove();
800}
801
802class	AppleRegistration extends AppleService implements DNSSDRegistration
803{
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
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
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.
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.
834	protected native int	AddRecord( int flags, int rrType, byte[] rData, int ttl, AppleDNSRecord destObj);
835}
836
837class	AppleRecordRegistrar extends AppleService implements DNSSDRecordRegistrar
838{
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
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.
859	protected native int	CreateConnection();
860
861	// Sets fNativeContext. Returns non-zero on error.
862	protected native int	RegisterRecord( int flags, int ifIndex, String fullname, int rrtype,
863										int rrclass, byte[] rdata, int ttl, AppleDNSRecord destObj);
864}
865
866class	AppleQuery extends AppleService
867{
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.
879	protected native int	CreateQuery( int flags, int ifIndex, String serviceName, int rrtype, int rrclass);
880}
881
882class	AppleDomainEnum extends AppleService
883{
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.
894	protected native int	BeginEnum( int flags, int ifIndex);
895}
896
897
898