Home | History | Annotate | Download | only in impl
      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.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
    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 }
   1026