1 // Copyright 2003-2005 Arthur van Hoff, Rick Blair 2 // Licensed under Apache License version 2.0 3 // Original license LGPL 4 5 package javax.jmdns.impl; 6 7 import java.io.DataOutputStream; 8 import java.io.IOException; 9 import java.io.UnsupportedEncodingException; 10 import java.net.Inet4Address; 11 import java.net.Inet6Address; 12 import java.net.InetAddress; 13 import java.net.UnknownHostException; 14 import java.util.HashMap; 15 import java.util.Map; 16 import java.util.logging.Level; 17 import java.util.logging.Logger; 18 19 import javax.jmdns.ServiceEvent; 20 import javax.jmdns.ServiceInfo; 21 import javax.jmdns.ServiceInfo.Fields; 22 import javax.jmdns.impl.DNSOutgoing.MessageOutputStream; 23 import javax.jmdns.impl.constants.DNSConstants; 24 import javax.jmdns.impl.constants.DNSRecordClass; 25 import javax.jmdns.impl.constants.DNSRecordType; 26 27 /** 28 * DNS record 29 * 30 * @author Arthur van Hoff, Rick Blair, Werner Randelshofer, Pierre Frisch 31 */ 32 public abstract class DNSRecord extends DNSEntry { 33 private static Logger logger = Logger.getLogger(DNSRecord.class.getName()); 34 private int _ttl; 35 private long _created; 36 37 /** 38 * This source is mainly for debugging purposes, should be the address that sent this record. 39 */ 40 private InetAddress _source; 41 42 /** 43 * Create a DNSRecord with a name, type, class, and ttl. 44 */ 45 DNSRecord(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl) { 46 super(name, type, recordClass, unique); 47 this._ttl = ttl; 48 this._created = System.currentTimeMillis(); 49 } 50 51 /* 52 * (non-Javadoc) 53 * @see javax.jmdns.impl.DNSEntry#equals(java.lang.Object) 54 */ 55 @Override 56 public boolean equals(Object other) { 57 return (other instanceof DNSRecord) && super.equals(other) && sameValue((DNSRecord) other); 58 } 59 60 /** 61 * True if this record has the same value as some other record. 62 */ 63 abstract boolean sameValue(DNSRecord other); 64 65 /** 66 * True if this record has the same type as some other record. 67 */ 68 boolean sameType(DNSRecord other) { 69 return this.getRecordType() == other.getRecordType(); 70 } 71 72 /** 73 * Handles a query represented by this record. 74 * 75 * @return Returns true if a conflict with one of the services registered with JmDNS or with the hostname occured. 76 */ 77 abstract boolean handleQuery(JmDNSImpl dns, long expirationTime); 78 79 /** 80 * Handles a response represented by this record. 81 * 82 * @return Returns true if a conflict with one of the services registered with JmDNS or with the hostname occured. 83 */ 84 abstract boolean handleResponse(JmDNSImpl dns); 85 86 /** 87 * Adds this as an answer to the provided outgoing datagram. 88 */ 89 abstract DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException; 90 91 /** 92 * True if this record is suppressed by the answers in a message. 93 */ 94 boolean suppressedBy(DNSIncoming msg) { 95 try { 96 for (DNSRecord answer : msg.getAllAnswers()) { 97 if (suppressedBy(answer)) { 98 return true; 99 } 100 } 101 return false; 102 } catch (ArrayIndexOutOfBoundsException e) { 103 logger.log(Level.WARNING, "suppressedBy() message " + msg + " exception ", e); 104 // msg.print(true); 105 return false; 106 } 107 } 108 109 /** 110 * True if this record would be suppressed by an answer. This is the case if this record would not have a significantly longer TTL. 111 */ 112 boolean suppressedBy(DNSRecord other) { 113 if (this.equals(other) && (other._ttl > _ttl / 2)) { 114 return true; 115 } 116 return false; 117 } 118 119 /** 120 * Get the expiration time of this record. 121 */ 122 long getExpirationTime(int percent) { 123 // ttl is in seconds the constant 10 is 1000 ms / 100 % 124 return _created + (percent * _ttl * 10L); 125 } 126 127 /** 128 * Get the remaining TTL for this record. 129 */ 130 int getRemainingTTL(long now) { 131 return (int) Math.max(0, (getExpirationTime(100) - now) / 1000); 132 } 133 134 /* 135 * (non-Javadoc) 136 * @see javax.jmdns.impl.DNSEntry#isExpired(long) 137 */ 138 @Override 139 public boolean isExpired(long now) { 140 return getExpirationTime(100) <= now; 141 } 142 143 /* 144 * (non-Javadoc) 145 * @see javax.jmdns.impl.DNSEntry#isStale(long) 146 */ 147 @Override 148 public boolean isStale(long now) { 149 return getExpirationTime(50) <= now; 150 } 151 152 /** 153 * Reset the TTL of a record. This avoids having to update the entire record in the cache. 154 */ 155 void resetTTL(DNSRecord other) { 156 _created = other._created; 157 _ttl = other._ttl; 158 } 159 160 /** 161 * When a record flushed we don't remove it immediately, but mark it for rapid decay. 162 */ 163 void setWillExpireSoon(long now) { 164 _created = now; 165 _ttl = DNSConstants.RECORD_EXPIRY_DELAY; 166 } 167 168 /** 169 * Write this record into an outgoing message. 170 */ 171 abstract void write(MessageOutputStream out); 172 173 public static class IPv4Address extends Address { 174 175 IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) { 176 super(name, DNSRecordType.TYPE_A, recordClass, unique, ttl, addr); 177 } 178 179 IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress) { 180 super(name, DNSRecordType.TYPE_A, recordClass, unique, ttl, rawAddress); 181 } 182 183 @Override 184 void write(MessageOutputStream out) { 185 if (_addr != null) { 186 byte[] buffer = _addr.getAddress(); 187 // If we have a type A records we should answer with a IPv4 address 188 if (_addr instanceof Inet4Address) { 189 // All is good 190 } else { 191 // Get the last four bytes 192 byte[] tempbuffer = buffer; 193 buffer = new byte[4]; 194 System.arraycopy(tempbuffer, 12, buffer, 0, 4); 195 } 196 int length = buffer.length; 197 out.writeBytes(buffer, 0, length); 198 } 199 } 200 201 /* 202 * (non-Javadoc) 203 * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) 204 */ 205 @Override 206 public ServiceInfo getServiceInfo(boolean persistent) { 207 208 ServiceInfoImpl info = (ServiceInfoImpl) super.getServiceInfo(persistent); 209 info.addAddress((Inet4Address) _addr); 210 return info; 211 } 212 213 } 214 215 public static class IPv6Address extends Address { 216 217 IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) { 218 super(name, DNSRecordType.TYPE_AAAA, recordClass, unique, ttl, addr); 219 } 220 221 IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress) { 222 super(name, DNSRecordType.TYPE_AAAA, recordClass, unique, ttl, rawAddress); 223 } 224 225 @Override 226 void write(MessageOutputStream out) { 227 if (_addr != null) { 228 byte[] buffer = _addr.getAddress(); 229 // If we have a type AAAA records we should answer with a IPv6 address 230 if (_addr instanceof Inet4Address) { 231 byte[] tempbuffer = buffer; 232 buffer = new byte[16]; 233 for (int i = 0; i < 16; i++) { 234 if (i < 11) { 235 buffer[i] = tempbuffer[i - 12]; 236 } else { 237 buffer[i] = 0; 238 } 239 } 240 } 241 int length = buffer.length; 242 out.writeBytes(buffer, 0, length); 243 } 244 } 245 246 /* 247 * (non-Javadoc) 248 * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) 249 */ 250 @Override 251 public ServiceInfo getServiceInfo(boolean persistent) { 252 253 ServiceInfoImpl info = (ServiceInfoImpl) super.getServiceInfo(persistent); 254 info.addAddress((Inet6Address) _addr); 255 return info; 256 } 257 258 } 259 260 /** 261 * Address record. 262 */ 263 public static abstract class Address extends DNSRecord { 264 private static Logger logger1 = Logger.getLogger(Address.class.getName()); 265 266 InetAddress _addr; 267 268 protected Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) { 269 super(name, type, recordClass, unique, ttl); 270 this._addr = addr; 271 } 272 273 protected Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress) { 274 super(name, type, recordClass, unique, ttl); 275 try { 276 this._addr = InetAddress.getByAddress(rawAddress); 277 } catch (UnknownHostException exception) { 278 logger1.log(Level.WARNING, "Address() exception ", exception); 279 } 280 } 281 282 boolean same(DNSRecord other) { 283 if (! (other instanceof Address) ) { 284 return false; 285 } 286 return ((sameName(other)) && ((sameValue(other)))); 287 } 288 289 boolean sameName(DNSRecord other) { 290 return this.getName().equalsIgnoreCase(other.getName()); 291 } 292 293 @Override 294 boolean sameValue(DNSRecord other) { 295 if (! (other instanceof Address) ) { 296 return false; 297 } 298 Address address = (Address) other; 299 if ((this.getAddress() == null) && (address.getAddress() != null)) { 300 return false; 301 } 302 return this.getAddress().equals(address.getAddress()); 303 } 304 305 @Override 306 public boolean isSingleValued() { 307 return false; 308 } 309 310 InetAddress getAddress() { 311 return _addr; 312 } 313 314 /** 315 * Creates a byte array representation of this record. This is needed for tie-break tests according to draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2. 316 */ 317 @Override 318 protected void toByteArray(DataOutputStream dout) throws IOException { 319 super.toByteArray(dout); 320 byte[] buffer = this.getAddress().getAddress(); 321 for (int i = 0; i < buffer.length; i++) { 322 dout.writeByte(buffer[i]); 323 } 324 } 325 326 /** 327 * Does the necessary actions, when this as a query. 328 */ 329 @Override 330 boolean handleQuery(JmDNSImpl dns, long expirationTime) { 331 if (dns.getLocalHost().conflictWithRecord(this)) { 332 DNSRecord.Address localAddress = dns.getLocalHost().getDNSAddressRecord(this.getRecordType(), this.isUnique(), DNSConstants.DNS_TTL); 333 int comparison = this.compareTo(localAddress); 334 335 if (comparison == 0) { 336 // the 2 records are identical this probably means we are seeing our own record. 337 // With multiple interfaces on a single computer it is possible to see our 338 // own records come in on different interfaces than the ones they were sent on. 339 // see section "10. Conflict Resolution" of mdns draft spec. 340 logger1.finer("handleQuery() Ignoring an identical address query"); 341 return false; 342 } 343 344 logger1.finer("handleQuery() Conflicting query detected."); 345 // Tie breaker test 346 if (dns.isProbing() && comparison > 0) { 347 // We lost the tie-break. We have to choose a different name. 348 dns.getLocalHost().incrementHostName(); 349 dns.getCache().clear(); 350 for (ServiceInfo serviceInfo : dns.getServices().values()) { 351 ServiceInfoImpl info = (ServiceInfoImpl) serviceInfo; 352 info.revertState(); 353 } 354 } 355 dns.revertState(); 356 return true; 357 } 358 return false; 359 } 360 361 /** 362 * Does the necessary actions, when this as a response. 363 */ 364 @Override 365 boolean handleResponse(JmDNSImpl dns) { 366 if (dns.getLocalHost().conflictWithRecord(this)) { 367 logger1.finer("handleResponse() Denial detected"); 368 369 if (dns.isProbing()) { 370 dns.getLocalHost().incrementHostName(); 371 dns.getCache().clear(); 372 for (ServiceInfo serviceInfo : dns.getServices().values()) { 373 ServiceInfoImpl info = (ServiceInfoImpl) serviceInfo; 374 info.revertState(); 375 } 376 } 377 dns.revertState(); 378 return true; 379 } 380 return false; 381 } 382 383 @Override 384 DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException { 385 return out; 386 } 387 388 /* 389 * (non-Javadoc) 390 * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) 391 */ 392 @Override 393 public ServiceInfo getServiceInfo(boolean persistent) { 394 ServiceInfoImpl info = new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, (byte[]) null); 395 // info.setAddress(_addr); This is done in the sub class so we don't have to test for class type 396 return info; 397 } 398 399 /* 400 * (non-Javadoc) 401 * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl) 402 */ 403 @Override 404 public ServiceEvent getServiceEvent(JmDNSImpl dns) { 405 ServiceInfo info = this.getServiceInfo(false); 406 ((ServiceInfoImpl) info).setDns(dns); 407 return new ServiceEventImpl(dns, info.getType(), info.getName(), info); 408 } 409 410 /* 411 * (non-Javadoc) 412 * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder) 413 */ 414 @Override 415 protected void toString(StringBuilder aLog) { 416 super.toString(aLog); 417 aLog.append(" address: '" + (this.getAddress() != null ? this.getAddress().getHostAddress() : "null") + "'"); 418 } 419 420 } 421 422 /** 423 * Pointer record. 424 */ 425 public static class Pointer extends DNSRecord { 426 // private static Logger logger = Logger.getLogger(Pointer.class.getName()); 427 private final String _alias; 428 429 public Pointer(String name, DNSRecordClass recordClass, boolean unique, int ttl, String alias) { 430 super(name, DNSRecordType.TYPE_PTR, recordClass, unique, ttl); 431 this._alias = alias; 432 } 433 434 /* 435 * (non-Javadoc) 436 * @see javax.jmdns.impl.DNSEntry#isSameEntry(javax.jmdns.impl.DNSEntry) 437 */ 438 @Override 439 public boolean isSameEntry(DNSEntry entry) { 440 return super.isSameEntry(entry) && (entry instanceof Pointer) && this.sameValue((Pointer) entry); 441 } 442 443 @Override 444 void write(MessageOutputStream out) { 445 out.writeName(_alias); 446 } 447 448 @Override 449 boolean sameValue(DNSRecord other) { 450 if (! (other instanceof Pointer) ) { 451 return false; 452 } 453 Pointer pointer = (Pointer) other; 454 if ((_alias == null) && (pointer._alias != null)) { 455 return false; 456 } 457 return _alias.equals(pointer._alias); 458 } 459 460 @Override 461 public boolean isSingleValued() { 462 return false; 463 } 464 465 @Override 466 boolean handleQuery(JmDNSImpl dns, long expirationTime) { 467 // Nothing to do (?) 468 // I think there is no possibility for conflicts for this record type? 469 return false; 470 } 471 472 @Override 473 boolean handleResponse(JmDNSImpl dns) { 474 // Nothing to do (?) 475 // I think there is no possibility for conflicts for this record type? 476 return false; 477 } 478 479 String getAlias() { 480 return _alias; 481 } 482 483 @Override 484 DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException { 485 return out; 486 } 487 488 /* 489 * (non-Javadoc) 490 * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) 491 */ 492 @Override 493 public ServiceInfo getServiceInfo(boolean persistent) { 494 if (this.isServicesDiscoveryMetaQuery()) { 495 // The service name is in the alias 496 Map<Fields, String> map = ServiceInfoImpl.decodeQualifiedNameMapForType(this.getAlias()); 497 return new ServiceInfoImpl(map, 0, 0, 0, persistent, (byte[]) null); 498 } else if (this.isReverseLookup()) { 499 return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, (byte[]) null); 500 } else if (this.isDomainDiscoveryQuery()) { 501 // FIXME [PJYF Nov 16 2010] We do not currently support domain discovery 502 return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, (byte[]) null); 503 } 504 Map<Fields, String> map = ServiceInfoImpl.decodeQualifiedNameMapForType(this.getAlias()); 505 map.put(Fields.Subtype, this.getQualifiedNameMap().get(Fields.Subtype)); 506 return new ServiceInfoImpl(map, 0, 0, 0, persistent, this.getAlias()); 507 } 508 509 /* 510 * (non-Javadoc) 511 * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl) 512 */ 513 @Override 514 public ServiceEvent getServiceEvent(JmDNSImpl dns) { 515 ServiceInfo info = this.getServiceInfo(false); 516 ((ServiceInfoImpl) info).setDns(dns); 517 String domainName = info.getType(); 518 String serviceName = JmDNSImpl.toUnqualifiedName(domainName, this.getAlias()); 519 return new ServiceEventImpl(dns, domainName, serviceName, info); 520 } 521 522 /* 523 * (non-Javadoc) 524 * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder) 525 */ 526 @Override 527 protected void toString(StringBuilder aLog) { 528 super.toString(aLog); 529 aLog.append(" alias: '" + (_alias != null ? _alias.toString() : "null") + "'"); 530 } 531 532 } 533 534 public final static byte[] EMPTY_TXT = new byte[] { 0 }; 535 536 public static class Text extends DNSRecord { 537 // private static Logger logger = Logger.getLogger(Text.class.getName()); 538 private final byte[] _text; 539 540 public Text(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte text[]) { 541 super(name, DNSRecordType.TYPE_TXT, recordClass, unique, ttl); 542 this._text = (text != null && text.length > 0 ? text : EMPTY_TXT); 543 } 544 545 /** 546 * @return the text 547 */ 548 byte[] getText() { 549 return this._text; 550 } 551 552 @Override 553 void write(MessageOutputStream out) { 554 out.writeBytes(_text, 0, _text.length); 555 } 556 557 @Override 558 boolean sameValue(DNSRecord other) { 559 if (! (other instanceof Text) ) { 560 return false; 561 } 562 Text txt = (Text) other; 563 if ((_text == null) && (txt._text != null)) { 564 return false; 565 } 566 if (txt._text.length != _text.length) { 567 return false; 568 } 569 for (int i = _text.length; i-- > 0;) { 570 if (txt._text[i] != _text[i]) { 571 return false; 572 } 573 } 574 return true; 575 } 576 577 @Override 578 public boolean isSingleValued() { 579 return true; 580 } 581 582 @Override 583 boolean handleQuery(JmDNSImpl dns, long expirationTime) { 584 // Nothing to do (?) 585 // I think there is no possibility for conflicts for this record type? 586 return false; 587 } 588 589 @Override 590 boolean handleResponse(JmDNSImpl dns) { 591 // Nothing to do (?) 592 // Shouldn't we care if we get a conflict at this level? 593 /* 594 * ServiceInfo info = (ServiceInfo) dns.services.get(name.toLowerCase()); if (info != null) { if (! Arrays.equals(text,info.text)) { info.revertState(); return true; } } 595 */ 596 return false; 597 } 598 599 @Override 600 DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException { 601 return out; 602 } 603 604 /* 605 * (non-Javadoc) 606 * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) 607 */ 608 @Override 609 public ServiceInfo getServiceInfo(boolean persistent) { 610 return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, _text); 611 } 612 613 /* 614 * (non-Javadoc) 615 * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl) 616 */ 617 @Override 618 public ServiceEvent getServiceEvent(JmDNSImpl dns) { 619 ServiceInfo info = this.getServiceInfo(false); 620 ((ServiceInfoImpl) info).setDns(dns); 621 return new ServiceEventImpl(dns, info.getType(), info.getName(), info); 622 } 623 624 /* 625 * (non-Javadoc) 626 * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder) 627 */ 628 @Override 629 protected void toString(StringBuilder aLog) { 630 super.toString(aLog); 631 aLog.append(" text: '" + ((_text.length > 20) ? new String(_text, 0, 17) + "..." : new String(_text)) + "'"); 632 } 633 634 } 635 636 /** 637 * Service record. 638 */ 639 public static class Service extends DNSRecord { 640 private static Logger logger1 = Logger.getLogger(Service.class.getName()); 641 private final int _priority; 642 private final int _weight; 643 private final int _port; 644 private final String _server; 645 646 public Service(String name, DNSRecordClass recordClass, boolean unique, int ttl, int priority, int weight, int port, String server) { 647 super(name, DNSRecordType.TYPE_SRV, recordClass, unique, ttl); 648 this._priority = priority; 649 this._weight = weight; 650 this._port = port; 651 this._server = server; 652 } 653 654 @Override 655 void write(MessageOutputStream out) { 656 out.writeShort(_priority); 657 out.writeShort(_weight); 658 out.writeShort(_port); 659 if (DNSIncoming.USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET) { 660 out.writeName(_server); 661 } else { 662 // [PJYF Nov 13 2010] Do we still need this? This looks really bad. All label are supposed to start by a length. 663 out.writeUTF(_server, 0, _server.length()); 664 665 // add a zero byte to the end just to be safe, this is the strange form 666 // used by the BonjourConformanceTest 667 out.writeByte(0); 668 } 669 } 670 671 @Override 672 protected void toByteArray(DataOutputStream dout) throws IOException { 673 super.toByteArray(dout); 674 dout.writeShort(_priority); 675 dout.writeShort(_weight); 676 dout.writeShort(_port); 677 try { 678 dout.write(_server.getBytes("UTF-8")); 679 } catch (UnsupportedEncodingException exception) { 680 /* UTF-8 is always present */ 681 } 682 } 683 684 String getServer() { 685 return _server; 686 } 687 688 /** 689 * @return the priority 690 */ 691 public int getPriority() { 692 return this._priority; 693 } 694 695 /** 696 * @return the weight 697 */ 698 public int getWeight() { 699 return this._weight; 700 } 701 702 /** 703 * @return the port 704 */ 705 public int getPort() { 706 return this._port; 707 } 708 709 @Override 710 boolean sameValue(DNSRecord other) { 711 if (! (other instanceof Service) ) { 712 return false; 713 } 714 Service s = (Service) other; 715 return (_priority == s._priority) && (_weight == s._weight) && (_port == s._port) && _server.equals(s._server); 716 } 717 718 @Override 719 public boolean isSingleValued() { 720 return true; 721 } 722 723 @Override 724 boolean handleQuery(JmDNSImpl dns, long expirationTime) { 725 ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(this.getKey()); 726 if (info != null && (info.isAnnouncing() || info.isAnnounced()) && (_port != info.getPort() || !_server.equalsIgnoreCase(dns.getLocalHost().getName()))) { 727 logger1.finer("handleQuery() Conflicting probe detected from: " + getRecordSource()); 728 DNSRecord.Service localService = new DNSRecord.Service(info.getQualifiedName(), DNSRecordClass.CLASS_IN, DNSRecordClass.UNIQUE, DNSConstants.DNS_TTL, info.getPriority(), info.getWeight(), info.getPort(), dns.getLocalHost().getName()); 729 730 // This block is useful for debugging race conditions when jmdns is responding to itself. 731 try { 732 if (dns.getInetAddress().equals(getRecordSource())) { 733 logger1.warning("Got conflicting probe from ourselves\n" + "incoming: " + this.toString() + "\n" + "local : " + localService.toString()); 734 } 735 } catch (IOException e) { 736 logger1.log(Level.WARNING, "IOException", e); 737 } 738 739 int comparison = this.compareTo(localService); 740 741 if (comparison == 0) { 742 // the 2 records are identical this probably means we are seeing our own record. 743 // With multiple interfaces on a single computer it is possible to see our 744 // own records come in on different interfaces than the ones they were sent on. 745 // see section "10. Conflict Resolution" of mdns draft spec. 746 logger1.finer("handleQuery() Ignoring a identical service query"); 747 return false; 748 } 749 750 // Tie breaker test 751 if (info.isProbing() && comparison > 0) { 752 // We lost the tie break 753 String oldName = info.getQualifiedName().toLowerCase(); 754 info.setName(dns.incrementName(info.getName())); 755 dns.getServices().remove(oldName); 756 dns.getServices().put(info.getQualifiedName().toLowerCase(), info); 757 logger1.finer("handleQuery() Lost tie break: new unique name chosen:" + info.getName()); 758 759 // We revert the state to start probing again with the new name 760 info.revertState(); 761 } else { 762 // We won the tie break, so this conflicting probe should be ignored 763 // See paragraph 3 of section 9.2 in mdns draft spec 764 return false; 765 } 766 767 return true; 768 769 } 770 return false; 771 } 772 773 @Override 774 boolean handleResponse(JmDNSImpl dns) { 775 ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(this.getKey()); 776 if (info != null && (_port != info.getPort() || !_server.equalsIgnoreCase(dns.getLocalHost().getName()))) { 777 logger1.finer("handleResponse() Denial detected"); 778 779 if (info.isProbing()) { 780 String oldName = info.getQualifiedName().toLowerCase(); 781 info.setName(dns.incrementName(info.getName())); 782 dns.getServices().remove(oldName); 783 dns.getServices().put(info.getQualifiedName().toLowerCase(), info); 784 logger1.finer("handleResponse() New unique name chose:" + info.getName()); 785 786 } 787 info.revertState(); 788 return true; 789 } 790 return false; 791 } 792 793 @Override 794 DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException { 795 ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(this.getKey()); 796 if (info != null) { 797 if (this._port == info.getPort() != _server.equals(dns.getLocalHost().getName())) { 798 return dns.addAnswer(in, addr, port, out, new DNSRecord.Service(info.getQualifiedName(), DNSRecordClass.CLASS_IN, DNSRecordClass.UNIQUE, DNSConstants.DNS_TTL, info.getPriority(), info.getWeight(), info.getPort(), dns 799 .getLocalHost().getName())); 800 } 801 } 802 return out; 803 } 804 805 /* 806 * (non-Javadoc) 807 * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) 808 */ 809 @Override 810 public ServiceInfo getServiceInfo(boolean persistent) { 811 return new ServiceInfoImpl(this.getQualifiedNameMap(), _port, _weight, _priority, persistent, _server); 812 } 813 814 /* 815 * (non-Javadoc) 816 * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl) 817 */ 818 @Override 819 public ServiceEvent getServiceEvent(JmDNSImpl dns) { 820 ServiceInfo info = this.getServiceInfo(false); 821 ((ServiceInfoImpl) info).setDns(dns); 822 // String domainName = ""; 823 // String serviceName = this.getServer(); 824 // int index = serviceName.indexOf('.'); 825 // if (index > 0) 826 // { 827 // serviceName = this.getServer().substring(0, index); 828 // if (index + 1 < this.getServer().length()) 829 // domainName = this.getServer().substring(index + 1); 830 // } 831 // return new ServiceEventImpl(dns, domainName, serviceName, info); 832 return new ServiceEventImpl(dns, info.getType(), info.getName(), info); 833 834 } 835 836 /* 837 * (non-Javadoc) 838 * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder) 839 */ 840 @Override 841 protected void toString(StringBuilder aLog) { 842 super.toString(aLog); 843 aLog.append(" server: '" + _server + ":" + _port + "'"); 844 } 845 846 } 847 848 public static class HostInformation extends DNSRecord { 849 String _os; 850 String _cpu; 851 852 /** 853 * @param name 854 * @param recordClass 855 * @param unique 856 * @param ttl 857 * @param cpu 858 * @param os 859 */ 860 public HostInformation(String name, DNSRecordClass recordClass, boolean unique, int ttl, String cpu, String os) { 861 super(name, DNSRecordType.TYPE_HINFO, recordClass, unique, ttl); 862 _cpu = cpu; 863 _os = os; 864 } 865 866 /* 867 * (non-Javadoc) 868 * @see javax.jmdns.impl.DNSRecord#addAnswer(javax.jmdns.impl.JmDNSImpl, javax.jmdns.impl.DNSIncoming, java.net.InetAddress, int, javax.jmdns.impl.DNSOutgoing) 869 */ 870 @Override 871 DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException { 872 return out; 873 } 874 875 /* 876 * (non-Javadoc) 877 * @see javax.jmdns.impl.DNSRecord#handleQuery(javax.jmdns.impl.JmDNSImpl, long) 878 */ 879 @Override 880 boolean handleQuery(JmDNSImpl dns, long expirationTime) { 881 return false; 882 } 883 884 /* 885 * (non-Javadoc) 886 * @see javax.jmdns.impl.DNSRecord#handleResponse(javax.jmdns.impl.JmDNSImpl) 887 */ 888 @Override 889 boolean handleResponse(JmDNSImpl dns) { 890 return false; 891 } 892 893 /* 894 * (non-Javadoc) 895 * @see javax.jmdns.impl.DNSRecord#sameValue(javax.jmdns.impl.DNSRecord) 896 */ 897 @Override 898 boolean sameValue(DNSRecord other) { 899 if (! (other instanceof HostInformation) ) { 900 return false; 901 } 902 HostInformation hinfo = (HostInformation) other; 903 if ((_cpu == null) && (hinfo._cpu != null)) { 904 return false; 905 } 906 if ((_os == null) && (hinfo._os != null)) { 907 return false; 908 } 909 return _cpu.equals(hinfo._cpu) && _os.equals(hinfo._os); 910 } 911 912 /* 913 * (non-Javadoc) 914 * @see javax.jmdns.impl.DNSRecord#isSingleValued() 915 */ 916 @Override 917 public boolean isSingleValued() { 918 return true; 919 } 920 921 /* 922 * (non-Javadoc) 923 * @see javax.jmdns.impl.DNSRecord#write(javax.jmdns.impl.DNSOutgoing) 924 */ 925 @Override 926 void write(MessageOutputStream out) { 927 String hostInfo = _cpu + " " + _os; 928 out.writeUTF(hostInfo, 0, hostInfo.length()); 929 } 930 931 /* 932 * (non-Javadoc) 933 * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean) 934 */ 935 @Override 936 public ServiceInfo getServiceInfo(boolean persistent) { 937 Map<String, String> hinfo = new HashMap<String, String>(2); 938 hinfo.put("cpu", _cpu); 939 hinfo.put("os", _os); 940 return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, hinfo); 941 } 942 943 /* 944 * (non-Javadoc) 945 * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl) 946 */ 947 @Override 948 public ServiceEvent getServiceEvent(JmDNSImpl dns) { 949 ServiceInfo info = this.getServiceInfo(false); 950 ((ServiceInfoImpl) info).setDns(dns); 951 return new ServiceEventImpl(dns, info.getType(), info.getName(), info); 952 } 953 954 /* 955 * (non-Javadoc) 956 * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder) 957 */ 958 @Override 959 protected void toString(StringBuilder aLog) { 960 super.toString(aLog); 961 aLog.append(" cpu: '" + _cpu + "' os: '" + _os + "'"); 962 } 963 964 } 965 966 /** 967 * Determine if a record can have multiple values in the cache. 968 * 969 * @return <code>false</code> if this record can have multiple values in the cache, <code>true</code> otherwise. 970 */ 971 public abstract boolean isSingleValued(); 972 973 /** 974 * Return a service information associated with that record if appropriate. 975 * 976 * @return service information 977 */ 978 public ServiceInfo getServiceInfo() { 979 return this.getServiceInfo(false); 980 } 981 982 /** 983 * Return a service information associated with that record if appropriate. 984 * 985 * @param persistent 986 * if <code>true</code> ServiceListener.resolveService will be called whenever new new information is received. 987 * @return service information 988 */ 989 public abstract ServiceInfo getServiceInfo(boolean persistent); 990 991 /** 992 * Creates and return a service event for this record. 993 * 994 * @param dns 995 * DNS serviced by this event 996 * @return service event 997 */ 998 public abstract ServiceEvent getServiceEvent(JmDNSImpl dns); 999 1000 public void setRecordSource(InetAddress source) { 1001 this._source = source; 1002 } 1003 1004 public InetAddress getRecordSource() { 1005 return _source; 1006 } 1007 1008 /* 1009 * (non-Javadoc) 1010 * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder) 1011 */ 1012 @Override 1013 protected void toString(StringBuilder aLog) { 1014 super.toString(aLog); 1015 aLog.append(" ttl: '" + getRemainingTTL(System.currentTimeMillis()) + "/" + _ttl + "'"); 1016 } 1017 1018 public void setTTL(int ttl) { 1019 this._ttl = ttl; 1020 } 1021 1022 public int getTTL() { 1023 return _ttl; 1024 } 1025 } 1026