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