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.ByteArrayOutputStream;
8import java.io.DataOutputStream;
9import java.io.IOException;
10import java.util.Collections;
11import java.util.Map;
12
13import javax.jmdns.ServiceInfo.Fields;
14import javax.jmdns.impl.constants.DNSRecordClass;
15import 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 */
22public 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     */
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
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     */
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     */
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     */
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     */
105    public String getName() {
106        return (_name != null ? _name : "");
107    }
108
109    /**
110     * @return the type
111     */
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     */
121    public String getKey() {
122        return (_key != null ? _key : "");
123    }
124
125    /**
126     * @return record type
127     */
128    public DNSRecordType getRecordType() {
129        return (_recordType != null ? _recordType : DNSRecordType.TYPE_IGNORE);
130    }
131
132    /**
133     * @return record class
134     */
135    public DNSRecordClass getRecordClass() {
136        return (_dnsClass != null ? _dnsClass : DNSRecordClass.CLASS_UNKNOWN);
137    }
138
139    /**
140     * @return true if unique
141     */
142    public boolean isUnique() {
143        return _unique;
144    }
145
146    public Map<Fields, String> getQualifiedNameMap() {
147        return Collections.unmodifiableMap(_qualifiedNameMap);
148    }
149
150    public boolean isServicesDiscoveryMetaQuery() {
151        return _qualifiedNameMap.get(Fields.Application).equals("dns-sd") && _qualifiedNameMap.get(Fields.Instance).equals("_services");
152    }
153
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
168    public boolean isReverseLookup() {
169        return this.isV4ReverseLookup() || this.isV6ReverseLookup();
170    }
171
172    public boolean isV4ReverseLookup() {
173        return _qualifiedNameMap.get(Fields.Domain).endsWith("in-addr.arpa");
174    }
175
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     */
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     */
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     */
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     */
214    public boolean isSameType(DNSEntry entry) {
215        return (entry != null) && (entry.getRecordType() == this.getRecordType());
216    }
217
218    /**
219     * @param dout
220     * @exception IOException
221     */
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     */
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     */
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
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
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     */
292    protected void toString(StringBuilder aLog) {
293        // Stub
294    }
295
296}
297