1
2
3
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
39
40
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
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
118
119
120
121
122
123
124
125
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
141
142
143
144
145
146
147
148
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
156
157
158
159
160
161
162
163
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
207
208
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
269 if (!aType.startsWith("_") || aType.startsWith("_services")) {
270 index = aType.indexOf('.');
271 if (index > 0) {
272
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
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
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
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
337 String instance = (qualifiedNameMap.containsKey(Fields.Instance) ? qualifiedNameMap.get(Fields.Instance) : "");
338 if ((instance == null) || (instance.length() == 0)) {
339 instance = "";
340
341 }
342 instance = removeSeparators(instance);
343 checkedQualifiedNameMap.put(Fields.Instance, instance);
344
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
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
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
394
395 @Override
396 public String getName() {
397 return (_name != null ? _name : "");
398 }
399
400
401
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
413
414
415
416
417 void setName(String name) {
418 this._name = name;
419 this._key = null;
420 }
421
422
423
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
432
433
434 return (instance.length() > 0 ? instance + "." : "") + (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + ".";
435 }
436
437
438
439
440 @Override
441 public String getServer() {
442 return (_server != null ? _server : "");
443 }
444
445
446
447
448
449 void setServer(String server) {
450 this._server = server;
451 }
452
453
454
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
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
482
483
484 void addAddress(Inet4Address addr) {
485 _ipv4Addresses.add(addr);
486 }
487
488
489
490
491
492 void addAddress(Inet6Address addr) {
493 _ipv6Addresses.add(addr);
494 }
495
496
497
498
499 @Deprecated
500 @Override
501 public InetAddress getAddress() {
502 return this.getInetAddress();
503 }
504
505
506
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
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
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
537
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
549
550
551 @Override
552 public Inet4Address[] getInet4Addresses() {
553 return _ipv4Addresses.toArray(new Inet4Address[_ipv4Addresses.size()]);
554 }
555
556
557
558
559
560 @Override
561 public Inet6Address[] getInet6Addresses() {
562 return _ipv6Addresses.toArray(new Inet6Address[_ipv6Addresses.size()]);
563 }
564
565
566
567
568 @Override
569 public int getPort() {
570 return _port;
571 }
572
573
574
575
576 @Override
577 public int getPriority() {
578 return _priority;
579 }
580
581
582
583
584 @Override
585 public int getWeight() {
586 return _weight;
587 }
588
589
590
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
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
616
617
618 @Deprecated
619 @Override
620 public String getURL() {
621 return this.getURL("http");
622 }
623
624
625
626
627
628 @Override
629 public String[] getURLs() {
630 return this.getURLs("http");
631 }
632
633
634
635
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
646
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
669
670 @Override
671 public synchronized byte[] getPropertyBytes(String name) {
672 return this.getProperties().get(name);
673 }
674
675
676
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
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
702
703 @Override
704 public String getApplication() {
705 return (_application != null ? _application : "");
706 }
707
708
709
710
711 @Override
712 public String getDomain() {
713 return (_domain != null ? _domain : "local");
714 }
715
716
717
718
719 @Override
720 public String getProtocol() {
721 return (_protocol != null ? _protocol : "tcp");
722 }
723
724
725
726
727 @Override
728 public String getSubtype() {
729 return (_subtype != null ? _subtype : "");
730 }
731
732
733
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
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
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
786 break;
787 case 12:
788 case 13:
789 if (offset >= len) {
790 return null;
791 }
792
793 ch = ((ch & 0x1F) << 6) | (data[offset++] & 0x3F);
794 break;
795 case 14:
796 if (offset + 2 >= len) {
797 return null;
798 }
799
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
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
822 int len = getTextBytes()[off++] & 0xFF;
823 if ((len == 0) || (off + len > getTextBytes().length)) {
824 properties.clear();
825 break;
826 }
827
828 int i = 0;
829 for (; (i < len) && (getTextBytes()[off + i] != '='); i++) {
830
831 }
832
833
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
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
859
860
861
862
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:
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:
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
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
929 synchronized (this) {
930 this.notifyAll();
931 }
932 }
933 }
934
935
936
937
938
939
940 @Override
941 public synchronized boolean hasData() {
942 return this.getServer() != null && this.hasInetAddress() && this.getTextBytes() != null && this.getTextBytes().length > 0;
943
944 }
945
946 private final boolean hasInetAddress() {
947 return _ipv4Addresses.size() > 0 || _ipv6Addresses.size() > 0;
948 }
949
950
951
952
953
954
955 @Override
956 public boolean advanceState(DNSTask task) {
957 return _state.advanceState(task);
958 }
959
960
961
962
963 @Override
964 public boolean revertState() {
965 return _state.revertState();
966 }
967
968
969
970
971 @Override
972 public boolean cancelState() {
973 return _state.cancelState();
974 }
975
976
977
978
979 @Override
980 public boolean closeState() {
981 return this._state.closeState();
982 }
983
984
985
986
987 @Override
988 public boolean recoverState() {
989 return this._state.recoverState();
990 }
991
992
993
994
995 @Override
996 public void removeAssociationWithTask(DNSTask task) {
997 _state.removeAssociationWithTask(task);
998 }
999
1000
1001
1002
1003 @Override
1004 public void associateWithTask(DNSTask task, DNSState state) {
1005 _state.associateWithTask(task, state);
1006 }
1007
1008
1009
1010
1011 @Override
1012 public boolean isAssociatedWithTask(DNSTask task, DNSState state) {
1013 return _state.isAssociatedWithTask(task, state);
1014 }
1015
1016
1017
1018
1019 @Override
1020 public boolean isProbing() {
1021 return _state.isProbing();
1022 }
1023
1024
1025
1026
1027 @Override
1028 public boolean isAnnouncing() {
1029 return _state.isAnnouncing();
1030 }
1031
1032
1033
1034
1035 @Override
1036 public boolean isAnnounced() {
1037 return _state.isAnnounced();
1038 }
1039
1040
1041
1042
1043 @Override
1044 public boolean isCanceling() {
1045 return this._state.isCanceling();
1046 }
1047
1048
1049
1050
1051 @Override
1052 public boolean isCanceled() {
1053 return _state.isCanceled();
1054 }
1055
1056
1057
1058
1059 @Override
1060 public boolean isClosing() {
1061 return _state.isClosing();
1062 }
1063
1064
1065
1066
1067 @Override
1068 public boolean isClosed() {
1069 return _state.isClosed();
1070 }
1071
1072
1073
1074
1075 @Override
1076 public boolean waitForAnnounced(long timeout) {
1077 return _state.waitForAnnounced(timeout);
1078 }
1079
1080
1081
1082
1083 @Override
1084 public boolean waitForCanceled(long timeout) {
1085 return _state.waitForCanceled(timeout);
1086 }
1087
1088
1089
1090
1091 @Override
1092 public int hashCode() {
1093 return getQualifiedName().hashCode();
1094 }
1095
1096
1097
1098
1099 @Override
1100 public boolean equals(Object obj) {
1101 return (obj instanceof ServiceInfoImpl) && getQualifiedName().equals(((ServiceInfoImpl) obj).getQualifiedName());
1102 }
1103
1104
1105
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
1128
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
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
1174
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
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
1214
1215 @Override
1216 public void setText(Map<String, ?> props) throws IllegalStateException {
1217 this.setText(textFromProperties(props));
1218 }
1219
1220
1221
1222
1223
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
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
1276
1277 @Override
1278 public JmDNSImpl getDns() {
1279 return this._state.getDns();
1280 }
1281
1282
1283
1284
1285 @Override
1286 public boolean isPersistent() {
1287 return _persistent;
1288 }
1289
1290
1291
1292
1293
1294 public void setNeedTextAnnouncing(boolean needTextAnnouncing) {
1295 this._needTextAnnouncing = needTextAnnouncing;
1296 if (this._needTextAnnouncing) {
1297 _state.setTask(null);
1298 }
1299 }
1300
1301
1302
1303
1304 public boolean needTextAnnouncing() {
1305 return _needTextAnnouncing;
1306 }
1307
1308
1309
1310
1311 Delegate getDelegate() {
1312 return this._delegate;
1313 }
1314
1315
1316
1317
1318
1319 void setDelegate(Delegate delegate) {
1320 this._delegate = delegate;
1321 }
1322
1323 }