summaryrefslogtreecommitdiffstats
path: root/mDNSResponder/mDNSShared/Java/DNSSD.java
blob: f749a88ead863f01f0c8db155e988c908f061ae8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
/* -*- Mode: Java; tab-width: 4 -*-
 *
 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.

	This file declares and implements DNSSD, the central Java factory class
	for doing DNS Service Discovery. It includes the mostly-abstract public
	interface, as well as the Apple* implementation subclasses.
 */


package	com.apple.dnssd;


/**
	DNSSD provides access to DNS Service Discovery features of ZeroConf networking.<P>

	It is a factory class that is used to invoke registration and discovery-related
	operations. Most operations are non-blocking; clients are called back through an interface
	with the result of an operation. Callbacks are made from a separate worker thread.<P>

	For example, in this program<P>
	<PRE><CODE>
    class   MyClient implements BrowseListener {
        void    lookForWebServers() {
            myBrowser = DNSSD.browse("_http._tcp", this);
        }
    
        public void serviceFound(DNSSDService browser, int flags, int ifIndex,
                            String serviceName, String regType, String domain) {}
        ...
    }</CODE></PRE>
	<CODE>MyClient.serviceFound()</CODE> would be called for every HTTP server discovered in the
	default browse domain(s).
*/

abstract public class	DNSSD
{
	/**	Flag indicates to a {@link BrowseListener} that another result is
		queued.  Applications should not update their UI to display browse
		results if the MORE_COMING flag is set; they will be called at least once
		more with the flag clear.
	*/
	public static final int		MORE_COMING = ( 1 << 0 );

	/** If flag is set in a {@link DomainListener} callback, indicates that the result is the default domain. */
	public static final int		DEFAULT = ( 1 << 2 );

    /**	If flag is set, a name conflict will trigger an exception when registering non-shared records.<P>
    	A name must be explicitly specified when registering a service if this bit is set
    	(i.e. the default name may not not be used).
     */
	public static final int		NO_AUTO_RENAME = ( 1 << 3 );

	/**	If flag is set, allow multiple records with this name on the network (e.g. PTR records)
		when registering individual records on a {@link DNSSDRegistration}.
	*/
	public static final int		SHARED = ( 1 << 4 );

	/**	If flag is set, records with this name must be unique on the network (e.g. SRV records). */
	public static final int		UNIQUE = ( 1 << 5 );

	/** Set flag when calling enumerateDomains() to restrict results to domains recommended for browsing. */
	public static final int		BROWSE_DOMAINS = ( 1 << 6 );
	/** Set flag when calling enumerateDomains() to restrict results to domains recommended for registration. */
	public static final int		REGISTRATION_DOMAINS = ( 1 << 7 );

	/** Maximum length, in bytes, of a domain name represented as an escaped C-String. */
    public static final int     MAX_DOMAIN_NAME = 1009;

	/** Pass for ifIndex to specify all available interfaces. */
    public static final int     ALL_INTERFACES = 0;

	/** Pass for ifIndex to specify the localhost interface. */
    public static final int     LOCALHOST_ONLY = -1;

	/** Browse for instances of a service.<P>

		Note: browsing consumes network bandwidth. Call {@link DNSSDService#stop} when you have finished browsing.<P>

		@param	flags
					Currently ignored, reserved for future use.
		<P>
		@param	ifIndex
					If non-zero, specifies the interface on which to browse for services
					(the index for a given interface is determined via the if_nametoindex()
					family of calls.)  Most applications will pass 0 to browse on all available
					interfaces.  Pass -1 to only browse for services provided on the local host.
		<P>
		@param	regType
					The registration type being browsed for followed by the protocol, separated by a
					dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp".
		<P>
		@param	domain
					If non-null, specifies the domain on which to browse for services.
					Most applications will not specify a domain, instead browsing on the
					default domain(s).
		<P>
		@param	listener
					This object will get called when instances of the service are discovered (or disappear).
		<P>
		@return		A {@link DNSSDService} that represents the active browse operation.

		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
		@see    RuntimePermission
	*/
	public static DNSSDService	browse( int flags, int ifIndex, String regType, String domain, BrowseListener listener)
	throws DNSSDException
	{ return getInstance()._makeBrowser( flags, ifIndex, regType, domain, listener); }

