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 */ DNSRecord(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl)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 equals(Object other)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 */ sameValue(DNSRecord other)63 abstract boolean sameValue(DNSRecord other); 64 65 /** 66 * True if this record has the same type as some other record. 67 */ sameType(DNSRecord other)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 */ handleQuery(JmDNSImpl dns, long expirationTime)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 */ handleResponse(JmDNSImpl dns)84 abstract boolean handleResponse(JmDNSImpl dns); 85 86 /** 87 * Adds this as an answer to the provided outgoing datagram. 88 */ addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out)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 */ suppressedBy(DNSIncoming msg)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 */ suppressedBy(DNSRecord other)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 */ getExpirationTime(int percent)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 */ getRemainingTTL(long now)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 isExpired(long now)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 isStale(long now)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 */ resetTTL(DNSRecord other)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 */ setWillExpireSoon(long now)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 */ write(MessageOutputStream out)171 abstract void write(MessageOutputStream out); 172 173 public static class IPv4Address extends Address { 174 IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr)175 IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) { 176 super(name, DNSRecordType.TYPE_A, recordClass, unique, ttl, addr); 177 } 178 IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress)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 write(MessageOutputStream out)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 getServiceInfo(boolean persistent)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 IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr)217 IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) { 218 super(name, DNSRecordType.TYPE_AAAA, recordClass, unique, ttl, addr); 219 } 220 IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress)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 write(MessageOutputStream out)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 getServiceInfo(boolean persistent)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 Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr)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 Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress)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 same(DNSRecord other)282 boolean same(DNSRecord other) { 283 if (! (other instanceof Address) ) { 284 return false; 285 } 286 return ((sameName(other)) && ((sameValue(other)))); 287 } 288 sameName(DNSRecord other)289 boolean sameName(DNSRecord other) { 290 return this.getName().equalsIgnoreCase(other.getName()); 291 } 292 293 @Override sameValue(DNSRecord other)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 isSingleValued()306 public boolean isSingleValued() { 307 return false; 308 } 309 getAddress()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 toByteArray(DataOutputStream dout)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 handleQuery(JmDNSImpl dns, long expirationTime)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 handleResponse(JmDNSImpl dns)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 addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out)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 getServiceInfo(boolean persistent)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 getServiceEvent(JmDNSImpl dns)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 toString(StringBuilder aLog)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 Pointer(String name, DNSRecordClass recordClass, boolean unique, int ttl, String alias)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 isSameEntry(DNSEntry entry)439 public boolean isSameEntry(DNSEntry entry) { 440 return super.isSameEntry(entry) && (entry instanceof Pointer) && this.sameValue((Pointer) entry); 441 } 442 443 @Override write(MessageOutputStream out)444 void write(MessageOutputStream out) { 445 out.writeName(_alias); 446 } 447 448 @Override sameValue(DNSRecord other)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 isSingleValued()461 public boolean isSingleValued() { 462 return false; 463 } 464 465 @Override handleQuery(JmDNSImpl dns, long expirationTime)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 handleResponse(JmDNSImpl dns)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 getAlias()479 String getAlias() { 480 return _alias; 481 } 482 483 @Override addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out)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 getServiceInfo(boolean persistent)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 getServiceEvent(JmDNSImpl dns)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 toString(StringBuilder aLog)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 Text(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte text[])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 */ getText()548 byte[] getText() { 549 return this._text; 550 } 551 552 @Override write(MessageOutputStream out)553 void write(MessageOutputStream out) { 554 out.writeBytes(_text, 0, _text.length); 555 } 556 557 @Override sameValue(DNSRecord other)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 isSingleValued()578 public boolean isSingleValued() { 579 return true; 580 } 581 582 @Override handleQuery(JmDNSImpl dns, long expirationTime)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 handleResponse(JmDNSImpl dns)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 addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out)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 getServiceInfo(boolean persistent)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 getServiceEvent(JmDNSImpl dns)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 toString(StringBuilder aLog)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 Service(String name, DNSRecordClass recordClass, boolean unique, int ttl, int priority, int weight, int port, String server)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 write(MessageOutputStream out)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 toByteArray(DataOutputStream dout)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 getServer()684 String getServer() { 685 return _server; 686 } 687 688 /** 689 * @return the priority 690 */ getPriority()691 public int getPriority() { 692 return this._priority; 693 } 694 695 /** 696 * @return the weight 697 */ getWeight()698 public int getWeight() { 699 return this._weight; 700 } 701 702 /** 703 * @return the port 704 */ getPort()705 public int getPort() { 706 return this._port; 707 } 708 709 @Override sameValue(DNSRecord other)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 isSingleValued()719 public boolean isSingleValued() { 720 return true; 721 } 722 723 @Override handleQuery(JmDNSImpl dns, long expirationTime)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 handleResponse(JmDNSImpl dns)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 addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out)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 getServiceInfo(boolean persistent)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 getServiceEvent(JmDNSImpl dns)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 toString(StringBuilder aLog)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 */ HostInformation(String name, DNSRecordClass recordClass, boolean unique, int ttl, String cpu, String os)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 addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out)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 handleQuery(JmDNSImpl dns, long expirationTime)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 handleResponse(JmDNSImpl dns)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 sameValue(DNSRecord other)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 isSingleValued()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 write(MessageOutputStream out)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 getServiceInfo(boolean persistent)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 getServiceEvent(JmDNSImpl dns)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 toString(StringBuilder aLog)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 */ isSingleValued()971 public abstract boolean isSingleValued(); 972 973 /** 974 * Return a service information associated with that record if appropriate. 975 * 976 * @return service information 977 */ getServiceInfo()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 */ getServiceInfo(boolean persistent)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 */ getServiceEvent(JmDNSImpl dns)998 public abstract ServiceEvent getServiceEvent(JmDNSImpl dns); 999 setRecordSource(InetAddress source)1000 public void setRecordSource(InetAddress source) { 1001 this._source = source; 1002 } 1003 getRecordSource()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 toString(StringBuilder aLog)1013 protected void toString(StringBuilder aLog) { 1014 super.toString(aLog); 1015 aLog.append(" ttl: '" + getRemainingTTL(System.currentTimeMillis()) + "/" + _ttl + "'"); 1016 } 1017 setTTL(int ttl)1018 public void setTTL(int ttl) { 1019 this._ttl = ttl; 1020 } 1021 getTTL()1022 public int getTTL() { 1023 return _ttl; 1024 } 1025 } 1026