View Javadoc

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