	/** Browse for instances of a service. Use default flags, ifIndex and domain.<P>

		@param	regType
					The registration type being browsed for followed by the protocol, separated by a
					dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp".
		<P>
		@param	listener
					This object will get called when instances of the service are discovered (or disappear).
		<P>
		@return		A {@link DNSSDService} that represents the active browse operation.

		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
		@see    RuntimePermission
	*/
	public static DNSSDService	browse( String regType, BrowseListener listener)
	throws DNSSDException
	{ return browse( 0, 0, regType, "", listener); }

	/** Resolve a service name discovered via browse() to a target host name, port number, and txt record.<P>
		
		Note: Applications should NOT use resolve() solely for txt record monitoring - use
		queryRecord() instead, as it is more efficient for this task.<P>
		
		Note: When the desired results have been returned, the client MUST terminate the resolve by
		calling {@link DNSSDService#stop}.<P>

		Note: resolve() behaves correctly for typical services that have a single SRV record and
 		a single TXT record (the TXT record may be empty.)  To resolve non-standard services with
 		multiple SRV or TXT records, use queryRecord().<P>

		@param	flags
					Currently ignored, reserved for future use.
		<P>
		@param	ifIndex
					The interface on which to resolve the service.  The client should
					pass the interface on which the serviceName was discovered (i.e.
					the ifIndex passed to the serviceFound() callback)
					or 0 to resolve the named service on all available interfaces.
		<P>
		@param	serviceName
					The servicename to be resolved.
		<P>
		@param	regType
					The registration type being resolved followed by the protocol, separated by a
					dot (e.g. "_ftp._tcp").  The transport protocol must be "_tcp" or "_udp".
		<P>
		@param	domain
					The domain on which the service is registered, i.e. the domain passed
					to the serviceFound() callback.
		<P>
		@param	listener
					This object will get called when the service is resolved.
		<P>
		@return		A {@link DNSSDService} that represents the active resolve operation.

		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
		@see    RuntimePermission
	*/
	public static DNSSDService	resolve( int flags, int ifIndex, String serviceName, String regType,
										String domain, ResolveListener listener)
	throws DNSSDException
	{ return getInstance()._resolve( flags, ifIndex, serviceName, regType, domain, listener); }

	/** Register a service, to be discovered via browse() and resolve() calls.<P>
		@param	flags
					Possible values are: NO_AUTO_RENAME.
		<P>
		@param	ifIndex
					If non-zero, specifies the interface on which to register the service
					(the index for a given interface is determined via the if_nametoindex()
					family of calls.)  Most applications will pass 0 to register on all
					available interfaces.  Pass -1 to register a service only on the local
					machine (service will not be visible to remote hosts).
		<P>
		@param	serviceName
					If non-null, specifies the service name to be registered.
					Applications need not specify a name, in which case the
					computer name is used (this name is communicated to the client via
					the serviceRegistered() callback).
		<P>
		@param	regType
					The registration type being registered followed by the protocol, separated by a
					dot (e.g. "_ftp._tcp").  The transport protocol must be "_tcp" or "_udp".
		<P>
		@param	domain
					If non-null, specifies the domain on which to advertise the service.
					Most applications will not specify a domain, instead automatically
					registering in the default domain(s).
		<P>
		@param	host
					If non-null, specifies the SRV target host name.  Most applications
					will not specify a host, instead automatically using the machine's
					default host name(s).  Note that specifying a non-null host does NOT
					create an address record for that host - the application is responsible
					for ensuring that the appropriate address record exists, or creating it
					via {@link DNSSDRegistration#addRecord}.
		<P>
		@param	port
					The port on which the service accepts connections.  Pass 0 for a
					"placeholder" service (i.e. a service that will not be discovered by
					browsing, but will cause a name conflict if another client tries to
					register that same name.)  Most clients will not use placeholder services.
		<P>
		@param	txtRecord
					The txt record rdata.  May be null.  Note that a non-null txtRecord
					MUST be a properly formatted DNS TXT record, i.e. &lt;length byte&gt; &lt;data&gt;
					&lt;length byte&gt; &lt;data&gt; ...
		<P>
		@param	listener
					This object will get called when the service is registered.
		<P>
		@return		A {@link DNSSDRegistration} that controls the active registration.

		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
		@see    RuntimePermission
	*/
	public static DNSSDRegistration	register( int flags, int ifIndex, String serviceName, String regType,
									String domain, String host, int port, TXTRecord txtRecord, RegisterListener listener)
	throws DNSSDException
	{ return getInstance()._register( flags, ifIndex, serviceName, regType, domain, host, port, txtRecord, listener); }

