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