1// Copyright 2003-2005 Arthur van Hoff, Rick Blair
2// Licensed under Apache License version 2.0
3// Original license LGPL
4
5package javax.jmdns.impl;
6
7import java.io.DataOutputStream;
8import java.io.IOException;
9import java.io.UnsupportedEncodingException;
10import java.net.Inet4Address;
11import java.net.Inet6Address;
12import java.net.InetAddress;
13import java.net.UnknownHostException;
14import java.util.HashMap;
15import java.util.Map;
16import java.util.logging.Level;
17import java.util.logging.Logger;
18
19import javax.jmdns.ServiceEvent;
20import javax.jmdns.ServiceInfo;
21import javax.jmdns.ServiceInfo.Fields;
22import javax.jmdns.impl.DNSOutgoing.MessageOutputStream;
23import javax.jmdns.impl.constants.DNSConstants;
24import javax.jmdns.impl.constants.DNSRecordClass;
25import javax.jmdns.impl.constants.DNSRecordType;
26
27/**
28 * DNS record
29 *
30 * @author Arthur van Hoff, Rick Blair, Werner Randelshofer, Pierre Frisch
31 */
32public 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