	/** Register a service, to be discovered via browse() and resolve() calls. Use default flags, ifIndex, domain, host and txtRecord.<P>
		@param	serviceName
					If non-null, specifies the service name to be registered.
					Applications need not specify a name, in which case the
					computer name is used (this name is communicated to the client via
					the serviceRegistered() callback).
		<P>
		@param	regType
					The registration type being registered followed by the protocol, separated by a
					dot (e.g. "_ftp._tcp").  The transport protocol must be "_tcp" or "_udp".
		<P>
		@param	port
					The port on which the service accepts connections.  Pass 0 for a
					"placeholder" service (i.e. a service that will not be discovered by
					browsing, but will cause a name conflict if another client tries to
					register that same name.)  Most clients will not use placeholder services.
		<P>
		@param	listener
					This object will get called when the service is registered.
		<P>
		@return		A {@link DNSSDRegistration} that controls the active registration.

		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
		@see    RuntimePermission
	*/
	public static DNSSDRegistration	register( String serviceName, String regType, int port, RegisterListener listener)
	throws DNSSDException
	{ return register( 0, 0, serviceName, regType, null, null, port, null, listener); }

	/** Create a {@link DNSSDRecordRegistrar} allowing efficient registration of
		multiple individual records.<P>
		<P>
		@return		A {@link DNSSDRecordRegistrar} that can be used to register records.

		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
		@see    RuntimePermission
	*/
	public static DNSSDRecordRegistrar	createRecordRegistrar( RegisterRecordListener listener)
	throws DNSSDException
	{ return getInstance()._createRecordRegistrar( listener); }

	/** Query for an arbitrary DNS record.<P>
		@param	flags
					Possible values are: MORE_COMING.
		<P>
		@param	ifIndex
					If non-zero, specifies the interface on which to issue the query
					(the index for a given interface is determined via the if_nametoindex()
					family of calls.)  Passing 0 causes the name to be queried for on all
					interfaces.  Passing -1 causes the name to be queried for only on the
					local host.
		<P>
		@param	serviceName
					The full domain name of the resource record to be queried for.
		<P>
		@param	rrtype
					The numerical type of the resource record to be queried for (e.g. PTR, SRV, etc)
					as defined in nameser.h.
		<P>
		@param	rrclass
					The class of the resource record, as defined in nameser.h
					(usually 1 for the Internet class).
		<P>
		@param	listener
					This object will get called when the query completes.
		<P>
		@return		A {@link DNSSDService} that controls the active query.

		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
		@see    RuntimePermission
	*/
	public static DNSSDService	queryRecord( int flags, int ifIndex, String serviceName, int rrtype,
										int rrclass, QueryListener listener)
	throws DNSSDException
	{ return getInstance()._queryRecord( flags, ifIndex, serviceName, rrtype, rrclass, listener); }

	/** Asynchronously enumerate domains available for browsing and registration.<P>
	
		Currently, the only domain returned is "local.", but other domains will be returned in future.<P>
		
		The enumeration MUST be cancelled by calling {@link DNSSDService#stop} when no more domains
		are to be found.<P>
		@param	flags
					Possible values are: BROWSE_DOMAINS, REGISTRATION_DOMAINS.
		<P>
		@param	ifIndex
					If non-zero, specifies the interface on which to look for domains.
					(the index for a given interface is determined via the if_nametoindex()
					family of calls.)  Most applications will pass 0 to enumerate domains on
					all interfaces.
		<P>
		@param	listener
					This object will get called when domains are found.
		<P>
		@return		A {@link DNSSDService} that controls the active enumeration.

		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
		@see    RuntimePermission
	*/
	public static DNSSDService	enumerateDomains( int flags, int ifIndex, DomainListener listener)
	throws DNSSDException
	{ return getInstance()._enumerateDomains( flags, ifIndex, listener); }

	/**	Concatenate a three-part domain name (as provided to the listeners) into a
		properly-escaped full domain name. Note that strings passed to listeners are
		ALREADY ESCAPED where necessary.<P>
		@param	serviceName
					The service name - any dots or slashes must NOT be escaped.
					May be null (to construct a PTR record name, e.g. "_ftp._tcp.apple.com").
		<P>
		@param	regType
					The registration type followed by the protocol, separated by a dot (e.g. "_ftp._tcp").
		<P>
		@param	domain
					The domain name, e.g. "apple.com".  Any literal dots or backslashes must be escaped.
		<P>
		@return		The full domain name.

		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
		@see    RuntimePermission
	*/
	public static String		constructFullName( String serviceName, String regType, String domain)
	throws DNSSDException
	{ return getInstance()._constructFullName( serviceName, regType, domain); }

