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.ByteArrayOutputStream;
8 import java.io.DataOutputStream;
9 import java.io.IOException;
10 import java.util.Collections;
11 import java.util.Map;
12 
13 import javax.jmdns.ServiceInfo.Fields;
14 import javax.jmdns.impl.constants.DNSRecordClass;
15 import javax.jmdns.impl.constants.DNSRecordType;
16 
17 /**
18  * DNS entry with a name, type, and class. This is the base class for questions and records.
19  *
20  * @author Arthur van Hoff, Pierre Frisch, Rick Blair
21  */
22 public abstract class DNSEntry {
23     // private static Logger logger = Logger.getLogger(DNSEntry.class.getName());
24     private final String         _key;
25 
26     private final String         _name;
27 
28     private final String         _type;
29 
30     private final DNSRecordType  _recordType;
31 
32     private final DNSRecordClass _dnsClass;
33 
34     private final boolean        _unique;
35 
36     final Map<Fields, String>    _qualifiedNameMap;
37 
38     /**
39      * Create an entry.
40      */
DNSEntry(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique)41     DNSEntry(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique) {
42         _name = name;
43         // _key = (name != null ? name.trim().toLowerCase() : null);
44         _recordType = type;
45         _dnsClass = recordClass;
46         _unique = unique;
47         _qualifiedNameMap = ServiceInfoImpl.decodeQualifiedNameMapForType(this.getName());
48         String domain = _qualifiedNameMap.get(Fields.Domain);
49         String protocol = _qualifiedNameMap.get(Fields.Protocol);
50         String application = _qualifiedNameMap.get(Fields.Application);
51         String instance = _qualifiedNameMap.get(Fields.Instance).toLowerCase();
52         _type = (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + ".";
53         _key = ((instance.length() > 0 ? instance + "." : "") + _type).toLowerCase();
54     }
55 
56     /*
57      * (non-Javadoc)
58      * @see java.lang.Object#equals(java.lang.Object)
59      */
60     @Override
equals(Object obj)61     public boolean equals(Object obj) {
62         boolean result = false;
63         if (obj instanceof DNSEntry) {
64             DNSEntry other = (DNSEntry) obj;
65             result = this.getKey().equals(other.getKey()) && this.getRecordType().equals(other.getRecordType()) && this.getRecordClass() == other.getRecordClass();
66         }
67         return result;
68     }
69 
70     /**
71      * Check if two entries have exactly the same name, type, and class.
72      *
73      * @param entry
74      * @return <code>true</code> if the two entries have are for the same record, <code>false</code> otherwise
75      */
isSameEntry(DNSEntry entry)76     public boolean isSameEntry(DNSEntry entry) {
77         return this.getKey().equals(entry.getKey()) && this.getRecordType().equals(entry.getRecordType()) && ((DNSRecordClass.CLASS_ANY == entry.getRecordClass()) || this.getRecordClass().equals(entry.getRecordClass()));
78     }
79 
80     /**
81      * Check if two entries have the same subtype.
82      *
83      * @param other
84      * @return <code>true</code> if the two entries have are for the same subtype, <code>false</code> otherwise
85      */
sameSubtype(DNSEntry other)86     public boolean sameSubtype(DNSEntry other) {
87         return this.getSubtype().equals(other.getSubtype());
88     }
89 
90     /**
91      * Returns the subtype of this entry
92      *
93      * @return subtype of this entry
94      */
getSubtype()95     public String getSubtype() {
96         String subtype = this.getQualifiedNameMap().get(Fields.Subtype);
97         return (subtype != null ? subtype : "");
98     }
99 
100     /**
101      * Returns the name of this entry
102      *
103      * @return name of this entry
104      */
getName()105     public String getName() {
106         return (_name != null ? _name : "");
107     }
108 
109     /**
110      * @return the type
111      */
getType()112     public String getType() {
113         return (_type != null ? _type : "");
114     }
115 
116     /**
117      * Returns the key for this entry. The key is the lower case name.
118      *
119      * @return key for this entry
120      */
getKey()121     public String getKey() {
122         return (_key != null ? _key : "");
123     }
124 
125     /**
126      * @return record type
127      */
getRecordType()128     public DNSRecordType getRecordType() {
129         return (_recordType != null ? _recordType : DNSRecordType.TYPE_IGNORE);
130     }
131 
132     /**
133      * @return record class
134      */
getRecordClass()135     public DNSRecordClass getRecordClass() {
136         return (_dnsClass != null ? _dnsClass : DNSRecordClass.CLASS_UNKNOWN);
137     }
138 
139     /**
140      * @return true if unique
141      */
isUnique()142     public boolean isUnique() {
143         return _unique;
144     }
145 
getQualifiedNameMap()146     public Map<Fields, String> getQualifiedNameMap() {
147         return Collections.unmodifiableMap(_qualifiedNameMap);
148     }
149 
isServicesDiscoveryMetaQuery()150     public boolean isServicesDiscoveryMetaQuery() {
151         return _qualifiedNameMap.get(Fields.Application).equals("dns-sd") && _qualifiedNameMap.get(Fields.Instance).equals("_services");
152     }
153 
isDomainDiscoveryQuery()154     public boolean isDomainDiscoveryQuery() {
155         // b._dns-sd._udp.<domain>.
156         // db._dns-sd._udp.<domain>.
157         // r._dns-sd._udp.<domain>.
158         // dr._dns-sd._udp.<domain>.
159         // lb._dns-sd._udp.<domain>.
160 
161         if (_qualifiedNameMap.get(Fields.Application).equals("dns-sd")) {
162             String name = _qualifiedNameMap.get(Fields.Instance);
163             return "b".equals(name) || "db".equals(name) || "r".equals(name) || "dr".equals(name) || "lb".equals(name);
164         }
165         return false;
166     }
167 
isReverseLookup()168     public boolean isReverseLookup() {
169         return this.isV4ReverseLookup() || this.isV6ReverseLookup();
170     }
171 
isV4ReverseLookup()172     public boolean isV4ReverseLookup() {
173         return _qualifiedNameMap.get(Fields.Domain).endsWith("in-addr.arpa");
174     }
175 
isV6ReverseLookup()176     public boolean isV6ReverseLookup() {
177         return _qualifiedNameMap.get(Fields.Domain).endsWith("ip6.arpa");
178     }
179 
180     /**
181      * Check if the record is stale, i.e. it has outlived more than half of its TTL.
182      *
183      * @param now
184      *            update date
185      * @return <code>true</code> is the record is stale, <code>false</code> otherwise.
186      */
isStale(long now)187     public abstract boolean isStale(long now);
188 
189     /**
190      * Check if the record is expired.
191      *
192      * @param now
193      *            update date
194      * @return <code>true</code> is the record is expired, <code>false</code> otherwise.
195      */
isExpired(long now)196     public abstract boolean isExpired(long now);
197 
198     /**
199      * Check that 2 entries are of the same class.
200      *
201      * @param entry
202      * @return <code>true</code> is the two class are the same, <code>false</code> otherwise.
203      */
isSameRecordClass(DNSEntry entry)204     public boolean isSameRecordClass(DNSEntry entry) {
205         return (entry != null) && (entry.getRecordClass() == this.getRecordClass());
206     }
207 
208     /**
209      * Check that 2 entries are of the same type.
210      *
211      * @param entry
212      * @return <code>true</code> is the two type are the same, <code>false</code> otherwise.
213      */
isSameType(DNSEntry entry)214     public boolean isSameType(DNSEntry entry) {
215         return (entry != null) && (entry.getRecordType() == this.getRecordType());
216     }
217 
218     /**
219      * @param dout
220      * @exception IOException
221      */
toByteArray(DataOutputStream dout)222     protected void toByteArray(DataOutputStream dout) throws IOException {
223         dout.write(this.getName().getBytes("UTF8"));
224         dout.writeShort(this.getRecordType().indexValue());
225         dout.writeShort(this.getRecordClass().indexValue());
226     }
227 
228     /**
229      * 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.
230      *
231      * @return byte array representation
232      */
toByteArray()233     protected byte[] toByteArray() {
234         try {
235             ByteArrayOutputStream bout = new ByteArrayOutputStream();
236             DataOutputStream dout = new DataOutputStream(bout);
237             this.toByteArray(dout);
238             dout.close();
239             return bout.toByteArray();
240         } catch (IOException e) {
241             throw new InternalError();
242         }
243     }
244 
245     /**
246      * Does a lexicographic comparison of the byte array representation of this record and that record. This is needed for tie-break tests according to draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2.
247      *
248      * @param that
249      * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
250      */
compareTo(DNSEntry that)251     public int compareTo(DNSEntry that) {
252         byte[] thisBytes = this.toByteArray();
253         byte[] thatBytes = that.toByteArray();
254         for (int i = 0, n = Math.min(thisBytes.length, thatBytes.length); i < n; i++) {
255             if (thisBytes[i] > thatBytes[i]) {
256                 return 1;
257             } else if (thisBytes[i] < thatBytes[i]) {
258                 return -1;
259             }
260         }
261         return thisBytes.length - thatBytes.length;
262     }
263 
264     /**
265      * Overriden, to return a value which is consistent with the value returned by equals(Object).
266      */
267     @Override
hashCode()268     public int hashCode() {
269         return this.getKey().hashCode() + this.getRecordType().indexValue() + this.getRecordClass().indexValue();
270     }
271 
272     /*
273      * (non-Javadoc)
274      * @see java.lang.Object#toString()
275      */
276     @Override
toString()277     public String toString() {
278         StringBuilder aLog = new StringBuilder(200);
279         aLog.append("[" + this.getClass().getSimpleName() + "@" + System.identityHashCode(this));
280         aLog.append(" type: " + this.getRecordType());
281         aLog.append(", class: " + this.getRecordClass());
282         aLog.append((_unique ? "-unique," : ","));
283         aLog.append(" name: " + _name);
284         this.toString(aLog);
285         aLog.append("]");
286         return aLog.toString();
287     }
288 
289     /**
290      * @param aLog
291      */
toString(StringBuilder aLog)292     protected void toString(StringBuilder aLog) {
293         // Stub
294     }
295 
296 }
297