$search
00001 // Copyright 2003-2005 Arthur van Hoff, Rick Blair 00002 // Licensed under Apache License version 2.0 00003 // Original license LGPL 00004 00005 package javax.jmdns.impl; 00006 00007 import java.io.ByteArrayOutputStream; 00008 import java.io.IOException; 00009 import java.io.OutputStream; 00010 import java.net.Inet4Address; 00011 import java.net.Inet6Address; 00012 import java.net.InetAddress; 00013 import java.util.ArrayList; 00014 import java.util.Collection; 00015 import java.util.Collections; 00016 import java.util.Enumeration; 00017 import java.util.HashMap; 00018 import java.util.Hashtable; 00019 import java.util.LinkedHashSet; 00020 import java.util.List; 00021 import java.util.Map; 00022 import java.util.Set; 00023 import java.util.Vector; 00024 import java.util.logging.Level; 00025 import java.util.logging.Logger; 00026 00027 import javax.jmdns.ServiceEvent; 00028 import javax.jmdns.ServiceInfo; 00029 import javax.jmdns.impl.DNSRecord.Pointer; 00030 import javax.jmdns.impl.DNSRecord.Service; 00031 import javax.jmdns.impl.DNSRecord.Text; 00032 import javax.jmdns.impl.constants.DNSRecordClass; 00033 import javax.jmdns.impl.constants.DNSRecordType; 00034 import javax.jmdns.impl.constants.DNSState; 00035 import javax.jmdns.impl.tasks.DNSTask; 00036 00042 public class ServiceInfoImpl extends ServiceInfo implements DNSListener, DNSStatefulObject { 00043 private static Logger logger = Logger.getLogger(ServiceInfoImpl.class.getName()); 00044 00045 private String _domain; 00046 private String _protocol; 00047 private String _application; 00048 private String _name; 00049 private String _subtype; 00050 private String _server; 00051 private int _port; 00052 private int _weight; 00053 private int _priority; 00054 private byte _text[]; 00055 private Map<String, byte[]> _props; 00056 private final Set<Inet4Address> _ipv4Addresses; 00057 private final Set<Inet6Address> _ipv6Addresses; 00058 00059 private transient String _key; 00060 00061 private boolean _persistent; 00062 private boolean _needTextAnnouncing; 00063 00064 private final ServiceInfoState _state; 00065 00066 private Delegate _delegate; 00067 00068 public static interface Delegate { 00069 00070 public void textValueUpdated(ServiceInfo target, byte[] value); 00071 00072 } 00073 00074 private final static class ServiceInfoState extends DNSStatefulObject.DefaultImplementation { 00075 00076 private static final long serialVersionUID = 1104131034952196820L; 00077 00078 private final ServiceInfoImpl _info; 00079 00083 public ServiceInfoState(ServiceInfoImpl info) { 00084 super(); 00085 _info = info; 00086 } 00087 00088 @Override 00089 protected void setTask(DNSTask task) { 00090 super.setTask(task); 00091 if ((this._task == null) && _info.needTextAnnouncing()) { 00092 this.lock(); 00093 try { 00094 if ((this._task == null) && _info.needTextAnnouncing()) { 00095 if (this._state.isAnnounced()) { 00096 this.setState(DNSState.ANNOUNCING_1); 00097 if (this.getDns() != null) { 00098 this.getDns().startAnnouncer(); 00099 } 00100 } 00101 _info.setNeedTextAnnouncing(false); 00102 } 00103 } finally { 00104 this.unlock(); 00105 } 00106 } 00107 } 00108 00109 @Override 00110 public void setDns(JmDNSImpl dns) { 00111 super.setDns(dns); 00112 } 00113 00114 } 00115 00127 public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, String text) { 00128 this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, (byte[]) null); 00129 _server = text; 00130 try { 00131 ByteArrayOutputStream out = new ByteArrayOutputStream(text.length()); 00132 writeUTF(out, text); 00133 this._text = out.toByteArray(); 00134 } catch (IOException e) { 00135 throw new RuntimeException("unexpected exception: " + e); 00136 } 00137 } 00138 00150 public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, Map<String, ?> props) { 00151 this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, textFromProperties(props)); 00152 } 00153 00165 public ServiceInfoImpl(String type, String name, String subtype, int port, int weight, int priority, boolean persistent, byte text[]) { 00166 this(ServiceInfoImpl.decodeQualifiedNameMap(type, name, subtype), port, weight, priority, persistent, text); 00167 } 00168 00169 public ServiceInfoImpl(Map<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, Map<String, ?> props) { 00170 this(qualifiedNameMap, port, weight, priority, persistent, textFromProperties(props)); 00171 } 00172 00173 ServiceInfoImpl(Map<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, String text) { 00174 this(qualifiedNameMap, port, weight, priority, persistent, (byte[]) null); 00175 _server = text; 00176 try { 00177 ByteArrayOutputStream out = new ByteArrayOutputStream(text.length()); 00178 writeUTF(out, text); 00179 this._text = out.toByteArray(); 00180 } catch (IOException e) { 00181 throw new RuntimeException("unexpected exception: " + e); 00182 } 00183 } 00184 00185 ServiceInfoImpl(Map<Fields, String> qualifiedNameMap, int port, int weight, int priority, boolean persistent, byte text[]) { 00186 Map<Fields, String> map = ServiceInfoImpl.checkQualifiedNameMap(qualifiedNameMap); 00187 00188 this._domain = map.get(Fields.Domain); 00189 this._protocol = map.get(Fields.Protocol); 00190 this._application = map.get(Fields.Application); 00191 this._name = map.get(Fields.Instance); 00192 this._subtype = map.get(Fields.Subtype); 00193 00194 this._port = port; 00195 this._weight = weight; 00196 this._priority = priority; 00197 this._text = text; 00198 this.setNeedTextAnnouncing(false); 00199 this._state = new ServiceInfoState(this); 00200 this._persistent = persistent; 00201 this._ipv4Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet4Address>()); 00202 this._ipv6Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet6Address>()); 00203 } 00204 00210 ServiceInfoImpl(ServiceInfo info) { 00211 this._ipv4Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet4Address>()); 00212 this._ipv6Addresses = Collections.synchronizedSet(new LinkedHashSet<Inet6Address>()); 00213 if (info != null) { 00214 this._domain = info.getDomain(); 00215 this._protocol = info.getProtocol(); 00216 this._application = info.getApplication(); 00217 this._name = info.getName(); 00218 this._subtype = info.getSubtype(); 00219 this._port = info.getPort(); 00220 this._weight = info.getWeight(); 00221 this._priority = info.getPriority(); 00222 this._text = info.getTextBytes(); 00223 this._persistent = info.isPersistent(); 00224 Inet6Address[] ipv6Addresses = info.getInet6Addresses(); 00225 for (Inet6Address address : ipv6Addresses) { 00226 this._ipv6Addresses.add(address); 00227 } 00228 Inet4Address[] ipv4Addresses = info.getInet4Addresses(); 00229 for (Inet4Address address : ipv4Addresses) { 00230 this._ipv4Addresses.add(address); 00231 } 00232 } 00233 this._state = new ServiceInfoState(this); 00234 } 00235 00236 public static Map<Fields, String> decodeQualifiedNameMap(String type, String name, String subtype) { 00237 Map<Fields, String> qualifiedNameMap = decodeQualifiedNameMapForType(type); 00238 00239 qualifiedNameMap.put(Fields.Instance, name); 00240 qualifiedNameMap.put(Fields.Subtype, subtype); 00241 00242 return checkQualifiedNameMap(qualifiedNameMap); 00243 } 00244 00245 public static Map<Fields, String> decodeQualifiedNameMapForType(String type) { 00246 int index; 00247 00248 String casePreservedType = type; 00249 00250 String aType = type.toLowerCase(); 00251 String application = aType; 00252 String protocol = ""; 00253 String subtype = ""; 00254 String name = ""; 00255 String domain = ""; 00256 00257 if (aType.contains("in-addr.arpa") || aType.contains("ip6.arpa")) { 00258 index = (aType.contains("in-addr.arpa") ? aType.indexOf("in-addr.arpa") : aType.indexOf("ip6.arpa")); 00259 name = removeSeparators(casePreservedType.substring(0, index)); 00260 domain = casePreservedType.substring(index); 00261 application = ""; 00262 } else if ((!aType.contains("_")) && aType.contains(".")) { 00263 index = aType.indexOf('.'); 00264 name = removeSeparators(casePreservedType.substring(0, index)); 00265 domain = removeSeparators(casePreservedType.substring(index)); 00266 application = ""; 00267 } else { 00268 // First remove the name if it there. 00269 if (!aType.startsWith("_") || aType.startsWith("_services")) { 00270 index = aType.indexOf('.'); 00271 if (index > 0) { 00272 // We need to preserve the case for the user readable name. 00273 name = casePreservedType.substring(0, index); 00274 if (index + 1 < aType.length()) { 00275 aType = aType.substring(index + 1); 00276 casePreservedType = casePreservedType.substring(index + 1); 00277 } 00278 } 00279 } 00280 00281 index = aType.lastIndexOf("._"); 00282 if (index > 0) { 00283 int start = index + 2; 00284 int end = aType.indexOf('.', start); 00285 protocol = casePreservedType.substring(start, end); 00286 } 00287 if (protocol.length() > 0) { 00288 index = aType.indexOf("_" + protocol.toLowerCase() + "."); 00289 int start = index + protocol.length() + 2; 00290 int end = aType.length() - (aType.endsWith(".") ? 1 : 0); 00291 if (end > start) { 00292 domain = casePreservedType.substring(start, end); 00293 } 00294 if (index > 0) { 00295 application = casePreservedType.substring(0, index - 1); 00296 } else { 00297 application = ""; 00298 } 00299 } 00300 index = application.toLowerCase().indexOf("._sub"); 00301 if (index > 0) { 00302 int start = index + 5; 00303 subtype = removeSeparators(application.substring(0, index)); 00304 application = application.substring(start); 00305 } 00306 } 00307 00308 final Map<Fields, String> qualifiedNameMap = new HashMap<Fields, String>(5); 00309 qualifiedNameMap.put(Fields.Domain, removeSeparators(domain)); 00310 qualifiedNameMap.put(Fields.Protocol, protocol); 00311 qualifiedNameMap.put(Fields.Application, removeSeparators(application)); 00312 qualifiedNameMap.put(Fields.Instance, name); 00313 qualifiedNameMap.put(Fields.Subtype, subtype); 00314 00315 return qualifiedNameMap; 00316 } 00317 00318 protected static Map<Fields, String> checkQualifiedNameMap(Map<Fields, String> qualifiedNameMap) { 00319 Map<Fields, String> checkedQualifiedNameMap = new HashMap<Fields, String>(5); 00320 00321 // Optional domain 00322 String domain = (qualifiedNameMap.containsKey(Fields.Domain) ? qualifiedNameMap.get(Fields.Domain) : "local"); 00323 if ((domain == null) || (domain.length() == 0)) { 00324 domain = "local"; 00325 } 00326 domain = removeSeparators(domain); 00327 checkedQualifiedNameMap.put(Fields.Domain, domain); 00328 // Optional protocol 00329 String protocol = (qualifiedNameMap.containsKey(Fields.Protocol) ? qualifiedNameMap.get(Fields.Protocol) : "tcp"); 00330 if ((protocol == null) || (protocol.length() == 0)) { 00331 protocol = "tcp"; 00332 } 00333 protocol = removeSeparators(protocol); 00334 checkedQualifiedNameMap.put(Fields.Protocol, protocol); 00335 // Application 00336 String application = (qualifiedNameMap.containsKey(Fields.Application) ? qualifiedNameMap.get(Fields.Application) : ""); 00337 if ((application == null) || (application.length() == 0)) { 00338 application = ""; 00339 } 00340 application = removeSeparators(application); 00341 checkedQualifiedNameMap.put(Fields.Application, application); 00342 // Instance 00343 String instance = (qualifiedNameMap.containsKey(Fields.Instance) ? qualifiedNameMap.get(Fields.Instance) : ""); 00344 if ((instance == null) || (instance.length() == 0)) { 00345 instance = ""; 00346 // throw new IllegalArgumentException("The instance name component of a fully qualified service cannot be empty."); 00347 } 00348 instance = removeSeparators(instance); 00349 checkedQualifiedNameMap.put(Fields.Instance, instance); 00350 // Optional Subtype 00351 String subtype = (qualifiedNameMap.containsKey(Fields.Subtype) ? qualifiedNameMap.get(Fields.Subtype) : ""); 00352 if ((subtype == null) || (subtype.length() == 0)) { 00353 subtype = ""; 00354 } 00355 subtype = removeSeparators(subtype); 00356 checkedQualifiedNameMap.put(Fields.Subtype, subtype); 00357 00358 return checkedQualifiedNameMap; 00359 } 00360 00361 private static String removeSeparators(String name) { 00362 if (name == null) { 00363 return ""; 00364 } 00365 String newName = name.trim(); 00366 if (newName.startsWith(".")) { 00367 newName = newName.substring(1); 00368 } 00369 if (newName.startsWith("_")) { 00370 newName = newName.substring(1); 00371 } 00372 if (newName.endsWith(".")) { 00373 newName = newName.substring(0, newName.length() - 1); 00374 } 00375 return newName; 00376 } 00377 00381 @Override 00382 public String getType() { 00383 String domain = this.getDomain(); 00384 String protocol = this.getProtocol(); 00385 String application = this.getApplication(); 00386 return (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; 00387 } 00388 00392 @Override 00393 public String getTypeWithSubtype() { 00394 String subtype = this.getSubtype(); 00395 return (subtype.length() > 0 ? "_" + subtype.toLowerCase() + "._sub." : "") + this.getType(); 00396 } 00397 00401 @Override 00402 public String getName() { 00403 return (_name != null ? _name : ""); 00404 } 00405 00409 @Override 00410 public String getKey() { 00411 if (this._key == null) { 00412 this._key = this.getQualifiedName().toLowerCase(); 00413 } 00414 return this._key; 00415 } 00416 00423 void setName(String name) { 00424 this._name = name; 00425 this._key = null; 00426 } 00427 00431 @Override 00432 public String getQualifiedName() { 00433 String domain = this.getDomain(); 00434 String protocol = this.getProtocol(); 00435 String application = this.getApplication(); 00436 String instance = this.getName(); 00437 // String subtype = this.getSubtype(); 00438 // return (instance.length() > 0 ? instance + "." : "") + (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + (subtype.length() > 0 ? ",_" + subtype.toLowerCase() + "." : ".") : "") + domain 00439 // + "."; 00440 return (instance.length() > 0 ? instance + "." : "") + (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; 00441 } 00442 00446 @Override 00447 public String getServer() { 00448 return (_server != null ? _server : ""); 00449 } 00450 00455 void setServer(String server) { 00456 this._server = server; 00457 } 00458 00462 @Deprecated 00463 @Override 00464 public String getHostAddress() { 00465 String[] names = this.getHostAddresses(); 00466 return (names.length > 0 ? names[0] : ""); 00467 } 00468 00472 @Override 00473 public String[] getHostAddresses() { 00474 Inet4Address[] ip4Aaddresses = this.getInet4Addresses(); 00475 Inet6Address[] ip6Aaddresses = this.getInet6Addresses(); 00476 String[] names = new String[ip4Aaddresses.length + ip6Aaddresses.length]; 00477 for (int i = 0; i < ip4Aaddresses.length; i++) { 00478 names[i] = ip4Aaddresses[i].getHostAddress(); 00479 } 00480 for (int i = 0; i < ip6Aaddresses.length; i++) { 00481 names[i + ip4Aaddresses.length] = "[" + ip6Aaddresses[i].getHostAddress() + "]"; 00482 } 00483 return names; 00484 } 00485 00490 void addAddress(Inet4Address addr) { 00491 _ipv4Addresses.add(addr); 00492 } 00493 00498 void addAddress(Inet6Address addr) { 00499 _ipv6Addresses.add(addr); 00500 } 00501 00505 @Deprecated 00506 @Override 00507 public InetAddress getAddress() { 00508 return this.getInetAddress(); 00509 } 00510 00514 @Deprecated 00515 @Override 00516 public InetAddress getInetAddress() { 00517 InetAddress[] addresses = this.getInetAddresses(); 00518 return (addresses.length > 0 ? addresses[0] : null); 00519 } 00520 00524 @Deprecated 00525 @Override 00526 public Inet4Address getInet4Address() { 00527 Inet4Address[] addresses = this.getInet4Addresses(); 00528 return (addresses.length > 0 ? addresses[0] : null); 00529 } 00530 00534 @Deprecated 00535 @Override 00536 public Inet6Address getInet6Address() { 00537 Inet6Address[] addresses = this.getInet6Addresses(); 00538 return (addresses.length > 0 ? addresses[0] : null); 00539 } 00540 00541 /* 00542 * (non-Javadoc) 00543 * @see javax.jmdns.ServiceInfo#getInetAddresses() 00544 */ 00545 @Override 00546 public InetAddress[] getInetAddresses() { 00547 List<InetAddress> aList = new ArrayList<InetAddress>(_ipv4Addresses.size() + _ipv6Addresses.size()); 00548 aList.addAll(_ipv4Addresses); 00549 aList.addAll(_ipv6Addresses); 00550 return aList.toArray(new InetAddress[aList.size()]); 00551 } 00552 00553 /* 00554 * (non-Javadoc) 00555 * @see javax.jmdns.ServiceInfo#getInet4Addresses() 00556 */ 00557 @Override 00558 public Inet4Address[] getInet4Addresses() { 00559 return _ipv4Addresses.toArray(new Inet4Address[_ipv4Addresses.size()]); 00560 } 00561 00562 /* 00563 * (non-Javadoc) 00564 * @see javax.jmdns.ServiceInfo#getInet6Addresses() 00565 */ 00566 @Override 00567 public Inet6Address[] getInet6Addresses() { 00568 return _ipv6Addresses.toArray(new Inet6Address[_ipv6Addresses.size()]); 00569 } 00570 00574 @Override 00575 public int getPort() { 00576 return _port; 00577 } 00578 00582 @Override 00583 public int getPriority() { 00584 return _priority; 00585 } 00586 00590 @Override 00591 public int getWeight() { 00592 return _weight; 00593 } 00594 00598 @Override 00599 public byte[] getTextBytes() { 00600 return (this._text != null && this._text.length > 0 ? this._text : DNSRecord.EMPTY_TXT); 00601 } 00602 00606 @Deprecated 00607 @Override 00608 public String getTextString() { 00609 Map<String, byte[]> properties = this.getProperties(); 00610 for (String key : properties.keySet()) { 00611 byte[] value = properties.get(key); 00612 if ((value != null) && (value.length > 0)) { 00613 return key + "=" + new String(value); 00614 } 00615 return key; 00616 } 00617 return ""; 00618 } 00619 00620 /* 00621 * (non-Javadoc) 00622 * @see javax.jmdns.ServiceInfo#getURL() 00623 */ 00624 @Deprecated 00625 @Override 00626 public String getURL() { 00627 return this.getURL("http"); 00628 } 00629 00630 /* 00631 * (non-Javadoc) 00632 * @see javax.jmdns.ServiceInfo#getURLs() 00633 */ 00634 @Override 00635 public String[] getURLs() { 00636 return this.getURLs("http"); 00637 } 00638 00639 /* 00640 * (non-Javadoc) 00641 * @see javax.jmdns.ServiceInfo#getURL(java.lang.String) 00642 */ 00643 @Deprecated 00644 @Override 00645 public String getURL(String protocol) { 00646 String[] urls = this.getURLs(protocol); 00647 return (urls.length > 0 ? urls[0] : protocol + "://null:" + getPort()); 00648 } 00649 00650 /* 00651 * (non-Javadoc) 00652 * @see javax.jmdns.ServiceInfo#getURLs(java.lang.String) 00653 */ 00654 @Override 00655 public String[] getURLs(String protocol) { 00656 InetAddress[] addresses = this.getInetAddresses(); 00657 String[] urls = new String[addresses.length]; 00658 for (int i = 0; i < addresses.length; i++) { 00659 String url = protocol + "://" + addresses[i].getHostAddress() + ":" + getPort(); 00660 String path = getPropertyString("path"); 00661 if (path != null) { 00662 if (path.indexOf("://") >= 0) { 00663 url = path; 00664 } else { 00665 url += path.startsWith("/") ? path : "/" + path; 00666 } 00667 } 00668 urls[i] = url; 00669 } 00670 return urls; 00671 } 00672 00676 @Override 00677 public synchronized byte[] getPropertyBytes(String name) { 00678 return this.getProperties().get(name); 00679 } 00680 00684 @Override 00685 public synchronized String getPropertyString(String name) { 00686 byte data[] = this.getProperties().get(name); 00687 if (data == null) { 00688 return null; 00689 } 00690 if (data == NO_VALUE) { 00691 return "true"; 00692 } 00693 return readUTF(data, 0, data.length); 00694 } 00695 00699 @Override 00700 public Enumeration<String> getPropertyNames() { 00701 Map<String, byte[]> properties = this.getProperties(); 00702 Collection<String> names = (properties != null ? properties.keySet() : Collections.<String> emptySet()); 00703 return new Vector<String>(names).elements(); 00704 } 00705 00709 @Override 00710 public String getApplication() { 00711 return (_application != null ? _application : ""); 00712 } 00713 00717 @Override 00718 public String getDomain() { 00719 return (_domain != null ? _domain : "local"); 00720 } 00721 00725 @Override 00726 public String getProtocol() { 00727 return (_protocol != null ? _protocol : "tcp"); 00728 } 00729 00733 @Override 00734 public String getSubtype() { 00735 return (_subtype != null ? _subtype : ""); 00736 } 00737 00741 @Override 00742 public Map<Fields, String> getQualifiedNameMap() { 00743 Map<Fields, String> map = new HashMap<Fields, String>(5); 00744 00745 map.put(Fields.Domain, this.getDomain()); 00746 map.put(Fields.Protocol, this.getProtocol()); 00747 map.put(Fields.Application, this.getApplication()); 00748 map.put(Fields.Instance, this.getName()); 00749 map.put(Fields.Subtype, this.getSubtype()); 00750 return map; 00751 } 00752 00756 static void writeUTF(OutputStream out, String str) throws IOException { 00757 for (int i = 0, len = str.length(); i < len; i++) { 00758 int c = str.charAt(i); 00759 if ((c >= 0x0001) && (c <= 0x007F)) { 00760 out.write(c); 00761 } else { 00762 if (c > 0x07FF) { 00763 out.write(0xE0 | ((c >> 12) & 0x0F)); 00764 out.write(0x80 | ((c >> 6) & 0x3F)); 00765 out.write(0x80 | ((c >> 0) & 0x3F)); 00766 } else { 00767 out.write(0xC0 | ((c >> 6) & 0x1F)); 00768 out.write(0x80 | ((c >> 0) & 0x3F)); 00769 } 00770 } 00771 } 00772 } 00773 00777 String readUTF(byte data[], int off, int len) { 00778 int offset = off; 00779 StringBuffer buf = new StringBuffer(); 00780 for (int end = offset + len; offset < end;) { 00781 int ch = data[offset++] & 0xFF; 00782 switch (ch >> 4) { 00783 case 0: 00784 case 1: 00785 case 2: 00786 case 3: 00787 case 4: 00788 case 5: 00789 case 6: 00790 case 7: 00791 // 0xxxxxxx 00792 break; 00793 case 12: 00794 case 13: 00795 if (offset >= len) { 00796 return null; 00797 } 00798 // 110x xxxx 10xx xxxx 00799 ch = ((ch & 0x1F) << 6) | (data[offset++] & 0x3F); 00800 break; 00801 case 14: 00802 if (offset + 2 >= len) { 00803 return null; 00804 } 00805 // 1110 xxxx 10xx xxxx 10xx xxxx 00806 ch = ((ch & 0x0f) << 12) | ((data[offset++] & 0x3F) << 6) | (data[offset++] & 0x3F); 00807 break; 00808 default: 00809 if (offset + 1 >= len) { 00810 return null; 00811 } 00812 // 10xx xxxx, 1111 xxxx 00813 ch = ((ch & 0x3F) << 4) | (data[offset++] & 0x0f); 00814 break; 00815 } 00816 buf.append((char) ch); 00817 } 00818 return buf.toString(); 00819 } 00820 00821 synchronized Map<String, byte[]> getProperties() { 00822 if ((_props == null) && (this.getTextBytes() != null)) { 00823 Hashtable<String, byte[]> properties = new Hashtable<String, byte[]>(); 00824 try { 00825 int off = 0; 00826 while (off < getTextBytes().length) { 00827 // length of the next key value pair 00828 int len = getTextBytes()[off++] & 0xFF; 00829 if ((len == 0) || (off + len > getTextBytes().length)) { 00830 properties.clear(); 00831 break; 00832 } 00833 // look for the '=' 00834 int i = 0; 00835 for (; (i < len) && (getTextBytes()[off + i] != '='); i++) { 00836 /* Stub */ 00837 } 00838 00839 // get the property name 00840 String name = readUTF(getTextBytes(), off, i); 00841 if (name == null) { 00842 properties.clear(); 00843 break; 00844 } 00845 if (i == len) { 00846 properties.put(name, NO_VALUE); 00847 } else { 00848 byte value[] = new byte[len - ++i]; 00849 System.arraycopy(getTextBytes(), off + i, value, 0, len - i); 00850 properties.put(name, value); 00851 off += len; 00852 } 00853 } 00854 } catch (Exception exception) { 00855 // We should get better logging. 00856 logger.log(Level.WARNING, "Malformed TXT Field ", exception); 00857 } 00858 this._props = properties; 00859 } 00860 return (_props != null ? _props : Collections.<String, byte[]> emptyMap()); 00861 } 00862 00870 @Override 00871 public void updateRecord(DNSCache dnsCache, long now, DNSEntry rec) { 00872 if ((rec instanceof DNSRecord) && !rec.isExpired(now)) { 00873 boolean serviceUpdated = false; 00874 String result; 00875 HostInfo host_info; 00876 switch (rec.getRecordType()) { 00877 case TYPE_A: // IPv4 00878 // { 00879 // result = "UpdateRecord (TYPE_IPv4):\n"; 00880 // host_info = this.getDns().getLocalHost(); 00881 // result += " Record Jmdns : " + host_info.getInetAddress().getHostAddress(); 00882 // result += " [" + host_info.getInterface().getDisplayName() + "]\n"; 00883 // result += " Record Name : " + rec.getName() + "\n"; 00884 // result += " Info Server : " + this.getServer() + "\n"; 00885 // for ( Inet4Address address : _ipv4Addresses ) { 00886 // result += " Address : " + address.getHostAddress() + "\n"; 00887 // } 00888 // for ( Inet6Address address : _ipv6Addresses ) { 00889 // result += " Address : " + address.getHostAddress() + "\n";; 00890 // } 00891 // result += " [+] Address : " + ((Inet4Address) ((DNSRecord.Address) rec).getAddress()).getHostAddress() + "\n"; 00892 // System.out.printf("%s", result); 00893 // } 00894 if (rec.getName().equalsIgnoreCase(this.getServer())) { 00895 _ipv4Addresses.add((Inet4Address) ((DNSRecord.Address) rec).getAddress()); 00896 serviceUpdated = true; 00897 } 00898 break; 00899 case TYPE_AAAA: // IPv6 00900 // { 00901 // result = "UpdateRecord (TYPE_IPv6):\n"; 00902 // host_info = this.getDns().getLocalHost(); 00903 // result += " Record Jmdns : " + host_info.getInetAddress().getHostAddress(); 00904 // result += " [" + host_info.getInterface().getDisplayName() + "]\n"; 00905 // result += " Record Name : " + rec.getName() + "\n"; 00906 // result += " Info Server : " + this.getServer() + "\n"; 00907 // for ( Inet4Address address : _ipv4Addresses ) { 00908 // result += " Address : " + address.getHostAddress() + "\n"; 00909 // } 00910 // for ( Inet6Address address : _ipv6Addresses ) { 00911 // result += " Address : " + address.getHostAddress() + "\n";; 00912 // } 00913 // result += " [+] Address : " + ((DNSRecord.Address) rec).getAddress().getHostAddress() + "\n"; 00914 // System.out.printf("%s", result); 00915 // } 00916 if (rec.getName().equalsIgnoreCase(this.getServer())) { 00917 _ipv6Addresses.add((Inet6Address) ((DNSRecord.Address) rec).getAddress()); 00918 serviceUpdated = true; 00919 } 00920 break; 00921 case TYPE_SRV: 00922 // do a git checkout 6e46cc136a95849d2799a857da1ac851046aadb9 to see my first experiment here to solve multi-interface issues 00923 if (rec.getName().equalsIgnoreCase(this.getQualifiedName())) { 00924 DNSRecord.Service srv = (DNSRecord.Service) rec; 00925 00926 // { // debugging 00927 // result = "UpdateRecord (TYPE_SRV):\n"; 00928 // host_info = this.getDns().getLocalHost(); 00929 // result += " Record Jmdns: " + host_info.getInetAddress().getHostAddress(); 00930 // result += " [" + host_info.getInterface().getDisplayName() + "]\n"; 00931 // result += " DNS Server : " + srv.getServer() + "\n"; 00932 // if ( _server == null ) { 00933 // result += " Info Server : -\n"; 00934 // } else { 00935 // result += " Info Server : " + _server + "\n"; 00936 // } 00937 // System.out.printf("%s", result); 00938 // } 00939 00940 // DJS: This is the original jmdns, but it fails on multiple interface pc's. 00941 // Issue: http://sourceforge.net/tracker/?func=detail&aid=3437568&group_id=93852&atid=605791 00942 boolean serverChanged = (_server == null) || !_server.equalsIgnoreCase(srv.getServer()); 00943 _server = srv.getServer(); 00944 _port = srv.getPort(); 00945 _weight = srv.getWeight(); 00946 _priority = srv.getPriority(); 00947 if (serverChanged) { 00948 _ipv4Addresses.clear(); 00949 _ipv6Addresses.clear(); 00950 for (DNSEntry entry : dnsCache.getDNSEntryList(_server, DNSRecordType.TYPE_A, DNSRecordClass.CLASS_IN)) { 00951 this.updateRecord(dnsCache, now, entry); 00952 } 00953 for (DNSEntry entry : dnsCache.getDNSEntryList(_server, DNSRecordType.TYPE_AAAA, DNSRecordClass.CLASS_IN)) { 00954 this.updateRecord(dnsCache, now, entry); 00955 } 00956 // We do not want to trigger the listener in this case as it will be triggered if the address resolves. 00957 } else { 00958 serviceUpdated = true; 00959 } 00960 } 00961 break; 00962 case TYPE_TXT: 00963 if (rec.getName().equalsIgnoreCase(this.getQualifiedName())) { 00964 DNSRecord.Text txt = (DNSRecord.Text) rec; 00965 _text = txt.getText(); 00966 _props = null; // set it null for apply update text data 00967 serviceUpdated = true; 00968 } 00969 break; 00970 case TYPE_PTR: 00971 if ((this.getSubtype().length() == 0) && (rec.getSubtype().length() != 0)) { 00972 _subtype = rec.getSubtype(); 00973 serviceUpdated = true; 00974 } 00975 break; 00976 default: 00977 break; 00978 } 00979 if (serviceUpdated && this.hasData()) { 00980 JmDNSImpl dns = this.getDns(); 00981 if (dns != null) { 00982 ServiceEvent event = ((DNSRecord) rec).getServiceEvent(dns); 00983 event = new ServiceEventImpl(dns, event.getType(), event.getName(), this); 00984 dns.handleServiceResolved(event); 00985 } 00986 } 00987 // This is done, to notify the wait loop in method JmDNS.waitForInfoData(ServiceInfo info, int timeout); 00988 synchronized (this) { 00989 this.notifyAll(); 00990 } 00991 } 00992 } 00993 00999 @Override 01000 public synchronized boolean hasData() { 01001 return this.getServer() != null && this.hasInetAddress() && this.getTextBytes() != null && this.getTextBytes().length > 0; 01002 // return this.getServer() != null && (this.getAddress() != null || (this.getTextBytes() != null && this.getTextBytes().length > 0)); 01003 } 01004 01005 private final boolean hasInetAddress() { 01006 return _ipv4Addresses.size() > 0 || _ipv6Addresses.size() > 0; 01007 } 01008 01009 // State machine 01010 01014 @Override 01015 public boolean advanceState(DNSTask task) { 01016 return _state.advanceState(task); 01017 } 01018 01022 @Override 01023 public boolean revertState() { 01024 return _state.revertState(); 01025 } 01026 01030 @Override 01031 public boolean cancelState() { 01032 return _state.cancelState(); 01033 } 01034 01038 @Override 01039 public boolean closeState() { 01040 return this._state.closeState(); 01041 } 01042 01046 @Override 01047 public boolean recoverState() { 01048 return this._state.recoverState(); 01049 } 01050 01054 @Override 01055 public void removeAssociationWithTask(DNSTask task) { 01056 _state.removeAssociationWithTask(task); 01057 } 01058 01062 @Override 01063 public void associateWithTask(DNSTask task, DNSState state) { 01064 _state.associateWithTask(task, state); 01065 } 01066 01070 @Override 01071 public boolean isAssociatedWithTask(DNSTask task, DNSState state) { 01072 return _state.isAssociatedWithTask(task, state); 01073 } 01074 01078 @Override 01079 public boolean isProbing() { 01080 return _state.isProbing(); 01081 } 01082 01086 @Override 01087 public boolean isAnnouncing() { 01088 return _state.isAnnouncing(); 01089 } 01090 01094 @Override 01095 public boolean isAnnounced() { 01096 return _state.isAnnounced(); 01097 } 01098 01102 @Override 01103 public boolean isCanceling() { 01104 return this._state.isCanceling(); 01105 } 01106 01110 @Override 01111 public boolean isCanceled() { 01112 return _state.isCanceled(); 01113 } 01114 01118 @Override 01119 public boolean isClosing() { 01120 return _state.isClosing(); 01121 } 01122 01126 @Override 01127 public boolean isClosed() { 01128 return _state.isClosed(); 01129 } 01130 01134 @Override 01135 public boolean waitForAnnounced(long timeout) { 01136 return _state.waitForAnnounced(timeout); 01137 } 01138 01142 @Override 01143 public boolean waitForCanceled(long timeout) { 01144 return _state.waitForCanceled(timeout); 01145 } 01146 01150 @Override 01151 public int hashCode() { 01152 return getQualifiedName().hashCode(); 01153 } 01154 01158 @Override 01159 public boolean equals(Object obj) { 01160 return (obj instanceof ServiceInfoImpl) && getQualifiedName().equals(((ServiceInfoImpl) obj).getQualifiedName()); 01161 } 01162 01166 @Override 01167 public String getNiceTextString() { 01168 StringBuffer buf = new StringBuffer(); 01169 for (int i = 0, len = this.getTextBytes().length; i < len; i++) { 01170 if (i >= 200) { 01171 buf.append("..."); 01172 break; 01173 } 01174 int ch = getTextBytes()[i] & 0xFF; 01175 if ((ch < ' ') || (ch > 127)) { 01176 buf.append("\\0"); 01177 buf.append(Integer.toString(ch, 8)); 01178 } else { 01179 buf.append((char) ch); 01180 } 01181 } 01182 return buf.toString(); 01183 } 01184 01185 /* 01186 * (non-Javadoc) 01187 * @see javax.jmdns.ServiceInfo#clone() 01188 */ 01189 @Override 01190 public ServiceInfoImpl clone() { 01191 ServiceInfoImpl serviceInfo = new ServiceInfoImpl(this.getQualifiedNameMap(), _port, _weight, _priority, _persistent, _text); 01192 Inet6Address[] ipv6Addresses = this.getInet6Addresses(); 01193 for (Inet6Address address : ipv6Addresses) { 01194 serviceInfo._ipv6Addresses.add(address); 01195 } 01196 Inet4Address[] ipv4Addresses = this.getInet4Addresses(); 01197 for (Inet4Address address : ipv4Addresses) { 01198 serviceInfo._ipv4Addresses.add(address); 01199 } 01200 return serviceInfo; 01201 } 01202 01206 @Override 01207 public String toString() { 01208 StringBuilder buf = new StringBuilder(); 01209 buf.append("[" + this.getClass().getSimpleName() + "@" + System.identityHashCode(this) + " "); 01210 buf.append("name: '"); 01211 buf.append((this.getName().length() > 0 ? this.getName() + "." : "") + this.getTypeWithSubtype()); 01212 buf.append("' address: '"); 01213 InetAddress[] addresses = this.getInetAddresses(); 01214 if (addresses.length > 0) { 01215 for (InetAddress address : addresses) { 01216 buf.append(address); 01217 buf.append(':'); 01218 buf.append(this.getPort()); 01219 buf.append(' '); 01220 } 01221 } else { 01222 buf.append("(null):"); 01223 buf.append(this.getPort()); 01224 } 01225 buf.append("' status: '"); 01226 buf.append(_state.toString()); 01227 buf.append(this.isPersistent() ? "' is persistent," : "',"); 01228 buf.append(" has "); 01229 buf.append(this.hasData() ? "" : "NO "); 01230 buf.append("data"); 01231 if (this.getTextBytes().length > 0) { 01232 // buf.append("\n"); 01233 // buf.append(this.getNiceTextString()); 01234 Map<String, byte[]> properties = this.getProperties(); 01235 if (!properties.isEmpty()) { 01236 buf.append("\n"); 01237 for (String key : properties.keySet()) { 01238 buf.append("\t" + key + ": " + new String(properties.get(key)) + "\n"); 01239 } 01240 } else { 01241 buf.append(" empty"); 01242 } 01243 } 01244 buf.append(']'); 01245 return buf.toString(); 01246 } 01247 01248 public Collection<DNSRecord> answers(boolean unique, int ttl, HostInfo localHost) { 01249 List<DNSRecord> list = new ArrayList<DNSRecord>(); 01250 if (this.getSubtype().length() > 0) { 01251 list.add(new Pointer(this.getTypeWithSubtype(), DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, ttl, this.getQualifiedName())); 01252 } 01253 list.add(new Pointer(this.getType(), DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE, ttl, this.getQualifiedName())); 01254 list.add(new Service(this.getQualifiedName(), DNSRecordClass.CLASS_IN, unique, ttl, _priority, _weight, _port, localHost.getName())); 01255 list.add(new Text(this.getQualifiedName(), DNSRecordClass.CLASS_IN, unique, ttl, this.getTextBytes())); 01256 return list; 01257 } 01258 01262 @Override 01263 public void setText(byte[] text) throws IllegalStateException { 01264 synchronized (this) { 01265 this._text = text; 01266 this._props = null; 01267 this.setNeedTextAnnouncing(true); 01268 } 01269 } 01270 01274 @Override 01275 public void setText(Map<String, ?> props) throws IllegalStateException { 01276 this.setText(textFromProperties(props)); 01277 } 01278 01284 void _setText(byte[] text) { 01285 this._text = text; 01286 this._props = null; 01287 } 01288 01289 private static byte[] textFromProperties(Map<String, ?> props) { 01290 byte[] text = null; 01291 if (props != null) { 01292 try { 01293 ByteArrayOutputStream out = new ByteArrayOutputStream(256); 01294 for (String key : props.keySet()) { 01295 Object val = props.get(key); 01296 ByteArrayOutputStream out2 = new ByteArrayOutputStream(100); 01297 writeUTF(out2, key); 01298 if (val == null) { 01299 // Skip 01300 } else if (val instanceof String) { 01301 out2.write('='); 01302 writeUTF(out2, (String) val); 01303 } else if (val instanceof byte[]) { 01304 byte[] bval = (byte[]) val; 01305 if (bval.length > 0) { 01306 out2.write('='); 01307 out2.write(bval, 0, bval.length); 01308 } else { 01309 val = null; 01310 } 01311 } else { 01312 throw new IllegalArgumentException("invalid property value: " + val); 01313 } 01314 byte data[] = out2.toByteArray(); 01315 if (data.length > 255) { 01316 throw new IOException("Cannot have individual values larger that 255 chars. Offending value: " + key + (val != null ? "" : "=" + val)); 01317 } 01318 out.write((byte) data.length); 01319 out.write(data, 0, data.length); 01320 } 01321 text = out.toByteArray(); 01322 } catch (IOException e) { 01323 throw new RuntimeException("unexpected exception: " + e); 01324 } 01325 } 01326 return (text != null && text.length > 0 ? text : DNSRecord.EMPTY_TXT); 01327 } 01328 01329 public void setDns(JmDNSImpl dns) { 01330 this._state.setDns(dns); 01331 } 01332 01336 @Override 01337 public JmDNSImpl getDns() { 01338 return this._state.getDns(); 01339 } 01340 01344 @Override 01345 public boolean isPersistent() { 01346 return _persistent; 01347 } 01348 01353 public void setNeedTextAnnouncing(boolean needTextAnnouncing) { 01354 this._needTextAnnouncing = needTextAnnouncing; 01355 if (this._needTextAnnouncing) { 01356 _state.setTask(null); 01357 } 01358 } 01359 01363 public boolean needTextAnnouncing() { 01364 return _needTextAnnouncing; 01365 } 01366 01370 Delegate getDelegate() { 01371 return this._delegate; 01372 } 01373 01378 void setDelegate(Delegate delegate) { 01379 this._delegate = delegate; 01380 } 01381 01382 }