	/** Instruct the daemon to verify the validity of a resource record that appears to
		be out of date. (e.g. because tcp connection to a service's target failed.) <P>
		
		Causes the record to be flushed from the daemon's cache (as well as all other
		daemons' caches on the network) if the record is determined to be invalid.<P>
		@param	flags
					Currently unused, reserved for future use.
		<P>
		@param	ifIndex
					If non-zero, specifies the interface on which to reconfirm the record
					(the index for a given interface is determined via the if_nametoindex()
					family of calls.)  Passing 0 causes the name to be reconfirmed on all
					interfaces.  Passing -1 causes the name to be reconfirmed only on the
					local host.
		<P>
		@param	fullName
					The resource record's full domain name.
		<P>
		@param	rrtype
					The resource record's type (e.g. PTR, SRV, etc) as defined in nameser.h.
		<P>
		@param	rrclass
					The class of the resource record, as defined in nameser.h (usually 1).
		<P>
		@param	rdata
					The raw rdata of the resource record.

		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
		@see    RuntimePermission
	*/
	public static void		reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype,
										int rrclass, byte[] rdata)
	{ getInstance()._reconfirmRecord( flags, ifIndex, fullName, rrtype, rrclass, rdata); }

	/** Return the canonical name of a particular interface index.<P>
		@param	ifIndex
					A valid interface index. Must not be ALL_INTERFACES.
		<P>
		@return		The name of the interface, which should match java.net.NetworkInterface.getName().

		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
		@see    RuntimePermission
	*/
	public static String	getNameForIfIndex( int ifIndex)
	{ return getInstance()._getNameForIfIndex( ifIndex); }

	/** Return the index of a named interface.<P>
		@param	ifName
					A valid interface name. An example is java.net.NetworkInterface.getName().
		<P>
		@return		The interface index.

		@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
		@see    RuntimePermission
	*/
	public static int		getIfIndexForName( String ifName)
	{ return getInstance()._getIfIndexForName( ifName); }

	protected						DNSSD() {}	// prevent direct instantiation

	/** Return the single instance of DNSSD. */
	static protected final DNSSD	getInstance()
	{
		SecurityManager sm = System.getSecurityManager();
        if (sm != null)
            sm.checkPermission( new RuntimePermission( "getDNSSDInstance"));
		 return fInstance;
	}

	abstract protected DNSSDService	_makeBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener listener)
	throws DNSSDException;

	abstract protected DNSSDService	_resolve( int flags, int ifIndex, String serviceName, String regType,
										String domain, ResolveListener listener)
	throws DNSSDException;

	abstract protected DNSSDRegistration	_register( int flags, int ifIndex, String serviceName, String regType,
									String domain, String host, int port, TXTRecord txtRecord, RegisterListener listener)
	throws DNSSDException;

	abstract protected DNSSDRecordRegistrar	_createRecordRegistrar( RegisterRecordListener listener)
	throws DNSSDException;

	abstract protected DNSSDService	_queryRecord( int flags, int ifIndex, String serviceName, int rrtype,
										int rrclass, QueryListener listener)
	throws DNSSDException;

	abstract protected DNSSDService	_enumerateDomains( int flags, int ifIndex, DomainListener listener)
	throws DNSSDException;

	abstract protected String		_constructFullName( String serviceName, String regType, String domain)
	throws DNSSDException;

	abstract protected void			_reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype,
										int rrclass, byte[] rdata);

	abstract protected String		_getNameForIfIndex( int ifIndex);

	abstract protected int			_getIfIndexForName( String ifName);

	protected static DNSSD			fInstance;

	static
	{
		try
		{
			String name = System.getProperty( "com.apple.dnssd.DNSSD" );
			if (name == null)
				name = "com.apple.dnssd.AppleDNSSD";	// Fall back to Apple-provided class.
			fInstance = (DNSSD) Class.forName(name).newInstance();
		}
		catch( Exception e )
		{
			throw new InternalError( "cannot instantiate DNSSD" + e );
		}
	}
}


