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