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.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       */
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
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       */
63      abstract boolean sameValue(DNSRecord other);
64  
65      /**
66       * True if this record has the same type as some other record.
67       */
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       */
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       */
84      abstract boolean handleResponse(JmDNSImpl dns);
85  
86      /**
87       * Adds this as an answer to the provided outgoing datagram.
88       */
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       */
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      */
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      */
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      */
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
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
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      */
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      */
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      */
171     abstract void write(MessageOutputStream out);
172 
173     public static class IPv4Address extends Address {
174 
175         IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) {
176             super(name, DNSRecordType.TYPE_A, recordClass, unique, ttl, addr);
177         }
178 
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
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
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 
217         IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) {
218             super(name, DNSRecordType.TYPE_AAAA, recordClass, unique, ttl, addr);
219         }
220 
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
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
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 
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 
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 
282         boolean same(DNSRecord other) {
283             if (! (other instanceof Address) ) {
284                 return false;
285             }
286             return ((sameName(other)) && ((sameValue(other))));
287         }
288 
289         boolean sameName(DNSRecord other) {
290             return this.getName().equalsIgnoreCase(other.getName());
291         }
292 
293         @Override
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
306         public boolean isSingleValued() {
307             return false;
308         }
309 
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
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
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
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
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
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
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
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 
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
439         public boolean isSameEntry(DNSEntry entry) {
440             return super.isSameEntry(entry) && (entry instanceof Pointer) && this.sameValue((Pointer) entry);
441         }
442 
443         @Override
444         void write(MessageOutputStream out) {
445             out.writeName(_alias);
446         }
447 
448         @Override
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
461         public boolean isSingleValued() {
462             return false;
463         }
464 
465         @Override
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
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 
479         String getAlias() {
480             return _alias;
481         }
482 
483         @Override
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
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
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
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 
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          */
548         byte[] getText() {
549             return this._text;
550         }
551 
552         @Override
553         void write(MessageOutputStream out) {
554             out.writeBytes(_text, 0, _text.length);
555         }
556 
557         @Override
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
578         public boolean isSingleValued() {
579             return true;
580         }
581 
582         @Override
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
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
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
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
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
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 
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
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
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 
684         String getServer() {
685             return _server;
686         }
687 
688         /**
689          * @return the priority
690          */
691         public int getPriority() {
692             return this._priority;
693         }
694 
695         /**
696          * @return the weight
697          */
698         public int getWeight() {
699             return this._weight;
700         }
701 
702         /**
703          * @return the port
704          */
705         public int getPort() {
706             return this._port;
707         }
708 
709         @Override
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
719         public boolean isSingleValued() {
720             return true;
721         }
722 
723         @Override
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.getInterface().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
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
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
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
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
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          */
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
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
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
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
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
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
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
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
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
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      */
971     public abstract boolean isSingleValued();
972 
973     /**
974      * Return a service information associated with that record if appropriate.
975      *
976      * @return service information
977      */
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      */
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      */
998     public abstract ServiceEvent getServiceEvent(JmDNSImpl dns);
999 
1000     public void setRecordSource(InetAddress source) {
1001         this._source = source;
1002     }
1003 
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
1013     protected void toString(StringBuilder aLog) {
1014         super.toString(aLog);
1015         aLog.append(" ttl: '" + getRemainingTTL(System.currentTimeMillis()) + "/" + _ttl + "'");
1016     }
1017 
1018     public void setTTL(int ttl) {
1019         this._ttl = ttl;
1020     }
1021 
1022     public int getTTL() {
1023         return _ttl;
1024     }
1025 }