// Concrete implementation of DNSSDException
class	AppleDNSSDException extends DNSSDException
{
	public						AppleDNSSDException( int errorCode) { fErrorCode = errorCode; }

	public int					getErrorCode() { return fErrorCode; }

	public String				getMessage()
	{
		final String	kMessages[] = {		// should probably be put into a resource or something
			"UNKNOWN",
			"NO_SUCH_NAME",
			"NO_MEMORY",
			"BAD_PARAM",
			"BAD_REFERENCE",
			"BAD_STATE",
			"BAD_FLAGS",
			"UNSUPPORTED",
			"NOT_INITIALIZED",
			"NO_CACHE",
			"ALREADY_REGISTERED",
			"NAME_CONFLICT",
			"INVALID",
			"FIREWALL",
			"INCOMPATIBLE",
			"BAD_INTERFACE_INDEX",
			"REFUSED",
			"NOSUCHRECORD",
			"NOAUTH",
			"NOSUCHKEY",
			"NATTRAVERSAL",
			"DOUBLENAT",
			"BADTIME",
			"BADSIG",
			"BADKEY",
			"TRANSIENT",
			"SERVICENOTRUNNING",
			"NATPORTMAPPINGUNSUPPORTED",
			"NATPORTMAPPINGDISABLED"
		};
	
		if (fErrorCode <= UNKNOWN && fErrorCode > ( UNKNOWN - kMessages.length))
		{
			return "DNS-SD Error " + String.valueOf( fErrorCode) + ": " + kMessages[ UNKNOWN - fErrorCode];
		}
		else
			return super.getMessage() + "(" + String.valueOf( fErrorCode) + ")";
	}

	protected int			fErrorCode;
}

// The concrete, default implementation.
class	AppleDNSSD extends DNSSD
{
	static
	{
		System.loadLibrary( "jdns_sd");
	
		int		libInitResult = InitLibrary( 2);	// Current version number (must be sync'd with jnilib version)

		if (libInitResult != DNSSDException.NO_ERROR)
			throw new InternalError( "cannot instantiate DNSSD: " + new AppleDNSSDException( libInitResult).getMessage());
	}

	static public boolean	hasAutoCallbacks;	// Set by InitLibrary() to value of AUTO_CALLBACKS

	protected DNSSDService	_makeBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener client)
	throws DNSSDException
	{
		return new AppleBrowser( flags, ifIndex, regType, domain, client);
	}

	protected DNSSDService	_resolve( int flags, int ifIndex, String serviceName, String regType,
										String domain, ResolveListener client)
	throws DNSSDException
	{
		return new AppleResolver( flags, ifIndex, serviceName, regType, domain, client);
	}

	protected DNSSDRegistration	_register( int flags, int ifIndex, String serviceName, String regType,
									String domain, String host, int port, TXTRecord txtRecord, RegisterListener client)
	throws DNSSDException
	{
		return new AppleRegistration( flags, ifIndex, serviceName, regType, domain, host, port,
										( txtRecord != null) ? txtRecord.getRawBytes() : null, client);
	}

	protected DNSSDRecordRegistrar	_createRecordRegistrar( RegisterRecordListener listener)
	throws DNSSDException
	{
		return new AppleRecordRegistrar( listener);
	}

	protected DNSSDService		_queryRecord( int flags, int ifIndex, String serviceName, int rrtype,
										int rrclass, QueryListener client)
	throws DNSSDException
	{
		return new AppleQuery( flags, ifIndex, serviceName, rrtype, rrclass, client);
	}

	protected DNSSDService		_enumerateDomains( int flags, int ifIndex, DomainListener listener)
	throws DNSSDException
	{
		return new AppleDomainEnum( flags, ifIndex, listener);
	}

	protected String			_constructFullName( String serviceName, String regType, String domain)
	throws DNSSDException
	{
		String[]	responseHolder = new String[1];	// lame maneuver to get around Java's lack of reference parameters

		int rc = ConstructName( serviceName, regType, domain, responseHolder);
		if (rc != 0)
			throw new AppleDNSSDException( rc);

		return responseHolder[0];
	}

	protected void				_reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype,
										int rrclass, byte[] rdata)
	{
		ReconfirmRecord( flags, ifIndex, fullName, rrtype, rrclass, rdata);
	}

	protected String			_getNameForIfIndex( int ifIndex)
	{
		return GetNameForIfIndex( ifIndex);
	}

	protected int				_getIfIndexForName( String ifName)
	{
		return GetIfIndexForName( ifName);
	}


	protected native int	ConstructName( String serviceName, String regType, String domain, String[] pOut);

	protected native void	ReconfirmRecord( int flags, int ifIndex, String fullName, int rrtype,
										int rrclass, byte[] rdata);

	protected native String	GetNameForIfIndex( int ifIndex);

	protected native int	GetIfIndexForName( String ifName);

	protected static native int	InitLibrary( int callerVersion);
}

