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