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