class	AppleService implements DNSSDService, Runnable
{
	public					AppleService(BaseListener listener)	{ fNativeContext = 0; fListener = listener; }

	public void				stop() { this.HaltOperation(); }

	/* Block until data arrives, or one second passes. Returns 1 if data present, 0 otherwise. */
	protected native int	BlockForData();

	/* Call ProcessResults when data appears on socket descriptor. */
	protected native int	ProcessResults();

	protected synchronized native void HaltOperation();

	protected void			ThrowOnErr( int rc) throws DNSSDException
	{
		if (rc != 0)
			throw new AppleDNSSDException( rc);
	}

	protected long	/* warning */	fNativeContext;		// Private storage for native side

	public void		run()
	{
		while ( true )
		{
			// Note: We want to allow our DNS-SD operation to be stopped from other threads, so we have to
			// block waiting for data *outside* the synchronized section. Because we're doing this unsynchronized
			// we have to write some careful code. Suppose our DNS-SD operation is stopped from some other thread,
			// and then immediately afterwards that thread (or some third, unrelated thread) starts a new DNS-SD
			// operation. The Unix kernel always allocates the lowest available file descriptor to a new socket,
			// so the same file descriptor is highly likely to be reused for the new operation, and if our old
			// stale ServiceThread accidentally consumes bytes off that new socket we'll get really messed up.
			// To guard against that, before calling ProcessResults we check to ensure that our
			// fNativeContext has not been deleted, which is a telltale sign that our operation was stopped.
			// After calling ProcessResults we check again, because it's extremely common for callback
			// functions to stop their own operation and start others. For example, a resolveListener callback
			// may well stop the resolve and then start a QueryRecord call to monitor the TXT record.
			//
			// The remaining risk is that between our checking fNativeContext and calling ProcessResults(),
			// some other thread could stop the operation and start a new one using same file descriptor, and
			// we wouldn't know. To prevent this, the AppleService object's HaltOperation() routine is declared
			// synchronized and we perform our checks synchronized on the AppleService object, which ensures
			// that HaltOperation() can't execute while we're doing it. Because Java locks are re-entrant this
			// locking DOESN'T prevent the callback routine from stopping its own operation, but DOES prevent
			// any other thread from stopping it until after the callback has completed and returned to us here.

			int result = BlockForData();
			synchronized (this)
			{
				if (fNativeContext == 0) break;	// Some other thread stopped our DNSSD operation; time to terminate this thread
				if (result == 0) continue;		// If BlockForData() said there was no data, go back and block again
				result = ProcessResults();
				if (fNativeContext == 0) break;	// Event listener stopped its own DNSSD operation; terminate this thread
				if (result != 0) { fListener.operationFailed(this, result); break; }	// If error, notify listener
			}
		}
	}

	protected BaseListener fListener;
}


class	AppleBrowser extends AppleService
{
	public			AppleBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener client)
	throws DNSSDException
	{
		super(client);
		this.ThrowOnErr( this.CreateBrowser( flags, ifIndex, regType, domain));
		if (!AppleDNSSD.hasAutoCallbacks)
			new Thread(this).start();
	}

	// Sets fNativeContext. Returns non-zero on error.
	protected native int	CreateBrowser( int flags, int ifIndex, String regType, String domain);
}

class	AppleResolver extends AppleService
{
	public			AppleResolver( int flags, int ifIndex, String serviceName, String regType,
									String domain, ResolveListener client)
	throws DNSSDException
	{
		super(client);
		this.ThrowOnErr( this.CreateResolver( flags, ifIndex, serviceName, regType, domain));
		if (!AppleDNSSD.hasAutoCallbacks)
			new Thread(this).start();
	}

	// Sets fNativeContext. Returns non-zero on error.
	protected native int	CreateResolver( int flags, int ifIndex, String serviceName, String regType,
											String domain);
}

// An AppleDNSRecord is a simple wrapper around a dns_sd DNSRecord.
class	AppleDNSRecord implements DNSRecord
{
	public			AppleDNSRecord( AppleService owner)
	{
		fOwner = owner;
		fRecord = 0; 		// record always starts out empty
	}

