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.ByteArrayOutputStream; 8 import java.io.IOException; 9 import java.io.OutputStream; 10 import java.net.Inet4Address; 11 import java.net.Inet6Address; 12 import java.net.InetAddress; 13 import java.util.ArrayList; 14 import java.util.Collection; 15 import java.util.Collections; 16 import java.util.Enumeration; 17 import java.util.HashMap; 18 import java.util.Hashtable; 19 import java.util.LinkedHashSet; 20 import java.util.List; 21 import java.util.Map; 22 import java.util.Set; 23 import java.util.Vector; 24 import java.util.logging.Level; 25 import java.util.logging.Logger; 26 27 import javax.jmdns.ServiceEvent; 28 import javax.jmdns.ServiceInfo; 29 import javax.jmdns.impl.DNSRecord.Pointer; 30 import javax.jmdns.impl.DNSRecord.Service; 31 import javax.jmdns.impl.DNSRecord.Text; 32 import javax.jmdns.impl.constants.DNSRecordClass; 33 import javax.jmdns.impl.constants.DNSRecordType; 34 import javax.jmdns.impl.constants.DNSState; 35 import javax.jmdns.impl.tasks.DNSTask; 36 37 /** 38 * JmDNS service information. 39 * 40 * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer 41 */ 42 public class ServiceInfoImpl extends ServiceInfo implements DNSListener, DNSStatefulObject { 43 private static Logger logger = Logger.getLogger(ServiceInfoImpl.class.getName()); 44 45 private String _domain; 46 private String _protocol; 47 private String _application; 48 private String _name; 49 private String _subtype; 50 private String _server; 51 private int _port; 52 private int _weight; 53 private int _priority; 54 private byte _text[]; 55 private Map<String, byte[]> _props; 56 private final Set<Inet4Address> _ipv4Addresses; 57 private final Set<Inet6Address> _ipv6Addresses; 58 59 private transient String _key; 60 61 private boolean _persistent; 62 private boolean _needTextAnnouncing; 63 64 private final ServiceInfoState _state; 65 66 private Delegate _delegate; 67 68 public static interface Delegate { 69 70 public void textValueUpdated(ServiceInfo target, byte[] value); 71 72 } 73 74 private final static class ServiceInfoState extends DNSStatefulObject.DefaultImplementation { 75 76 private static final long serialVersionUID = 1104131034952196820L; 77 78 private final ServiceInfoImpl _info; 79 80 /** 81 * @param info 82 */ 83 public ServiceInfoState(ServiceInfoImpl info) { 84 super(); 85 _info = info; 86 } 87 88 @Override 89 protected void setTask(DNSTask task) { 90 super.setTask(task); 91 if ((this._task == null) && _info.needTextAnnouncing()) { 92 this.lock(); 93 try { 94 if ((this._task == null) && _info.needTextAnnouncing()) { 95 if (this._state.isAnnounced()) { 96 this.setState(DNSState.ANNOUNCING_1); 97 if (this.getDns() != null) { 98 this.getDns().startAnnouncer(); 99 } 100 } 101 _info.setNeedTextAnnouncing(false); 102 } 103 } finally { 104 this.unlock(); 105 } 106 } 107 } 108 109 @Override 110 public void setDns(JmDNSImpl dns) { 111 super.setDns(dns); 112 } 113 114 } 115 116 /** 117 * @param type 118 * @param name 119 * @param subtype 120 * @param port 121 * @param weight 122 * @param priority 123 * @param persistent 124 * @param text 125 * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, String) 126 */ 127 public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, String text) { 128 this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, (byte[]) null); 129 _server = text; 130 try { 131 ByteArrayOutputStream out = new ByteArrayOutputStream(text.length()); 132 writeUTF(out, text); 133 this._text = out.toByteArray(); 134 } catch (IOException e) { 135 throw new RuntimeException("unexpected exception: " + e); 136 } 137 } 138 139 /** 140 * @param type 141 * @param name 142 * @param subtype 143 * @param port 144 * @param weight 145 * @param priority 146 * @param persistent 147 * @param props 148 * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, Map) 149 */ 150 public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, Map<String, ?> props) { 151 this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, textFromProperties(props)); 152 } 153 154 /** 155 * @param type 156 * @param name 157 * @param subtype 158 * @param port 159 * @param weight 160 * @param priority 161 * @param persistent 162 * @param text 163 * @see javax.jmdns.ServiceInfo#create(String, String, int, int, int, byte[]) 164 */ 165 public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, byte text[]) { 166 this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, text); 167 } 168 169 public ServiceInfoImpl(Map<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, Map<String, ?> props) { 170 this(qualifiedNameMap, port, weight, priority, persistent, textFromProperties(props)); 171 } 172 173 ServiceInfoImpl(Map<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, String text) { 174 this(qualifiedNameMap, port, weight, priority, persistent, (byte[]) null); 175 _server = text; 176 try { 177 ByteArrayOutputStream out = new ByteArrayOutputStream(text.length()); 178 writeUTF(out, text); 179 this._text = out.toByteArray(); 180 } catch (IOException e) { 181 throw new RuntimeException("unexpected exception: " + e); 182 } 183 } 184 185 ServiceInfoImpl(Map<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, byte text[]) { 186 Map<Fields, String> map = ServiceInfoImpl.checkQualifiedNameMap(qualifiedNameMap); 187 188 this._domain = map.get(Fields.Domain); 189 this._protocol = map.get(Fields.Protocol); 190 this._application = map.get(Fields.Application); 191 this._name = map.get(Fields.Instance); 192 this._subtype = map.get(Fields.Subtype); 193 194 this._port = port; 195 this._weight = weight; 196 this._priority = priority; 197 this._text = text; 198 this.setNeedTextAnnouncing(false); 199 this._state = new ServiceInfoState(this); 200 this._persistent = persistent; 201 this._ipv4Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet4Address>()); 202 this._ipv6Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet6Address>()); 203 } 204 205 /** 206 * During recovery we need to duplicate service info to reregister them 207 * 208 * @param info 209 */ 210 ServiceInfoImpl(ServiceInfo info) { 211 this._ipv4Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet4Address>()); 212 this._ipv6Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet6Address>()); 213 if (info != null) { 214 this._domain = info.getDomain(); 215 this._protocol = info.getProtocol(); 216 this._application = info.getApplication(); 217 this._name = info.getName(); 218 this._subtype = info.getSubtype(); 219 this._port = info.getPort(); 220 this._weight = info.getWeight(); 221 this._priority = info.getPriority(); 222 this._text = info.getTextBytes(); 223 this._persistent = info.isPersistent(); 224 Inet6Address[] ipv6Addresses = info.getInet6Addresses(); 225 for (Inet6Address address : ipv6Addresses) { 226 this._ipv6Addresses.add(address); 227 } 228 Inet4Address[] ipv4Addresses = info.getInet4Addresses(); 229 for (Inet4Address address : ipv4Addresses) { 230 this._ipv4Addresses.add(address); 231 } 232 } 233 this._state = new ServiceInfoState(this); 234 } 235 236 public static Map<Fields, String> decodeQualifiedNameMap(String type, String name, String subtype) { 237 Map<Fields, String> qualifiedNameMap = decodeQualifiedNameMapForType(type); 238 239 qualifiedNameMap.put(Fields.Instance, name); 240 qualifiedNameMap.put(Fields.Subtype, subtype); 241 242 return checkQualifiedNameMap(qualifiedNameMap); 243 } 244 245 public static Map<Fields, String> decodeQualifiedNameMapForType(String type) { 246 int index; 247 248 String casePreservedType = type; 249 250 String aType = type.toLowerCase(); 251 String application = aType; 252 String protocol = ""; 253 String subtype = ""; 254 String name = ""; 255 String domain = ""; 256 257 if (aType.contains("in-addr.arpa") || aType.contains("ip6.arpa")) { 258 index = (aType.contains("in-addr.arpa") ? aType.indexOf("in-addr.arpa") : aType.indexOf("ip6.arpa")); 259 name = removeSeparators(casePreservedType.substring(0, index)); 260 domain = casePreservedType.substring(index); 261 application = ""; 262 } else if ((!aType.contains("_")) && aType.contains(".")) { 263 index = aType.indexOf('.'); 264 name = removeSeparators(casePreservedType.substring(0, index)); 265 domain = removeSeparators(casePreservedType.substring(index)); 266 application = ""; 267 } else { 268 // First remove the name if it there. 269 if (!aType.startsWith("_") || aType.startsWith("_services")) { 270 index = aType.indexOf('.'); 271 if (index > 0) { 272 // We need to preserve the case for the user readable name. 273 name = casePreservedType.substring(0, index); 274 if (index + 1 < aType.length()) { 275 aType = aType.substring(index + 1); 276 casePreservedType = casePreservedType.substring(index + 1); 277 } 278 } 279 } 280 281 index = aType.lastIndexOf("._"); 282 if (index > 0) { 283 int start = index + 2; 284 int end = aType.indexOf('.', start); 285 protocol = casePreservedType.substring(start, end); 286 } 287 if (protocol.length() > 0) { 288 index = aType.indexOf("_" + protocol.toLowerCase() + "."); 289 int start = index + protocol.length() + 2; 290 int end = aType.length() - (aType.endsWith(".") ? 1 : 0); 291 domain = casePreservedType.substring(start, end); 292 application = casePreservedType.substring(0, index - 1); 293 } 294 index = application.toLowerCase().indexOf("._sub"); 295 if (index > 0) { 296 int start = index + 5; 297 subtype = removeSeparators(application.substring(0, index)); 298 application = application.substring(start); 299 } 300 } 301 302 final Map<Fields, String> qualifiedNameMap = new HashMap<Fields, String>(5); 303 qualifiedNameMap.put(Fields.Domain, removeSeparators(domain)); 304 qualifiedNameMap.put(Fields.Protocol, protocol); 305 qualifiedNameMap.put(Fields.Application, removeSeparators(application)); 306 qualifiedNameMap.put(Fields.Instance, name); 307 qualifiedNameMap.put(Fields.Subtype, subtype); 308 309 return qualifiedNameMap; 310 } 311 312 protected static Map<Fields, String> checkQualifiedNameMap(Map<Fields, String> qualifiedNameMap) { 313 Map<Fields, String> checkedQualifiedNameMap = new HashMap<Fields, String>(5); 314 315 // Optional domain 316 String domain = (qualifiedNameMap.containsKey(Fields.Domain) ? qualifiedNameMap.get(Fields.Domain) : "local"); 317 if ((domain == null) || (domain.length() == 0)) { 318 domain = "local"; 319 } 320 domain = removeSeparators(domain); 321 checkedQualifiedNameMap.put(Fields.Domain, domain); 322 // Optional protocol 323 String protocol = (qualifiedNameMap.containsKey(Fields.Protocol) ? qualifiedNameMap.get(Fields.Protocol) : "tcp"); 324 if ((protocol == null) || (protocol.length() == 0)) { 325 protocol = "tcp"; 326 } 327 protocol = removeSeparators(protocol); 328 checkedQualifiedNameMap.put(Fields.Protocol, protocol); 329 // Application 330 String application = (qualifiedNameMap.containsKey(Fields.Application) ? qualifiedNameMap.get(Fields.Application) : ""); 331 if ((application == null) || (application.length() == 0)) { 332 application = ""; 333 } 334 application = removeSeparators(application); 335 checkedQualifiedNameMap.put(Fields.Application, application); 336 // Instance 337 String instance = (qualifiedNameMap.containsKey(Fields.Instance) ? qualifiedNameMap.get(Fields.Instance) : ""); 338 if ((instance == null) || (instance.length() == 0)) { 339 instance = ""; 340 // throw new IllegalArgumentException("The instance name component of a fully qualified service cannot be empty."); 341 } 342 instance = removeSeparators(instance); 343 checkedQualifiedNameMap.put(Fields.Instance, instance); 344 // Optional Subtype 345 String subtype = (qualifiedNameMap.containsKey(Fields.Subtype) ? qualifiedNameMap.get(Fields.Subtype) : ""); 346 if ((subtype == null) || (subtype.length() == 0)) { 347 subtype = ""; 348 } 349 subtype = removeSeparators(subtype); 350 checkedQualifiedNameMap.put(Fields.Subtype, subtype); 351 352 return checkedQualifiedNameMap; 353 } 354 355 private static String removeSeparators(String name) { 356 if (name == null) { 357 return ""; 358 } 359 String newName = name.trim(); 360 if (newName.startsWith(".")) { 361 newName = newName.substring(1); 362 } 363 if (newName.startsWith("_")) { 364 newName = newName.substring(1); 365 } 366 if (newName.endsWith(".")) { 367 newName = newName.substring(0, newName.length() - 1); 368 } 369 return newName; 370 } 371 372 /** 373 * {@inheritDoc} 374 */ 375 @Override 376 public String getType() { 377 String domain = this.getDomain(); 378 String protocol = this.getProtocol(); 379 String application = this.getApplication(); 380 return (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; 381 } 382 383 /** 384 * {@inheritDoc} 385 */ 386 @Override 387 public String getTypeWithSubtype() { 388 String subtype = this.getSubtype(); 389 return (subtype.length() > 0 ? "_" + subtype.toLowerCase() + "._sub." : "") + this.getType(); 390 } 391 392 /** 393 * {@inheritDoc} 394 */ 395 @Override 396 public String getName() { 397 return (_name != null ? _name : ""); 398 } 399 400 /** 401 * {@inheritDoc} 402 */ 403 @Override 404 public String getKey() { 405 if (this._key == null) { 406 this._key = this.getQualifiedName().toLowerCase(); 407 } 408 return this._key; 409 } 410 411 /** 412 * Sets the service instance name. 413 * 414 * @param name 415 * unqualified service instance name, such as <code>foobar</code> 416 */ 417 void setName(String name) { 418 this._name = name; 419 this._key = null; 420 } 421 422 /** 423 * {@inheritDoc} 424 */ 425 @Override 426 public String getQualifiedName() { 427 String domain = this.getDomain(); 428 String protocol = this.getProtocol(); 429 String application = this.getApplication(); 430 String instance = this.getName(); 431 // String subtype = this.getSubtype(); 432 // return (instance.length() > 0 ? instance + "." : "") + (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + (subtype.length() > 0 ? ",_" + subtype.toLowerCase() + "." : ".") : "") + domain 433 // + "."; 434 return (instance.length() > 0 ? instance + "." : "") + (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; 435 } 436 437 /** 438 * @see javax.jmdns.ServiceInfo#getServer() 439 */ 440 @Override 441 public String getServer() { 442 return (_server != null ? _server : ""); 443 } 444 445 /** 446 * @param server 447 * the server to set 448 */ 449 void setServer(String server) { 450 this._server = server; 451 } 452 453 /** 454 * {@inheritDoc} 455 */ 456 @Deprecated 457 @Override 458 public String getHostAddress() { 459 String[] names = this.getHostAddresses(); 460 return (names.length > 0 ? names[0] : ""); 461 } 462 463 /** 464 * {@inheritDoc} 465 */ 466 @Override 467 public String[] getHostAddresses() { 468 Inet4Address[] ip4Aaddresses = this.getInet4Addresses(); 469 Inet6Address[] ip6Aaddresses = this.getInet6Addresses(); 470 String[] names = new String[ip4Aaddresses.length + ip6Aaddresses.length]; 471 for (int i = 0; i < ip4Aaddresses.length; i++) { 472 names[i] = ip4Aaddresses[i].getHostAddress(); 473 } 474 for (int i = 0; i < ip6Aaddresses.length; i++) { 475 names[i + ip4Aaddresses.length] = "[" + ip6Aaddresses[i].getHostAddress() + "]"; 476 } 477 return names; 478 } 479 480 /** 481 * @param addr 482 * the addr to add 483 */ 484 void addAddress(Inet4Address addr) { 485 _ipv4Addresses.add(addr); 486 } 487 488 /** 489 * @param addr 490 * the addr to add 491 */ 492 void addAddress(Inet6Address addr) { 493 _ipv6Addresses.add(addr); 494 } 495 496 /** 497 * {@inheritDoc} 498 */ 499 @Deprecated 500 @Override 501 public InetAddress getAddress() { 502 return this.getInetAddress(); 503 } 504 505 /** 506 * {@inheritDoc} 507 */ 508 @Deprecated 509 @Override 510 public InetAddress getInetAddress() { 511 InetAddress[] addresses = this.getInetAddresses(); 512 return (addresses.length > 0 ? addresses[0] : null); 513 } 514 515 /** 516 * {@inheritDoc} 517 */ 518 @Deprecated 519 @Override 520 public Inet4Address getInet4Address() { 521 Inet4Address[] addresses = this.getInet4Addresses(); 522 return (addresses.length > 0 ? addresses[0] : null); 523 } 524 525 /** 526 * {@inheritDoc} 527 */ 528 @Deprecated 529 @Override 530 public Inet6Address getInet6Address() { 531 Inet6Address[] addresses = this.getInet6Addresses(); 532 return (addresses.length > 0 ? addresses[0] : null); 533 } 534 535 /* 536 * (non-Javadoc) 537 * @see javax.jmdns.ServiceInfo#getInetAddresses() 538 */ 539 @Override 540 public InetAddress[] getInetAddresses() { 541 List<InetAddress> aList = new ArrayList<InetAddress>(_ipv4Addresses.size() + _ipv6Addresses.size()); 542 aList.addAll(_ipv4Addresses); 543 aList.addAll(_ipv6Addresses); 544 return aList.toArray(new InetAddress[aList.size()]); 545 } 546 547 /* 548 * (non-Javadoc) 549 * @see javax.jmdns.ServiceInfo#getInet4Addresses() 550 */ 551 @Override 552 public Inet4Address[] getInet4Addresses() { 553 return _ipv4Addresses.toArray(new Inet4Address[_ipv4Addresses.size()]); 554 } 555 556 /* 557 * (non-Javadoc) 558 * @see javax.jmdns.ServiceInfo#getInet6Addresses() 559 */ 560 @Override 561 public Inet6Address[] getInet6Addresses() { 562 return _ipv6Addresses.toArray(new Inet6Address[_ipv6Addresses.size()]); 563 } 564 565 /** 566 * @see javax.jmdns.ServiceInfo#getPort() 567 */ 568 @Override 569 public int getPort() { 570 return _port; 571 } 572 573 /** 574 * @see javax.jmdns.ServiceInfo#getPriority() 575 */ 576 @Override 577 public int getPriority() { 578 return _priority; 579 } 580 581 /** 582 * @see javax.jmdns.ServiceInfo#getWeight() 583 */ 584 @Override 585 public int getWeight() { 586 return _weight; 587 } 588 589 /** 590 * @see javax.jmdns.ServiceInfo#getTextBytes() 591 */ 592 @Override 593 public byte[] getTextBytes() { 594 return (this._text != null && this._text.length > 0 ? this._text : DNSRecord.EMPTY_TXT); 595 } 596 597 /** 598 * {@inheritDoc} 599 */ 600 @Deprecated 601 @Override 602 public String getTextString() { 603 Map<String, byte[]> properties = this.getProperties(); 604 for (String key : properties.keySet()) { 605 byte[] value = properties.get(key); 606 if ((value != null) && (value.length > 0)) { 607 return key + "=" + new String(value); 608 } 609 return key; 610 } 611 return ""; 612 } 613 614 /* 615 * (non-Javadoc) 616 * @see javax.jmdns.ServiceInfo#getURL() 617 */ 618 @Deprecated 619 @Override 620 public String getURL() { 621 return this.getURL("http"); 622 } 623 624 /* 625 * (non-Javadoc) 626 * @see javax.jmdns.ServiceInfo#getURLs() 627 */ 628 @Override 629 public String[] getURLs() { 630 return this.getURLs("http"); 631 } 632 633 /* 634 * (non-Javadoc) 635 * @see javax.jmdns.ServiceInfo#getURL(java.lang.String) 636 */ 637 @Deprecated 638 @Override 639 public String getURL(String protocol) { 640 String[] urls = this.getURLs(protocol); 641 return (urls.length > 0 ? urls[0] : protocol + "://null:" + getPort()); 642 } 643 644 /* 645 * (non-Javadoc) 646 * @see javax.jmdns.ServiceInfo#getURLs(java.lang.String) 647 */ 648 @Override 649 public String[] getURLs(String protocol) { 650 InetAddress[] addresses = this.getInetAddresses(); 651 String[] urls = new String[addresses.length]; 652 for (int i = 0; i < addresses.length; i++) { 653 String url = protocol + "://" + addresses[i].getHostAddress() + ":" + getPort(); 654 String path = getPropertyString("path"); 655 if (path != null) { 656 if (path.indexOf("://") >= 0) { 657 url = path; 658 } else { 659 url += path.startsWith("/") ? path : "/" + path; 660 } 661 } 662 urls[i] = url; 663 } 664 return urls; 665 } 666 667 /** 668 * {@inheritDoc} 669 */ 670 @Override 671 public synchronized byte[] getPropertyBytes(String name) { 672 return this.getProperties().get(name); 673 } 674 675 /** 676 * {@inheritDoc} 677 */ 678 @Override 679 public synchronized String getPropertyString(String name) { 680 byte data[] = this.getProperties().get(name); 681 if (data == null) { 682 return null; 683 } 684 if (data == NO_VALUE) { 685 return "true"; 686 } 687 return readUTF(data, 0, data.length); 688 } 689 690 /** 691 * {@inheritDoc} 692 */ 693 @Override 694 public Enumeration<String> getPropertyNames() { 695 Map<String, byte[]> properties = this.getProperties(); 696 Collection<String> names = (properties != null ? properties.keySet() : Collections.<String> emptySet()); 697 return new Vector<String>(names).elements(); 698 } 699 700 /** 701 * {@inheritDoc} 702 */ 703 @Override 704 public String getApplication() { 705 return (_application != null ? _application : ""); 706 } 707 708 /** 709 * {@inheritDoc} 710 */ 711 @Override 712 public String getDomain() { 713 return (_domain != null ? _domain : "local"); 714 } 715 716 /** 717 * {@inheritDoc} 718 */ 719 @Override 720 public String getProtocol() { 721 return (_protocol != null ? _protocol : "tcp"); 722 } 723 724 /** 725 * {@inheritDoc} 726 */ 727 @Override 728 public String getSubtype() { 729 return (_subtype != null ? _subtype : ""); 730 } 731 732 /** 733 * {@inheritDoc} 734 */ 735 @Override 736 public Map<Fields, String> getQualifiedNameMap() { 737 Map<Fields, String> map = new HashMap<Fields, String>(5); 738 739 map.put(Fields.Domain, this.getDomain()); 740 map.put(Fields.Protocol, this.getProtocol()); 741 map.put(Fields.Application, this.getApplication()); 742 map.put(Fields.Instance, this.getName()); 743 map.put(Fields.Subtype, this.getSubtype()); 744 return map; 745 } 746 747 /** 748 * Write a UTF string with a length to a stream. 749 */ 750 static void writeUTF(OutputStream out, String str) throws IOException { 751 for (int i = 0, len = str.length(); i < len; i++) { 752 int c = str.charAt(i); 753 if ((c >= 0x0001) && (c <= 0x007F)) { 754 out.write(c); 755 } else { 756 if (c > 0x07FF) { 757 out.write(0xE0 | ((c >> 12) & 0x0F)); 758 out.write(0x80 | ((c >> 6) & 0x3F)); 759 out.write(0x80 | ((c >> 0) & 0x3F)); 760 } else { 761 out.write(0xC0 | ((c >> 6) & 0x1F)); 762 out.write(0x80 | ((c >> 0) & 0x3F)); 763 } 764 } 765 } 766 } 767 768 /** 769 * Read data bytes as a UTF stream. 770 */ 771 String readUTF(byte data[], int off, int len) { 772 int offset = off; 773 StringBuffer buf = new StringBuffer(); 774 for (int end = offset + len; offset < end;) { 775 int ch = data[offset++] & 0xFF; 776 switch (ch >> 4) { 777 case 0: 778 case 1: 779 case 2: 780 case 3: 781 case 4: 782 case 5: 783 case 6: 784 case 7: 785 // 0xxxxxxx 786 break; 787 case 12: 788 case 13: 789 if (offset >= len) { 790 return null; 791 } 792 // 110x xxxx 10xx xxxx 793 ch = ((ch & 0x1F) << 6) | (data[offset++] & 0x3F); 794 break; 795 case 14: 796 if (offset + 2 >= len) { 797 return null; 798 } 799 // 1110 xxxx 10xx xxxx 10xx xxxx 800 ch = ((ch & 0x0f) << 12) | ((data[offset++] & 0x3F) << 6) | (data[offset++] & 0x3F); 801 break; 802 default: 803 if (offset + 1 >= len) { 804 return null; 805 } 806 // 10xx xxxx, 1111 xxxx 807 ch = ((ch & 0x3F) << 4) | (data[offset++] & 0x0f); 808 break; 809 } 810 buf.append((char) ch); 811 } 812 return buf.toString(); 813 } 814 815 synchronized Map<String, byte[]> getProperties() { 816 if ((_props == null) && (this.getTextBytes() != null)) { 817 Hashtable<String, byte[]> properties = new Hashtable<String, byte[]>(); 818 try { 819 int off = 0; 820 while (off < getTextBytes().length) { 821 // length of the next key value pair 822 int len = getTextBytes()[off++] & 0xFF; 823 if ((len == 0) || (off + len > getTextBytes().length)) { 824 properties.clear(); 825 break; 826 } 827 // look for the '=' 828 int i = 0; 829 for (; (i < len) && (getTextBytes()[off + i] != '='); i++) { 830 /* Stub */ 831 } 832 833 // get the property name 834 String name = readUTF(getTextBytes(), off, i); 835 if (name == null) { 836 properties.clear(); 837 break; 838 } 839 if (i == len) { 840 properties.put(name, NO_VALUE); 841 } else { 842 byte value[] = new byte[len - ++i]; 843 System.arraycopy(getTextBytes(), off + i, value, 0, len - i); 844 properties.put(name, value); 845 off += len; 846 } 847 } 848 } catch (Exception exception) { 849 // We should get better logging. 850 logger.log(Level.WARNING, "Malformed TXT Field ", exception); 851 } 852 this._props = properties; 853 } 854 return (_props != null ? _props : Collections.<String, byte[]> emptyMap()); 855 } 856 857 /** 858 * JmDNS callback to update a DNS record. 859 * 860 * @param dnsCache 861 * @param now 862 * @param rec 863 */ 864 @Override 865 public void updateRecord(DNSCache dnsCache, long now, DNSEntry rec) { 866 if ((rec instanceof DNSRecord) && !rec.isExpired(now)) { 867 boolean serviceUpdated = false; 868 switch (rec.getRecordType()) { 869 case TYPE_A: // IPv4 870 if (rec.getName().equalsIgnoreCase(this.getServer())) { 871 _ipv4Addresses.add((Inet4Address) ((DNSRecord.Address) rec).getAddress()); 872 serviceUpdated = true; 873 } 874 break; 875 case TYPE_AAAA: // IPv6 876 if (rec.getName().equalsIgnoreCase(this.getServer())) { 877 _ipv6Addresses.add((Inet6Address) ((DNSRecord.Address) rec).getAddress()); 878 serviceUpdated = true; 879 } 880 break; 881 case TYPE_SRV: 882 if (rec.getName().equalsIgnoreCase(this.getQualifiedName())) { 883 DNSRecord.Service srv = (DNSRecord.Service) rec; 884 boolean serverChanged = (_server == null) || !_server.equalsIgnoreCase(srv.getServer()); 885 _server = srv.getServer(); 886 _port = srv.getPort(); 887 _weight = srv.getWeight(); 888 _priority = srv.getPriority(); 889 if (serverChanged) { 890 _ipv4Addresses.clear(); 891 _ipv6Addresses.clear(); 892 for (DNSEntry entry : dnsCache.getDNSEntryList(_server, DNSRecordType.TYPE_A, DNSRecordClass.CLASS_IN)) { 893 this.updateRecord(dnsCache, now, entry); 894 } 895 for (DNSEntry entry : dnsCache.getDNSEntryList(_server, DNSRecordType.TYPE_AAAA, DNSRecordClass.CLASS_IN)) { 896 this.updateRecord(dnsCache, now, entry); 897 } 898 // We do not want to trigger the listener in this case as it will be triggered if the address resolves. 899 } else { 900 serviceUpdated = true; 901 } 902 } 903 break; 904 case TYPE_TXT: 905 if (rec.getName().equalsIgnoreCase(this.getQualifiedName())) { 906 DNSRecord.Text txt = (DNSRecord.Text) rec; 907 _text = txt.getText(); 908 _props = null; // set it null for apply update text data 909 serviceUpdated = true; 910 } 911 break; 912 case TYPE_PTR: 913 if ((this.getSubtype().length() == 0) && (rec.getSubtype().length() != 0)) { 914 _subtype = rec.getSubtype(); 915 serviceUpdated = true; 916 } 917 break; 918 default: 919 break; 920 } 921 if (serviceUpdated && this.hasData()) { 922 JmDNSImpl dns = this.getDns(); 923 if (dns != null) { 924 ServiceEvent event = ((DNSRecord) rec).getServiceEvent(dns); 925 event = new ServiceEventImpl(dns, event.getType(), event.getName(), this); 926 dns.handleServiceResolved(event); 927 } 928 } 929 // This is done, to notify the wait loop in method JmDNS.waitForInfoData(ServiceInfo info, int timeout); 930 synchronized (this) { 931 this.notifyAll(); 932 } 933 } 934 } 935 936 /** 937 * Returns true if the service info is filled with data. 938 * 939 * @return <code>true</code> if the service info has data, <code>false</code> otherwise. 940 */ 941 @Override 942 public synchronized boolean hasData() { 943 return this.getServer() != null && this.hasInetAddress() && this.getTextBytes() != null && this.getTextBytes().length > 0; 944 // return this.getServer() != null && (this.getAddress() != null || (this.getTextBytes() != null && this.getTextBytes().length > 0)); 945 } 946 947 private final boolean hasInetAddress() { 948 return _ipv4Addresses.size() > 0 || _ipv6Addresses.size() > 0; 949 } 950 951 // State machine 952 953 /** 954 * {@inheritDoc} 955 */ 956 @Override 957 public boolean advanceState(DNSTask task) { 958 return _state.advanceState(task); 959 } 960 961 /** 962 * {@inheritDoc} 963 */ 964 @Override 965 public boolean revertState() { 966 return _state.revertState(); 967 } 968 969 /** 970 * {@inheritDoc} 971 */ 972 @Override 973 public boolean cancelState() { 974 return _state.cancelState(); 975 } 976 977 /** 978 * {@inheritDoc} 979 */ 980 @Override 981 public boolean closeState() { 982 return this._state.closeState(); 983 } 984 985 /** 986 * {@inheritDoc} 987 */ 988 @Override 989 public boolean recoverState() { 990 return this._state.recoverState(); 991 } 992 993 /** 994 * {@inheritDoc} 995 */ 996 @Override 997 public void removeAssociationWithTask(DNSTask task) { 998 _state.removeAssociationWithTask(task); 999 } 1000 1001 /** 1002 * {@inheritDoc} 1003 */ 1004 @Override 1005 public void associateWithTask(DNSTask task, DNSState state) { 1006 _state.associateWithTask(task, state); 1007 } 1008 1009 /** 1010 * {@inheritDoc} 1011 */ 1012 @Override 1013 public boolean isAssociatedWithTask(DNSTask task, DNSState state) { 1014 return _state.isAssociatedWithTask(task, state); 1015 } 1016 1017 /** 1018 * {@inheritDoc} 1019 */ 1020 @Override 1021 public boolean isProbing() { 1022 return _state.isProbing(); 1023 } 1024 1025 /** 1026 * {@inheritDoc} 1027 */ 1028 @Override 1029 public boolean isAnnouncing() { 1030 return _state.isAnnouncing(); 1031 } 1032 1033 /** 1034 * {@inheritDoc} 1035 */ 1036 @Override 1037 public boolean isAnnounced() { 1038 return _state.isAnnounced(); 1039 } 1040 1041 /** 1042 * {@inheritDoc} 1043 */ 1044 @Override 1045 public boolean isCanceling() { 1046 return this._state.isCanceling(); 1047 } 1048 1049 /** 1050 * {@inheritDoc} 1051 */ 1052 @Override 1053 public boolean isCanceled() { 1054 return _state.isCanceled(); 1055 } 1056 1057 /** 1058 * {@inheritDoc} 1059 */ 1060 @Override 1061 public boolean isClosing() { 1062 return _state.isClosing(); 1063 } 1064 1065 /** 1066 * {@inheritDoc} 1067 */ 1068 @Override 1069 public boolean isClosed() { 1070 return _state.isClosed(); 1071 } 1072 1073 /** 1074 * {@inheritDoc} 1075 */ 1076 @Override 1077 public boolean waitForAnnounced(long timeout) { 1078 return _state.waitForAnnounced(timeout); 1079 } 1080 1081 /** 1082 * {@inheritDoc} 1083 */ 1084 @Override 1085 public boolean waitForCanceled(long timeout) { 1086 return _state.waitForCanceled(timeout); 1087 } 1088 1089 /** 1090 * {@inheritDoc} 1091 */ 1092 @Override 1093 public int hashCode() { 1094 return getQualifiedName().hashCode(); 1095 } 1096 1097 /** 1098 * {@inheritDoc} 1099 */ 1100 @Override 1101 public boolean equals(Object obj) { 1102 return (obj instanceof ServiceInfoImpl) && getQualifiedName().equals(((ServiceInfoImpl) obj).getQualifiedName()); 1103 } 1104 1105 /** 1106 * {@inheritDoc} 1107 */ 1108 @Override 1109 public String getNiceTextString() { 1110 StringBuffer buf = new StringBuffer(); 1111 for (int i = 0, len = this.getTextBytes().length; i < len; i++) { 1112 if (i >= 200) { 1113 buf.append("..."); 1114 break; 1115 } 1116 int ch = getTextBytes()[i] & 0xFF; 1117 if ((ch < ' ') || (ch > 127)) { 1118 buf.append("\\0"); 1119 buf.append(Integer.toString(ch, 8)); 1120 } else { 1121 buf.append((char) ch); 1122 } 1123 } 1124 return buf.toString(); 1125 } 1126 1127 /* 1128 * (non-Javadoc) 1129 * @see javax.jmdns.ServiceInfo#clone() 1130 */ 1131 @Override 1132 public ServiceInfoImpl clone() { 1133 ServiceInfoImpl serviceInfo = new ServiceInfoImpl(this.getQualifiedNameMap(), _port, _weight, _priority, _persistent, _text); 1134 Inet6Address[] ipv6Addresses = this.getInet6Addresses(); 1135 for (Inet6Address address : ipv6Addresses) { 1136 serviceInfo._ipv6Addresses.add(address); 1137 } 1138 Inet4Address[] ipv4Addresses = this.getInet4Addresses(); 1139 for (Inet4Address address : ipv4Addresses) { 1140 serviceInfo._ipv4Addresses.add(address); 1141 } 1142 return serviceInfo; 1143 } 1144 1145 /** 1146 * {@inheritDoc} 1147 */ 1148 @Override 1149 public String toString() { 1150 StringBuilder buf = new StringBuilder(); 1151 buf.append("[" + this.getClass().getSimpleName() + "@" + System.identityHashCode(this) + " "); 1152 buf.append("name: '"); 1153 buf.append((this.getName().length() > 0 ? this.getName() + "." : "") + this.getTypeWithSubtype()); 1154 buf.append("' address: '"); 1155 InetAddress[] addresses = this.getInetAddresses(); 1156 if (addresses.length > 0) { 1157 for (InetAddress address : addresses) { 1158 buf.append(address); 1159 buf.append(':'); 1160 buf.append(this.getPort()); 1161 buf.append(' '); 1162 } 1163 } else { 1164 buf.append("(null):"); 1165 buf.append(this.getPort()); 1166 } 1167 buf.append("' status: '"); 1168 buf.append(_state.toString()); 1169 buf.append(this.isPersistent() ? "' is persistent," : "',"); 1170 buf.append(" has "); 1171 buf.append(this.hasData() ? "" : "NO "); 1172 buf.append("data"); 1173 if (this.getTextBytes().length > 0) { 1174 // buf.append("\n"); 1175 // buf.append(this.getNiceTextString()); 1176 Map<String, byte[]> properties = this.getProperties(); 1177 if (!properties.isEmpty()) { 1178 buf.append("\n"); 1179 for (String key : properties.keySet()) { 1180 buf.append("\t" + key + ": " + new String(properties.get(key)) + "\n"); 1181 } 1182 } else { 1183 buf.append(" empty"); 1184 } 1185 } 1186 buf.append(']'); 1187 return buf.toString(); 1188 } 1189 1190 public Collection<DNSRecord> answers(boolean unique, int ttl, HostInfo localHost) { 1191 List<DNSRecord> list = new ArrayList<DNSRecord>(); 1192 if (this.getSubtype().length() > 0) { 1193 list.add(new Pointer(this.getTypeWithSubtype(), DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, ttl, this.getQualifiedName())); 1194 } 1195 list.add(new Pointer(this.getType(), DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, ttl, this.getQualifiedName())); 1196 list.add(new Service(this.getQualifiedName(), DNSRecordClass.CLASS_IN, unique, ttl, _priority, _weight, _port, localHost.getName())); 1197 list.add(new Text(this.getQualifiedName(), DNSRecordClass.CLASS_IN, unique, ttl, this.getTextBytes())); 1198 return list; 1199 } 1200 1201 /** 1202 * {@inheritDoc} 1203 */ 1204 @Override 1205 public void setText(byte[] text) throws IllegalStateException { 1206 synchronized (this) { 1207 this._text = text; 1208 this._props = null; 1209 this.setNeedTextAnnouncing(true); 1210 } 1211 } 1212 1213 /** 1214 * {@inheritDoc} 1215 */ 1216 @Override 1217 public void setText(Map<String, ?> props) throws IllegalStateException { 1218 this.setText(textFromProperties(props)); 1219 } 1220 1221 /** 1222 * This is used internally by the framework 1223 * 1224 * @param text 1225 */ 1226 void _setText(byte[] text) { 1227 this._text = text; 1228 this._props = null; 1229 } 1230 1231 private static byte[] textFromProperties(Map<String, ?> props) { 1232 byte[] text = null; 1233 if (props != null) { 1234 try { 1235 ByteArrayOutputStream out = new ByteArrayOutputStream(256); 1236 for (String key : props.keySet()) { 1237 Object val = props.get(key); 1238 ByteArrayOutputStream out2 = new ByteArrayOutputStream(100); 1239 writeUTF(out2, key); 1240 if (val == null) { 1241 // Skip 1242 } else if (val instanceof String) { 1243 out2.write('='); 1244 writeUTF(out2, (String) val); 1245 } else if (val instanceof byte[]) { 1246 byte[] bval = (byte[]) val; 1247 if (bval.length > 0) { 1248 out2.write('='); 1249 out2.write(bval, 0, bval.length); 1250 } else { 1251 val = null; 1252 } 1253 } else { 1254 throw new IllegalArgumentException("invalid property value: " + val); 1255 } 1256 byte data[] = out2.toByteArray(); 1257 if (data.length > 255) { 1258 throw new IOException("Cannot have individual values larger that 255 chars. Offending value: " + key + (val != null ? "" : "=" + val)); 1259 } 1260 out.write((byte) data.length); 1261 out.write(data, 0, data.length); 1262 } 1263 text = out.toByteArray(); 1264 } catch (IOException e) { 1265 throw new RuntimeException("unexpected exception: " + e); 1266 } 1267 } 1268 return (text != null && text.length > 0 ? text : DNSRecord.EMPTY_TXT); 1269 } 1270 1271 public void setDns(JmDNSImpl dns) { 1272 this._state.setDns(dns); 1273 } 1274 1275 /** 1276 * {@inheritDoc} 1277 */ 1278 @Override 1279 public JmDNSImpl getDns() { 1280 return this._state.getDns(); 1281 } 1282 1283 /** 1284 * {@inheritDoc} 1285 */ 1286 @Override 1287 public boolean isPersistent() { 1288 return _persistent; 1289 } 1290 1291 /** 1292 * @param needTextAnnouncing 1293 * the needTextAnnouncing to set 1294 */ 1295 public void setNeedTextAnnouncing(boolean needTextAnnouncing) { 1296 this._needTextAnnouncing = needTextAnnouncing; 1297 if (this._needTextAnnouncing) { 1298 _state.setTask(null); 1299 } 1300 } 1301 1302 /** 1303 * @return the needTextAnnouncing 1304 */ 1305 public boolean needTextAnnouncing() { 1306 return _needTextAnnouncing; 1307 } 1308 1309 /** 1310 * @return the delegate 1311 */ 1312 Delegate getDelegate() { 1313 return this._delegate; 1314 } 1315 1316 /** 1317 * @param delegate 1318 * the delegate to set 1319 */ 1320 void setDelegate(Delegate delegate) { 1321 this._delegate = delegate; 1322 } 1323 1324 } 1325