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 textValueUpdated(ServiceInfo target, byte[] value)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 */ ServiceInfoState(ServiceInfoImpl info)83 public ServiceInfoState(ServiceInfoImpl info) { 84 super(); 85 _info = info; 86 } 87 88 @Override setTask(DNSTask task)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 setDns(JmDNSImpl dns)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 */ ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, String text)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 */ ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, Map<String, ?> props)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 */ ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, byte text[])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 ServiceInfoImpl(Map<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, Map<String, ?> props)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 ServiceInfoImpl(Map<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, String text)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 ServiceInfoImpl(Map<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, byte text[])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 */ ServiceInfoImpl(ServiceInfo info)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 decodeQualifiedNameMap(String type, String name, String subtype)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 decodeQualifiedNameMapForType(String type)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 checkQualifiedNameMap(Map<Fields, String> qualifiedNameMap)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 removeSeparators(String name)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 getType()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 getTypeWithSubtype()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 getName()396 public String getName() { 397 return (_name != null ? _name : ""); 398 } 399 400 /** 401 * {@inheritDoc} 402 */ 403 @Override getKey()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 */ setName(String name)417 void setName(String name) { 418 this._name = name; 419 this._key = null; 420 } 421 422 /** 423 * {@inheritDoc} 424 */ 425 @Override getQualifiedName()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 getServer()441 public String getServer() { 442 return (_server != null ? _server : ""); 443 } 444 445 /** 446 * @param server 447 * the server to set 448 */ setServer(String server)449 void setServer(String server) { 450 this._server = server; 451 } 452 453 /** 454 * {@inheritDoc} 455 */ 456 @Deprecated 457 @Override getHostAddress()458 public String getHostAddress() { 459 String[] names = this.getHostAddresses(); 460 return (names.length > 0 ? names[0] : ""); 461 } 462 463 /** 464 * {@inheritDoc} 465 */ 466 @Override getHostAddresses()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 */ addAddress(Inet4Address addr)484 void addAddress(Inet4Address addr) { 485 _ipv4Addresses.add(addr); 486 } 487 488 /** 489 * @param addr 490 * the addr to add 491 */ addAddress(Inet6Address addr)492 void addAddress(Inet6Address addr) { 493 _ipv6Addresses.add(addr); 494 } 495 496 /** 497 * {@inheritDoc} 498 */ 499 @Deprecated 500 @Override getAddress()501 public InetAddress getAddress() { 502 return this.getInetAddress(); 503 } 504 505 /** 506 * {@inheritDoc} 507 */ 508 @Deprecated 509 @Override getInetAddress()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 getInet4Address()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 getInet6Address()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 getInetAddresses()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 getInet4Addresses()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 getInet6Addresses()561 public Inet6Address[] getInet6Addresses() { 562 return _ipv6Addresses.toArray(new Inet6Address[_ipv6Addresses.size()]); 563 } 564 565 /** 566 * @see javax.jmdns.ServiceInfo#getPort() 567 */ 568 @Override getPort()569 public int getPort() { 570 return _port; 571 } 572 573 /** 574 * @see javax.jmdns.ServiceInfo#getPriority() 575 */ 576 @Override getPriority()577 public int getPriority() { 578 return _priority; 579 } 580 581 /** 582 * @see javax.jmdns.ServiceInfo#getWeight() 583 */ 584 @Override getWeight()585 public int getWeight() { 586 return _weight; 587 } 588 589 /** 590 * @see javax.jmdns.ServiceInfo#getTextBytes() 591 */ 592 @Override getTextBytes()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 getTextString()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 getURL()620 public String getURL() { 621 return this.getURL("http"); 622 } 623 624 /* 625 * (non-Javadoc) 626 * @see javax.jmdns.ServiceInfo#getURLs() 627 */ 628 @Override getURLs()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 getURL(String protocol)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 getURLs(String protocol)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 getPropertyBytes(String name)671 public synchronized byte[] getPropertyBytes(String name) { 672 return this.getProperties().get(name); 673 } 674 675 /** 676 * {@inheritDoc} 677 */ 678 @Override getPropertyString(String name)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 getPropertyNames()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 getApplication()704 public String getApplication() { 705 return (_application != null ? _application : ""); 706 } 707 708 /** 709 * {@inheritDoc} 710 */ 711 @Override getDomain()712 public String getDomain() { 713 return (_domain != null ? _domain : "local"); 714 } 715 716 /** 717 * {@inheritDoc} 718 */ 719 @Override getProtocol()720 public String getProtocol() { 721 return (_protocol != null ? _protocol : "tcp"); 722 } 723 724 /** 725 * {@inheritDoc} 726 */ 727 @Override getSubtype()728 public String getSubtype() { 729 return (_subtype != null ? _subtype : ""); 730 } 731 732 /** 733 * {@inheritDoc} 734 */ 735 @Override getQualifiedNameMap()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 */ writeUTF(OutputStream out, String str)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 */ readUTF(byte data[], int off, int len)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 getProperties()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 updateRecord(DNSCache dnsCache, long now, DNSEntry rec)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 hasData()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 hasInetAddress()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 advanceState(DNSTask task)957 public boolean advanceState(DNSTask task) { 958 return _state.advanceState(task); 959 } 960 961 /** 962 * {@inheritDoc} 963 */ 964 @Override revertState()965 public boolean revertState() { 966 return _state.revertState(); 967 } 968 969 /** 970 * {@inheritDoc} 971 */ 972 @Override cancelState()973 public boolean cancelState() { 974 return _state.cancelState(); 975 } 976 977 /** 978 * {@inheritDoc} 979 */ 980 @Override closeState()981 public boolean closeState() { 982 return this._state.closeState(); 983 } 984 985 /** 986 * {@inheritDoc} 987 */ 988 @Override recoverState()989 public boolean recoverState() { 990 return this._state.recoverState(); 991 } 992 993 /** 994 * {@inheritDoc} 995 */ 996 @Override removeAssociationWithTask(DNSTask task)997 public void removeAssociationWithTask(DNSTask task) { 998 _state.removeAssociationWithTask(task); 999 } 1000 1001 /** 1002 * {@inheritDoc} 1003 */ 1004 @Override associateWithTask(DNSTask task, DNSState state)1005 public void associateWithTask(DNSTask task, DNSState state) { 1006 _state.associateWithTask(task, state); 1007 } 1008 1009 /** 1010 * {@inheritDoc} 1011 */ 1012 @Override isAssociatedWithTask(DNSTask task, DNSState state)1013 public boolean isAssociatedWithTask(DNSTask task, DNSState state) { 1014 return _state.isAssociatedWithTask(task, state); 1015 } 1016 1017 /** 1018 * {@inheritDoc} 1019 */ 1020 @Override isProbing()1021 public boolean isProbing() { 1022 return _state.isProbing(); 1023 } 1024 1025 /** 1026 * {@inheritDoc} 1027 */ 1028 @Override isAnnouncing()1029 public boolean isAnnouncing() { 1030 return _state.isAnnouncing(); 1031 } 1032 1033 /** 1034 * {@inheritDoc} 1035 */ 1036 @Override isAnnounced()1037 public boolean isAnnounced() { 1038 return _state.isAnnounced(); 1039 } 1040 1041 /** 1042 * {@inheritDoc} 1043 */ 1044 @Override isCanceling()1045 public boolean isCanceling() { 1046 return this._state.isCanceling(); 1047 } 1048 1049 /** 1050 * {@inheritDoc} 1051 */ 1052 @Override isCanceled()1053 public boolean isCanceled() { 1054 return _state.isCanceled(); 1055 } 1056 1057 /** 1058 * {@inheritDoc} 1059 */ 1060 @Override isClosing()1061 public boolean isClosing() { 1062 return _state.isClosing(); 1063 } 1064 1065 /** 1066 * {@inheritDoc} 1067 */ 1068 @Override isClosed()1069 public boolean isClosed() { 1070 return _state.isClosed(); 1071 } 1072 1073 /** 1074 * {@inheritDoc} 1075 */ 1076 @Override waitForAnnounced(long timeout)1077 public boolean waitForAnnounced(long timeout) { 1078 return _state.waitForAnnounced(timeout); 1079 } 1080 1081 /** 1082 * {@inheritDoc} 1083 */ 1084 @Override waitForCanceled(long timeout)1085 public boolean waitForCanceled(long timeout) { 1086 return _state.waitForCanceled(timeout); 1087 } 1088 1089 /** 1090 * {@inheritDoc} 1091 */ 1092 @Override hashCode()1093 public int hashCode() { 1094 return getQualifiedName().hashCode(); 1095 } 1096 1097 /** 1098 * {@inheritDoc} 1099 */ 1100 @Override equals(Object obj)1101 public boolean equals(Object obj) { 1102 return (obj instanceof ServiceInfoImpl) && getQualifiedName().equals(((ServiceInfoImpl) obj).getQualifiedName()); 1103 } 1104 1105 /** 1106 * {@inheritDoc} 1107 */ 1108 @Override getNiceTextString()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 clone()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 toString()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 answers(boolean unique, int ttl, HostInfo localHost)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 setText(byte[] text)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 setText(Map<String, ?> props)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 */ _setText(byte[] text)1226 void _setText(byte[] text) { 1227 this._text = text; 1228 this._props = null; 1229 } 1230 textFromProperties(Map<String, ?> props)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 setDns(JmDNSImpl dns)1271 public void setDns(JmDNSImpl dns) { 1272 this._state.setDns(dns); 1273 } 1274 1275 /** 1276 * {@inheritDoc} 1277 */ 1278 @Override getDns()1279 public JmDNSImpl getDns() { 1280 return this._state.getDns(); 1281 } 1282 1283 /** 1284 * {@inheritDoc} 1285 */ 1286 @Override isPersistent()1287 public boolean isPersistent() { 1288 return _persistent; 1289 } 1290 1291 /** 1292 * @param needTextAnnouncing 1293 * the needTextAnnouncing to set 1294 */ setNeedTextAnnouncing(boolean needTextAnnouncing)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 */ needTextAnnouncing()1305 public boolean needTextAnnouncing() { 1306 return _needTextAnnouncing; 1307 } 1308 1309 /** 1310 * @return the delegate 1311 */ getDelegate()1312 Delegate getDelegate() { 1313 return this._delegate; 1314 } 1315 1316 /** 1317 * @param delegate 1318 * the delegate to set 1319 */ setDelegate(Delegate delegate)1320 void setDelegate(Delegate delegate) { 1321 this._delegate = delegate; 1322 } 1323 1324 } 1325