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.DataOutputStream;
8 import java.io.IOException;
9 import java.io.UnsupportedEncodingException;
10 import java.net.Inet4Address;
11 import java.net.Inet6Address;
12 import java.net.InetAddress;
13 import java.net.UnknownHostException;
14 import java.util.HashMap;
15 import java.util.Map;
16 import java.util.logging.Level;
17 import java.util.logging.Logger;
18 
19 import javax.jmdns.ServiceEvent;
20 import javax.jmdns.ServiceInfo;
21 import javax.jmdns.ServiceInfo.Fields;
22 import javax.jmdns.impl.DNSOutgoing.MessageOutputStream;
23 import javax.jmdns.impl.constants.DNSConstants;
24 import javax.jmdns.impl.constants.DNSRecordClass;
25 import javax.jmdns.impl.constants.DNSRecordType;
26 
27 /**
28  * DNS record
29  *
30  * @author Arthur van Hoff, Rick Blair, Werner Randelshofer, Pierre Frisch
31  */
32 public abstract class DNSRecord extends DNSEntry {
33     private static Logger logger = Logger.getLogger(DNSRecord.class.getName());
34     private int           _ttl;
35     private long          _created;
36 
37     /**
38      * This source is mainly for debugging purposes, should be the address that sent this record.
39      */
40     private InetAddress   _source;
41 
42     /**
43      * Create a DNSRecord with a name, type, class, and ttl.
44      */
DNSRecord(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl)45     DNSRecord(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl) {
46         super(name, type, recordClass, unique);
47         this._ttl = ttl;
48         this._created = System.currentTimeMillis();
49     }
50 
51     /*
52      * (non-Javadoc)
53      * @see javax.jmdns.impl.DNSEntry#equals(java.lang.Object)
54      */
55     @Override
equals(Object other)56     public boolean equals(Object other) {
57         return (other instanceof DNSRecord) && super.equals(other) && sameValue((DNSRecord) other);
58     }
59 
60     /**
61      * True if this record has the same value as some other record.
62      */
sameValue(DNSRecord other)63     abstract boolean sameValue(DNSRecord other);
64 
65     /**
66      * True if this record has the same type as some other record.
67      */
sameType(DNSRecord other)68     boolean sameType(DNSRecord other) {
69         return this.getRecordType() == other.getRecordType();
70     }
71 
72     /**
73      * Handles a query represented by this record.
74      *
75      * @return Returns true if a conflict with one of the services registered with JmDNS or with the hostname occured.
76      */
handleQuery(JmDNSImpl dns, long expirationTime)77     abstract boolean handleQuery(JmDNSImpl dns, long expirationTime);
78 
79     /**
80      * Handles a response represented by this record.
81      *
82      * @return Returns true if a conflict with one of the services registered with JmDNS or with the hostname occured.
83      */
handleResponse(JmDNSImpl dns)84     abstract boolean handleResponse(JmDNSImpl dns);
85 
86     /**
87      * Adds this as an answer to the provided outgoing datagram.
88      */
addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out)89     abstract DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException;
90 
91     /**
92      * True if this record is suppressed by the answers in a message.
93      */
suppressedBy(DNSIncoming msg)94     boolean suppressedBy(DNSIncoming msg) {
95         try {
96             for (DNSRecord answer : msg.getAllAnswers()) {
97                 if (suppressedBy(answer)) {
98                     return true;
99                 }
100             }
101             return false;
102         } catch (ArrayIndexOutOfBoundsException e) {
103             logger.log(Level.WARNING, "suppressedBy() message " + msg + " exception ", e);
104             // msg.print(true);
105             return false;
106         }
107     }
108 
109     /**
110      * True if this record would be suppressed by an answer. This is the case if this record would not have a significantly longer TTL.
111      */
suppressedBy(DNSRecord other)112     boolean suppressedBy(DNSRecord other) {
113         if (this.equals(other) && (other._ttl > _ttl / 2)) {
114             return true;
115         }
116         return false;
117     }
118 
119     /**
120      * Get the expiration time of this record.
121      */
getExpirationTime(int percent)122     long getExpirationTime(int percent) {
123         // ttl is in seconds the constant 10 is 1000 ms / 100 %
124         return _created + (percent * _ttl * 10L);
125     }
126 
127     /**
128      * Get the remaining TTL for this record.
129      */
getRemainingTTL(long now)130     int getRemainingTTL(long now) {
131         return (int) Math.max(0, (getExpirationTime(100) - now) / 1000);
132     }
133 
134     /*
135      * (non-Javadoc)
136      * @see javax.jmdns.impl.DNSEntry#isExpired(long)
137      */
138     @Override
isExpired(long now)139     public boolean isExpired(long now) {
140         return getExpirationTime(100) <= now;
141     }
142 
143     /*
144      * (non-Javadoc)
145      * @see javax.jmdns.impl.DNSEntry#isStale(long)
146      */
147     @Override
isStale(long now)148     public boolean isStale(long now) {
149         return getExpirationTime(50) <= now;
150     }
151 
152     /**
153      * Reset the TTL of a record. This avoids having to update the entire record in the cache.
154      */
resetTTL(DNSRecord other)155     void resetTTL(DNSRecord other) {
156         _created = other._created;
157         _ttl = other._ttl;
158     }
159 
160     /**
161      * When a record flushed we don't remove it immediately, but mark it for rapid decay.
162      */
setWillExpireSoon(long now)163     void setWillExpireSoon(long now) {
164         _created = now;
165         _ttl = DNSConstants.RECORD_EXPIRY_DELAY;
166     }
167 
168     /**
169      * Write this record into an outgoing message.
170      */
write(MessageOutputStream out)171     abstract void write(MessageOutputStream out);
172 
173     public static class IPv4Address extends Address {
174 
IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr)175         IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) {
176             super(name, DNSRecordType.TYPE_A, recordClass, unique, ttl, addr);
177         }
178 
IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress)179         IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress) {
180             super(name, DNSRecordType.TYPE_A, recordClass, unique, ttl, rawAddress);
181         }
182 
183         @Override
write(MessageOutputStream out)184         void write(MessageOutputStream out) {
185             if (_addr != null) {
186                 byte[] buffer = _addr.getAddress();
187                 // If we have a type A records we should answer with a IPv4 address
188                 if (_addr instanceof Inet4Address) {
189                     // All is good
190                 } else {
191                     // Get the last four bytes
192                     byte[] tempbuffer = buffer;
193                     buffer = new byte[4];
194                     System.arraycopy(tempbuffer, 12, buffer, 0, 4);
195                 }
196                 int length = buffer.length;
197                 out.writeBytes(buffer, 0, length);
198             }
199         }
200 
201         /*
202          * (non-Javadoc)
203          * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
204          */
205         @Override
getServiceInfo(boolean persistent)206         public ServiceInfo getServiceInfo(boolean persistent) {
207 
208             ServiceInfoImpl info = (ServiceInfoImpl) super.getServiceInfo(persistent);
209             info.addAddress((Inet4Address) _addr);
210             return info;
211         }
212 
213     }
214 
215     public static class IPv6Address extends Address {
216 
IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr)217         IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) {
218             super(name, DNSRecordType.TYPE_AAAA, recordClass, unique, ttl, addr);
219         }
220 
IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress)221         IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress) {
222             super(name, DNSRecordType.TYPE_AAAA, recordClass, unique, ttl, rawAddress);
223         }
224 
225         @Override
write(MessageOutputStream out)226         void write(MessageOutputStream out) {
227             if (_addr != null) {
228                 byte[] buffer = _addr.getAddress();
229                 // If we have a type AAAA records we should answer with a IPv6 address
230                 if (_addr instanceof Inet4Address) {
231                     byte[] tempbuffer = buffer;
232                     buffer = new byte[16];
233                     for (int i = 0; i < 16; i++) {
234                         if (i < 11) {
235                             buffer[i] = tempbuffer[i - 12];
236                         } else {
237                             buffer[i] = 0;
238                         }
239                     }
240                 }
241                 int length = buffer.length;
242                 out.writeBytes(buffer, 0, length);
243             }
244         }
245 
246         /*
247          * (non-Javadoc)
248          * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
249          */
250         @Override
getServiceInfo(boolean persistent)251         public ServiceInfo getServiceInfo(boolean persistent) {
252 
253             ServiceInfoImpl info = (ServiceInfoImpl) super.getServiceInfo(persistent);
254             info.addAddress((Inet6Address) _addr);
255             return info;
256         }
257 
258     }
259 
260     /**
261      * Address record.
262      */
263     public static abstract class Address extends DNSRecord {
264         private static Logger logger1 = Logger.getLogger(Address.class.getName());
265 
266         InetAddress           _addr;
267 
Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr)268         protected Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) {
269             super(name, type, recordClass, unique, ttl);
270             this._addr = addr;
271         }
272 
Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress)273         protected Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress) {
274             super(name, type, recordClass, unique, ttl);
275             try {
276                 this._addr = InetAddress.getByAddress(rawAddress);
277             } catch (UnknownHostException exception) {
278                 logger1.log(Level.WARNING, "Address() exception ", exception);
279             }
280         }
281 
same(DNSRecord other)282         boolean same(DNSRecord other) {
283             if (! (other instanceof Address) ) {
284                 return false;
285             }
286             return ((sameName(other)) && ((sameValue(other))));
287         }
288 
sameName(DNSRecord other)289         boolean sameName(DNSRecord other) {
290             return this.getName().equalsIgnoreCase(other.getName());
291         }
292 
293         @Override
sameValue(DNSRecord other)294         boolean sameValue(DNSRecord other) {
295             if (! (other instanceof Address) ) {
296                 return false;
297             }
298             Address address = (Address) other;
299             if ((this.getAddress() == null) && (address.getAddress() != null)) {
300                 return false;
301             }
302             return this.getAddress().equals(address.getAddress());
303         }
304 
305         @Override
isSingleValued()306         public boolean isSingleValued() {
307             return false;
308         }
309 
getAddress()310         InetAddress getAddress() {
311             return _addr;
312         }
313 
314         /**
315          * Creates a byte array representation of this record. This is needed for tie-break tests according to draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2.
316          */
317         @Override
toByteArray(DataOutputStream dout)318         protected void toByteArray(DataOutputStream dout) throws IOException {
319             super.toByteArray(dout);
320             byte[] buffer = this.getAddress().getAddress();
321             for (int i = 0; i < buffer.length; i++) {
322                 dout.writeByte(buffer[i]);
323             }
324         }
325 
326         /**
327          * Does the necessary actions, when this as a query.
328          */
329         @Override
handleQuery(JmDNSImpl dns, long expirationTime)330         boolean handleQuery(JmDNSImpl dns, long expirationTime) {
331             if (dns.getLocalHost().conflictWithRecord(this)) {
332                 DNSRecord.Address localAddress = dns.getLocalHost().getDNSAddressRecord(this.getRecordType(), this.isUnique(), DNSConstants.DNS_TTL);
333                 int comparison = this.compareTo(localAddress);
334 
335                 if (comparison == 0) {
336                     // the 2 records are identical this probably means we are seeing our own record.
337                     // With multiple interfaces on a single computer it is possible to see our
338                     // own records come in on different interfaces than the ones they were sent on.
339                     // see section "10. Conflict Resolution" of mdns draft spec.
340                     logger1.finer("handleQuery() Ignoring an identical address query");
341                     return false;
342                 }
343 
344                 logger1.finer("handleQuery() Conflicting query detected.");
345                 // Tie breaker test
346                 if (dns.isProbing() && comparison > 0) {
347                     // We lost the tie-break. We have to choose a different name.
348                     dns.getLocalHost().incrementHostName();
349                     dns.getCache().clear();
350                     for (ServiceInfo serviceInfo : dns.getServices().values()) {
351                         ServiceInfoImpl info = (ServiceInfoImpl) serviceInfo;
352                         info.revertState();
353                     }
354                 }
355                 dns.revertState();
356                 return true;
357             }
358             return false;
359         }
360 
361         /**
362          * Does the necessary actions, when this as a response.
363          */
364         @Override
handleResponse(JmDNSImpl dns)365         boolean handleResponse(JmDNSImpl dns) {
366             if (dns.getLocalHost().conflictWithRecord(this)) {
367                 logger1.finer("handleResponse() Denial detected");
368 
369                 if (dns.isProbing()) {
370                     dns.getLocalHost().incrementHostName();
371                     dns.getCache().clear();
372                     for (ServiceInfo serviceInfo : dns.getServices().values()) {
373                         ServiceInfoImpl info = (ServiceInfoImpl) serviceInfo;
374                         info.revertState();
375                     }
376                 }
377                 dns.revertState();
378                 return true;
379             }
380             return false;
381         }
382 
383         @Override
addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out)384         DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException {
385             return out;
386         }
387 
388         /*
389          * (non-Javadoc)
390          * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
391          */
392         @Override
getServiceInfo(boolean persistent)393         public ServiceInfo getServiceInfo(boolean persistent) {
394             ServiceInfoImpl info = new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, (byte[]) null);
395             // info.setAddress(_addr); This is done in the sub class so we don't have to test for class type
396             return info;
397         }
398 
399         /*
400          * (non-Javadoc)
401          * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl)
402          */
403         @Override
getServiceEvent(JmDNSImpl dns)404         public ServiceEvent getServiceEvent(JmDNSImpl dns) {
405             ServiceInfo info = this.getServiceInfo(false);
406             ((ServiceInfoImpl) info).setDns(dns);
407             return new ServiceEventImpl(dns, info.getType(), info.getName(), info);
408         }
409 
410         /*
411          * (non-Javadoc)
412          * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder)
413          */
414         @Override
toString(StringBuilder aLog)415         protected void toString(StringBuilder aLog) {
416             super.toString(aLog);
417             aLog.append(" address: '" + (this.getAddress() != null ? this.getAddress().getHostAddress() : "null") + "'");
418         }
419 
420     }
421 
422     /**
423      * Pointer record.
424      */
425     public static class Pointer extends DNSRecord {
426         // private static Logger logger = Logger.getLogger(Pointer.class.getName());
427         private final String _alias;
428 
Pointer(String name, DNSRecordClass recordClass, boolean unique, int ttl, String alias)429         public Pointer(String name, DNSRecordClass recordClass, boolean unique, int ttl, String alias) {
430             super(name, DNSRecordType.TYPE_PTR, recordClass, unique, ttl);
431             this._alias = alias;
432         }
433 
434         /*
435          * (non-Javadoc)
436          * @see javax.jmdns.impl.DNSEntry#isSameEntry(javax.jmdns.impl.DNSEntry)
437          */
438         @Override
isSameEntry(DNSEntry entry)439         public boolean isSameEntry(DNSEntry entry) {
440             return super.isSameEntry(entry) && (entry instanceof Pointer) && this.sameValue((Pointer) entry);
441         }
442 
443         @Override
write(MessageOutputStream out)444         void write(MessageOutputStream out) {
445             out.writeName(_alias);
446         }
447 
448         @Override
sameValue(DNSRecord other)449         boolean sameValue(DNSRecord other) {
450             if (! (other instanceof Pointer) ) {
451                 return false;
452             }
453             Pointer pointer = (Pointer) other;
454             if ((_alias == null) && (pointer._alias != null)) {
455                 return false;
456             }
457             return _alias.equals(pointer._alias);
458         }
459 
460         @Override
isSingleValued()461         public boolean isSingleValued() {
462             return false;
463         }
464 
465         @Override
handleQuery(JmDNSImpl dns, long expirationTime)466         boolean handleQuery(JmDNSImpl dns, long expirationTime) {
467             // Nothing to do (?)
468             // I think there is no possibility for conflicts for this record type?
469             return false;
470         }
471 
472         @Override
handleResponse(JmDNSImpl dns)473         boolean handleResponse(JmDNSImpl dns) {
474             // Nothing to do (?)
475             // I think there is no possibility for conflicts for this record type?
476             return false;
477         }
478 
getAlias()479         String getAlias() {
480             return _alias;
481         }
482 
483         @Override
addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out)484         DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException {
485             return out;
486         }
487 
488         /*
489          * (non-Javadoc)
490          * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
491          */
492         @Override
getServiceInfo(boolean persistent)493         public ServiceInfo getServiceInfo(boolean persistent) {
494             if (this.isServicesDiscoveryMetaQuery()) {
495                 // The service name is in the alias
496                 Map<Fields, String> map = ServiceInfoImpl.decodeQualifiedNameMapForType(this.getAlias());
497                 return new ServiceInfoImpl(map, 0, 0, 0, persistent, (byte[]) null);
498             } else if (this.isReverseLookup()) {
499                 return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, (byte[]) null);
500             } else if (this.isDomainDiscoveryQuery()) {
501                 // FIXME [PJYF Nov 16 2010] We do not currently support domain discovery
502                 return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, (byte[]) null);
503             }
504             Map<Fields, String> map = ServiceInfoImpl.decodeQualifiedNameMapForType(this.getAlias());
505             map.put(Fields.Subtype, this.getQualifiedNameMap().get(Fields.Subtype));
506             return new ServiceInfoImpl(map, 0, 0, 0, persistent, this.getAlias());
507         }
508 
509         /*
510          * (non-Javadoc)
511          * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl)
512          */
513         @Override
getServiceEvent(JmDNSImpl dns)514         public ServiceEvent getServiceEvent(JmDNSImpl dns) {
515             ServiceInfo info = this.getServiceInfo(false);
516             ((ServiceInfoImpl) info).setDns(dns);
517             String domainName = info.getType();
518             String serviceName = JmDNSImpl.toUnqualifiedName(domainName, this.getAlias());
519             return new ServiceEventImpl(dns, domainName, serviceName, info);
520         }
521 
522         /*
523          * (non-Javadoc)
524          * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder)
525          */
526         @Override
toString(StringBuilder aLog)527         protected void toString(StringBuilder aLog) {
528             super.toString(aLog);
529             aLog.append(" alias: '" + (_alias != null ? _alias.toString() : "null") + "'");
530         }
531 
532     }
533 
534     public final static byte[] EMPTY_TXT = new byte[] { 0 };
535 
536     public static class Text extends DNSRecord {
537         // private static Logger logger = Logger.getLogger(Text.class.getName());
538         private final byte[] _text;
539 
Text(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte text[])540         public Text(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte text[]) {
541             super(name, DNSRecordType.TYPE_TXT, recordClass, unique, ttl);
542             this._text = (text != null && text.length > 0 ? text : EMPTY_TXT);
543         }
544 
545         /**
546          * @return the text
547          */
getText()548         byte[] getText() {
549             return this._text;
550         }
551 
552         @Override
write(MessageOutputStream out)553         void write(MessageOutputStream out) {
554             out.writeBytes(_text, 0, _text.length);
555         }
556 
557         @Override
sameValue(DNSRecord other)558         boolean sameValue(DNSRecord other) {
559             if (! (other instanceof Text) ) {
560                 return false;
561             }
562             Text txt = (Text) other;
563             if ((_text == null) && (txt._text != null)) {
564                 return false;
565             }
566             if (txt._text.length != _text.length) {
567                 return false;
568             }
569             for (int i = _text.length; i-- > 0;) {
570                 if (txt._text[i] != _text[i]) {
571                     return false;
572                 }
573             }
574             return true;
575         }
576 
577         @Override
isSingleValued()578         public boolean isSingleValued() {
579             return true;
580         }
581 
582         @Override
handleQuery(JmDNSImpl dns, long expirationTime)583         boolean handleQuery(JmDNSImpl dns, long expirationTime) {
584             // Nothing to do (?)
585             // I think there is no possibility for conflicts for this record type?
586             return false;
587         }
588 
589         @Override
handleResponse(JmDNSImpl dns)590         boolean handleResponse(JmDNSImpl dns) {
591             // Nothing to do (?)
592             // Shouldn't we care if we get a conflict at this level?
593             /*
594              * ServiceInfo info = (ServiceInfo) dns.services.get(name.toLowerCase()); if (info != null) { if (! Arrays.equals(text,info.text)) { info.revertState(); return true; } }
595              */
596             return false;
597         }
598 
599         @Override
addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out)600         DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException {
601             return out;
602         }
603 
604         /*
605          * (non-Javadoc)
606          * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
607          */
608         @Override
getServiceInfo(boolean persistent)609         public ServiceInfo getServiceInfo(boolean persistent) {
610             return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, _text);
611         }
612 
613         /*
614          * (non-Javadoc)
615          * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl)
616          */
617         @Override
getServiceEvent(JmDNSImpl dns)618         public ServiceEvent getServiceEvent(JmDNSImpl dns) {
619             ServiceInfo info = this.getServiceInfo(false);
620             ((ServiceInfoImpl) info).setDns(dns);
621             return new ServiceEventImpl(dns, info.getType(), info.getName(), info);
622         }
623 
624         /*
625          * (non-Javadoc)
626          * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder)
627          */
628         @Override
toString(StringBuilder aLog)629         protected void toString(StringBuilder aLog) {
630             super.toString(aLog);
631             aLog.append(" text: '" + ((_text.length > 20) ? new String(_text, 0, 17) + "..." : new String(_text)) + "'");
632         }
633 
634     }
635 
636     /**
637      * Service record.
638      */
639     public static class Service extends DNSRecord {
640         private static Logger logger1 = Logger.getLogger(Service.class.getName());
641         private final int     _priority;
642         private final int     _weight;
643         private final int     _port;
644         private final String  _server;
645 
Service(String name, DNSRecordClass recordClass, boolean unique, int ttl, int priority, int weight, int port, String server)646         public Service(String name, DNSRecordClass recordClass, boolean unique, int ttl, int priority, int weight, int port, String server) {
647             super(name, DNSRecordType.TYPE_SRV, recordClass, unique, ttl);
648             this._priority = priority;
649             this._weight = weight;
650             this._port = port;
651             this._server = server;
652         }
653 
654         @Override
write(MessageOutputStream out)655         void write(MessageOutputStream out) {
656             out.writeShort(_priority);
657             out.writeShort(_weight);
658             out.writeShort(_port);
659             if (DNSIncoming.USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET) {
660                 out.writeName(_server);
661             } else {
662                 // [PJYF Nov 13 2010] Do we still need this? This looks really bad. All label are supposed to start by a length.
663                 out.writeUTF(_server, 0, _server.length());
664 
665                 // add a zero byte to the end just to be safe, this is the strange form
666                 // used by the BonjourConformanceTest
667                 out.writeByte(0);
668             }
669         }
670 
671         @Override
toByteArray(DataOutputStream dout)672         protected void toByteArray(DataOutputStream dout) throws IOException {
673             super.toByteArray(dout);
674             dout.writeShort(_priority);
675             dout.writeShort(_weight);
676             dout.writeShort(_port);
677             try {
678                 dout.write(_server.getBytes("UTF-8"));
679             } catch (UnsupportedEncodingException exception) {
680                 /* UTF-8 is always present */
681             }
682         }
683 
getServer()684         String getServer() {
685             return _server;
686         }
687 
688         /**
689          * @return the priority
690          */
getPriority()691         public int getPriority() {
692             return this._priority;
693         }
694 
695         /**
696          * @return the weight
697          */
getWeight()698         public int getWeight() {
699             return this._weight;
700         }
701 
702         /**
703          * @return the port
704          */
getPort()705         public int getPort() {
706             return this._port;
707         }
708 
709         @Override
sameValue(DNSRecord other)710         boolean sameValue(DNSRecord other) {
711             if (! (other instanceof Service) ) {
712                 return false;
713             }
714             Service s = (Service) other;
715             return (_priority == s._priority) && (_weight == s._weight) && (_port == s._port) && _server.equals(s._server);
716         }
717 
718         @Override
isSingleValued()719         public boolean isSingleValued() {
720             return true;
721         }
722 
723         @Override
handleQuery(JmDNSImpl dns, long expirationTime)724         boolean handleQuery(JmDNSImpl dns, long expirationTime) {
725             ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(this.getKey());
726             if (info != null && (info.isAnnouncing() || info.isAnnounced()) && (_port != info.getPort() || !_server.equalsIgnoreCase(dns.getLocalHost().getName()))) {
727                 logger1.finer("handleQuery() Conflicting probe detected from: " + getRecordSource());
728                 DNSRecord.Service localService = new DNSRecord.Service(info.getQualifiedName(), DNSRecordClass.CLASS_IN, DNSRecordClass.UNIQUE, DNSConstants.DNS_TTL, info.getPriority(), info.getWeight(), info.getPort(), dns.getLocalHost().getName());
729 
730                 // This block is useful for debugging race conditions when jmdns is responding to itself.
731                 try {
732                     if (dns.getInetAddress().equals(getRecordSource())) {
733                         logger1.warning("Got conflicting probe from ourselves\n" + "incoming: " + this.toString() + "\n" + "local   : " + localService.toString());
734                     }
735                 } catch (IOException e) {
736                     logger1.log(Level.WARNING, "IOException", e);
737                 }
738 
739                 int comparison = this.compareTo(localService);
740 
741                 if (comparison == 0) {
742                     // the 2 records are identical this probably means we are seeing our own record.
743                     // With multiple interfaces on a single computer it is possible to see our
744                     // own records come in on different interfaces than the ones they were sent on.
745                     // see section "10. Conflict Resolution" of mdns draft spec.
746                     logger1.finer("handleQuery() Ignoring a identical service query");
747                     return false;
748                 }
749 
750                 // Tie breaker test
751                 if (info.isProbing() && comparison > 0) {
752                     // We lost the tie break
753                     String oldName = info.getQualifiedName().toLowerCase();
754                     info.setName(dns.incrementName(info.getName()));
755                     dns.getServices().remove(oldName);
756                     dns.getServices().put(info.getQualifiedName().toLowerCase(), info);
757                     logger1.finer("handleQuery() Lost tie break: new unique name chosen:" + info.getName());
758 
759                     // We revert the state to start probing again with the new name
760                     info.revertState();
761                 } else {
762                     // We won the tie break, so this conflicting probe should be ignored
763                     // See paragraph 3 of section 9.2 in mdns draft spec
764                     return false;
765                 }
766 
767                 return true;
768 
769             }
770             return false;
771         }
772 
773         @Override
handleResponse(JmDNSImpl dns)774         boolean handleResponse(JmDNSImpl dns) {
775             ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(this.getKey());
776             if (info != null && (_port != info.getPort() || !_server.equalsIgnoreCase(dns.getLocalHost().getName()))) {
777                 logger1.finer("handleResponse() Denial detected");
778 
779                 if (info.isProbing()) {
780                     String oldName = info.getQualifiedName().toLowerCase();
781                     info.setName(dns.incrementName(info.getName()));
782                     dns.getServices().remove(oldName);
783                     dns.getServices().put(info.getQualifiedName().toLowerCase(), info);
784                     logger1.finer("handleResponse() New unique name chose:" + info.getName());
785 
786                 }
787                 info.revertState();
788                 return true;
789             }
790             return false;
791         }
792 
793         @Override
addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out)794         DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException {
795             ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(this.getKey());
796             if (info != null) {
797                 if (this._port == info.getPort() != _server.equals(dns.getLocalHost().getName())) {
798                     return dns.addAnswer(in, addr, port, out, new DNSRecord.Service(info.getQualifiedName(), DNSRecordClass.CLASS_IN, DNSRecordClass.UNIQUE, DNSConstants.DNS_TTL, info.getPriority(), info.getWeight(), info.getPort(), dns
799                             .getLocalHost().getName()));
800                 }
801             }
802             return out;
803         }
804 
805         /*
806          * (non-Javadoc)
807          * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
808          */
809         @Override
getServiceInfo(boolean persistent)810         public ServiceInfo getServiceInfo(boolean persistent) {
811             return new ServiceInfoImpl(this.getQualifiedNameMap(), _port, _weight, _priority, persistent, _server);
812         }
813 
814         /*
815          * (non-Javadoc)
816          * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl)
817          */
818         @Override
getServiceEvent(JmDNSImpl dns)819         public ServiceEvent getServiceEvent(JmDNSImpl dns) {
820             ServiceInfo info = this.getServiceInfo(false);
821             ((ServiceInfoImpl) info).setDns(dns);
822             // String domainName = "";
823             // String serviceName = this.getServer();
824             // int index = serviceName.indexOf('.');
825             // if (index > 0)
826             // {
827             // serviceName = this.getServer().substring(0, index);
828             // if (index + 1 < this.getServer().length())
829             // domainName = this.getServer().substring(index + 1);
830             // }
831             // return new ServiceEventImpl(dns, domainName, serviceName, info);
832             return new ServiceEventImpl(dns, info.getType(), info.getName(), info);
833 
834         }
835 
836         /*
837          * (non-Javadoc)
838          * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder)
839          */
840         @Override
toString(StringBuilder aLog)841         protected void toString(StringBuilder aLog) {
842             super.toString(aLog);
843             aLog.append(" server: '" + _server + ":" + _port + "'");
844         }
845 
846     }
847 
848     public static class HostInformation extends DNSRecord {
849         String _os;
850         String _cpu;
851 
852         /**
853          * @param name
854          * @param recordClass
855          * @param unique
856          * @param ttl
857          * @param cpu
858          * @param os
859          */
HostInformation(String name, DNSRecordClass recordClass, boolean unique, int ttl, String cpu, String os)860         public HostInformation(String name, DNSRecordClass recordClass, boolean unique, int ttl, String cpu, String os) {
861             super(name, DNSRecordType.TYPE_HINFO, recordClass, unique, ttl);
862             _cpu = cpu;
863             _os = os;
864         }
865 
866         /*
867          * (non-Javadoc)
868          * @see javax.jmdns.impl.DNSRecord#addAnswer(javax.jmdns.impl.JmDNSImpl, javax.jmdns.impl.DNSIncoming, java.net.InetAddress, int, javax.jmdns.impl.DNSOutgoing)
869          */
870         @Override
addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out)871         DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException {
872             return out;
873         }
874 
875         /*
876          * (non-Javadoc)
877          * @see javax.jmdns.impl.DNSRecord#handleQuery(javax.jmdns.impl.JmDNSImpl, long)
878          */
879         @Override
handleQuery(JmDNSImpl dns, long expirationTime)880         boolean handleQuery(JmDNSImpl dns, long expirationTime) {
881             return false;
882         }
883 
884         /*
885          * (non-Javadoc)
886          * @see javax.jmdns.impl.DNSRecord#handleResponse(javax.jmdns.impl.JmDNSImpl)
887          */
888         @Override
handleResponse(JmDNSImpl dns)889         boolean handleResponse(JmDNSImpl dns) {
890             return false;
891         }
892 
893         /*
894          * (non-Javadoc)
895          * @see javax.jmdns.impl.DNSRecord#sameValue(javax.jmdns.impl.DNSRecord)
896          */
897         @Override
sameValue(DNSRecord other)898         boolean sameValue(DNSRecord other) {
899             if (! (other instanceof HostInformation) ) {
900                 return false;
901             }
902             HostInformation hinfo = (HostInformation) other;
903             if ((_cpu == null) && (hinfo._cpu != null)) {
904                 return false;
905             }
906             if ((_os == null) && (hinfo._os != null)) {
907                 return false;
908             }
909             return _cpu.equals(hinfo._cpu) && _os.equals(hinfo._os);
910         }
911 
912         /*
913          * (non-Javadoc)
914          * @see javax.jmdns.impl.DNSRecord#isSingleValued()
915          */
916         @Override
isSingleValued()917         public boolean isSingleValued() {
918             return true;
919         }
920 
921         /*
922          * (non-Javadoc)
923          * @see javax.jmdns.impl.DNSRecord#write(javax.jmdns.impl.DNSOutgoing)
924          */
925         @Override
write(MessageOutputStream out)926         void write(MessageOutputStream out) {
927             String hostInfo = _cpu + " " + _os;
928             out.writeUTF(hostInfo, 0, hostInfo.length());
929         }
930 
931         /*
932          * (non-Javadoc)
933          * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
934          */
935         @Override
getServiceInfo(boolean persistent)936         public ServiceInfo getServiceInfo(boolean persistent) {
937             Map<String, String> hinfo = new HashMap<String, String>(2);
938             hinfo.put("cpu", _cpu);
939             hinfo.put("os", _os);
940             return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, hinfo);
941         }
942 
943         /*
944          * (non-Javadoc)
945          * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl)
946          */
947         @Override
getServiceEvent(JmDNSImpl dns)948         public ServiceEvent getServiceEvent(JmDNSImpl dns) {
949             ServiceInfo info = this.getServiceInfo(false);
950             ((ServiceInfoImpl) info).setDns(dns);
951             return new ServiceEventImpl(dns, info.getType(), info.getName(), info);
952         }
953 
954         /*
955          * (non-Javadoc)
956          * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder)
957          */
958         @Override
toString(StringBuilder aLog)959         protected void toString(StringBuilder aLog) {
960             super.toString(aLog);
961             aLog.append(" cpu: '" + _cpu + "' os: '" + _os + "'");
962         }
963 
964     }
965 
966     /**
967      * Determine if a record can have multiple values in the cache.
968      *
969      * @return <code>false</code> if this record can have multiple values in the cache, <code>true</code> otherwise.
970      */
isSingleValued()971     public abstract boolean isSingleValued();
972 
973     /**
974      * Return a service information associated with that record if appropriate.
975      *
976      * @return service information
977      */
getServiceInfo()978     public ServiceInfo getServiceInfo() {
979         return this.getServiceInfo(false);
980     }
981 
982     /**
983      * Return a service information associated with that record if appropriate.
984      *
985      * @param persistent
986      *            if <code>true</code> ServiceListener.resolveService will be called whenever new new information is received.
987      * @return service information
988      */
getServiceInfo(boolean persistent)989     public abstract ServiceInfo getServiceInfo(boolean persistent);
990 
991     /**
992      * Creates and return a service event for this record.
993      *
994      * @param dns
995      *            DNS serviced by this event
996      * @return service event
997      */
getServiceEvent(JmDNSImpl dns)998     public abstract ServiceEvent getServiceEvent(JmDNSImpl dns);
999 
setRecordSource(InetAddress source)1000     public void setRecordSource(InetAddress source) {
1001         this._source = source;
1002     }
1003 
getRecordSource()1004     public InetAddress getRecordSource() {
1005         return _source;
1006     }
1007 
1008     /*
1009      * (non-Javadoc)
1010      * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder)
1011      */
1012     @Override
toString(StringBuilder aLog)1013     protected void toString(StringBuilder aLog) {
1014         super.toString(aLog);
1015         aLog.append(" ttl: '" + getRemainingTTL(System.currentTimeMillis()) + "/" + _ttl + "'");
1016     }
1017 
setTTL(int ttl)1018     public void setTTL(int ttl) {
1019         this._ttl = ttl;
1020     }
1021 
getTTL()1022     public int getTTL() {
1023         return _ttl;
1024     }
1025 }
1026