	public void			update( int flags, byte[] rData, int ttl)
	throws DNSSDException
	{
		this.ThrowOnErr( this.Update( flags, rData, ttl));
	}

	public void			remove()
	throws DNSSDException
	{
		this.ThrowOnErr( this.Remove());
	}

	protected long			fRecord;		// Really a DNSRecord; sizeof(long) == sizeof(void*) ?
	protected AppleService	fOwner;

	protected void			ThrowOnErr( int rc) throws DNSSDException
	{
		if (rc != 0)
			throw new AppleDNSSDException( rc);
	}

	protected native int	Update( int flags, byte[] rData, int ttl);

	protected native int	Remove();
}

class	AppleRegistration extends AppleService implements DNSSDRegistration
{
	public			AppleRegistration( int flags, int ifIndex, String serviceName, String regType, String domain,
								String host, int port, byte[] txtRecord, RegisterListener client)
	throws DNSSDException
	{
		super(client);
		this.ThrowOnErr( this.BeginRegister( ifIndex, flags, serviceName, regType, domain, host, port, txtRecord));
		if (!AppleDNSSD.hasAutoCallbacks)
			new Thread(this).start();
	}

	public DNSRecord	addRecord( int flags, int rrType, byte[] rData, int ttl)
	throws DNSSDException
	{
		AppleDNSRecord	newRecord = new AppleDNSRecord( this);

		this.ThrowOnErr( this.AddRecord( flags, rrType, rData, ttl, newRecord));
		return newRecord;
	}

	public DNSRecord	getTXTRecord()
	throws DNSSDException
	{
		return new AppleDNSRecord( this);	// A record with ref 0 is understood to be primary TXT record
	}

	// Sets fNativeContext. Returns non-zero on error.
	protected native int	BeginRegister( int ifIndex, int flags, String serviceName, String regType,
											String domain, String host, int port, byte[] txtRecord);

	// Sets fNativeContext. Returns non-zero on error.
	protected native int	AddRecord( int flags, int rrType, byte[] rData, int ttl, AppleDNSRecord destObj);
}

class	AppleRecordRegistrar extends AppleService implements DNSSDRecordRegistrar
{
	public			AppleRecordRegistrar( RegisterRecordListener listener)
	throws DNSSDException
	{
		super(listener);
		this.ThrowOnErr( this.CreateConnection());
		if (!AppleDNSSD.hasAutoCallbacks)
			new Thread(this).start();
	}

	public DNSRecord	registerRecord( int flags, int ifIndex, String fullname, int rrtype,
									int rrclass, byte[] rdata, int ttl)
	throws DNSSDException
	{
		AppleDNSRecord	newRecord = new AppleDNSRecord( this);

		this.ThrowOnErr( this.RegisterRecord( flags, ifIndex, fullname, rrtype, rrclass, rdata, ttl, newRecord));
		return newRecord;
	}

	// Sets fNativeContext. Returns non-zero on error.
	protected native int	CreateConnection();

	// Sets fNativeContext. Returns non-zero on error.
	protected native int	RegisterRecord( int flags, int ifIndex, String fullname, int rrtype,
										int rrclass, byte[] rdata, int ttl, AppleDNSRecord destObj);
}

class	AppleQuery extends AppleService
{
	public			AppleQuery( int flags, int ifIndex, String serviceName, int rrtype,
										int rrclass, QueryListener client)
	throws DNSSDException
	{
		super(client);
		this.ThrowOnErr( this.CreateQuery( flags, ifIndex, serviceName, rrtype, rrclass));
		if (!AppleDNSSD.hasAutoCallbacks)
			new Thread(this).start();
	}

	// Sets fNativeContext. Returns non-zero on error.
	protected native int	CreateQuery( int flags, int ifIndex, String serviceName, int rrtype, int rrclass);
}

class	AppleDomainEnum extends AppleService
{
	public			AppleDomainEnum( int flags, int ifIndex, DomainListener client)
	throws DNSSDException
	{
		super(client);
		this.ThrowOnErr( this.BeginEnum( flags, ifIndex));
		if (!AppleDNSSD.hasAutoCallbacks)
			new Thread(this).start();
	}

	// Sets fNativeContext. Returns non-zero on error.
	protected native int	BeginEnum( int flags, int ifIndex);
}