Record.java

  1package de.measite.minidns;
  2
  3import java.io.DataInputStream;
  4import java.io.IOException;
  5import java.util.HashMap;
  6import java.util.logging.Level;
  7import java.util.logging.Logger;
  8
  9import de.measite.minidns.record.A;
 10import de.measite.minidns.record.AAAA;
 11import de.measite.minidns.record.CNAME;
 12import de.measite.minidns.record.Data;
 13import de.measite.minidns.record.NS;
 14import de.measite.minidns.record.PTR;
 15import de.measite.minidns.record.SRV;
 16import de.measite.minidns.record.TXT;
 17import de.measite.minidns.util.NameUtil;
 18
 19/**
 20 * A generic DNS record.
 21 */
 22public class Record {
 23
 24    private static final Logger LOGGER = Logger.getLogger(Client.class.getName());
 25
 26    /**
 27     * The record type.
 28     * @see <a href="http://www.iana.org/assignments/dns-parameters">IANA DNS Parameters</a>
 29     */
 30    public static enum TYPE {
 31        A(1),
 32        NS(2),
 33        MD(3),
 34        MF(4),
 35        CNAME(5),
 36        SOA(6),
 37        MB(7),
 38        MG(8),
 39        MR(9),
 40        NULL(10),
 41        WKS(11),
 42        PTR(12),
 43        HINFO(13),
 44        MINFO(14),
 45        MX(15),
 46        TXT(16),
 47        RP(17),
 48        AFSDB(18),
 49        X25(19),
 50        ISDN(20),
 51        RT(21),
 52        NSAP(22),
 53        NSAP_PTR(23),
 54        SIG(24),
 55        KEY(25),
 56        PX(26),
 57        GPOS(27),
 58        AAAA(28),
 59        LOC(29),
 60        NXT(30),
 61        EID(31),
 62        NIMLOC(32),
 63        SRV(33),
 64        ATMA(34),
 65        NAPTR(35),
 66        KX(36),
 67        CERT(37),
 68        A6(38),
 69        DNAME(39),
 70        SINK(40),
 71        OPT(41),
 72        APL(42),
 73        DS(43),
 74        SSHFP(44),
 75        IPSECKEY(45),
 76        RRSIG(46),
 77        NSEC(47),
 78        DNSKEY(48),
 79        DHCID(49),
 80        NSEC3(50),
 81        NSEC3PARAM(51),
 82        HIP(55),
 83        NINFO(56),
 84        RKEY(57),
 85        TALINK(58),
 86        SPF(99),
 87        UINFO(100),
 88        UID(101),
 89        GID(102),
 90        TKEY(249),
 91        TSIG(250),
 92        IXFR(251),
 93        AXFR(252),
 94        MAILB(253),
 95        MAILA(254),
 96        ANY(255),
 97        TA(32768),
 98        DLV(32769);
 99
100        /**
101         * The value of this DNS record type.
102         */
103        private final int value;
104
105        /**
106         * Internal lookup table to map values to types.
107         */
108        private final static HashMap<Integer, TYPE> INVERSE_LUT =
109                                        new HashMap<Integer, TYPE>();
110
111        /**
112         * Initialize the reverse lookup table.
113         */
114        static {
115            for(TYPE t: TYPE.values()) {
116                INVERSE_LUT.put(t.getValue(), t);
117            }
118        }
119
120        /**
121         * Create a new record type.
122         * @param value The binary value of this type.
123         */
124        private TYPE(int value) {
125            this.value = value;
126        }
127
128        /**
129         * Retrieve the binary value of this type.
130         * @return The binary value.
131         */
132        public int getValue() {
133            return value;
134        }
135
136        /**
137         * Retrieve the symbolic type of the binary value.
138         * @param value The binary type value.
139         * @return The symbolic tpye.
140         */
141        public static TYPE getType(int value) {
142            return INVERSE_LUT.get(value);
143        }
144    };
145
146    /**
147     * The symbolic class of a DNS record (usually IN for Internet).
148     */
149    public static enum CLASS {
150        IN(1),
151        CH(3),
152        HS(4),
153        NONE(254),
154        ANY(255);
155
156        /**
157         * Internal reverse lookup table to map binary class values to symbolic
158         * names.
159         */
160        private final static HashMap<Integer, CLASS> INVERSE_LUT =
161                                            new HashMap<Integer, CLASS>();
162
163        /**
164         * Initialize the interal reverse lookup table.
165         */
166        static {
167            for(CLASS c: CLASS.values()) {
168                INVERSE_LUT.put(c.getValue(), c);
169            }
170        }
171
172        /**
173         * The binary value of this dns class.
174         */
175        private final int value;
176
177        /**
178         * Create a new DNS class based on a binary value.
179         * @param value The binary value of this DNS class.
180         */
181        private CLASS(int value) {
182            this.value = value;
183        }
184
185        /**
186         * Retrieve the binary value of this DNS class.
187         * @return The binary value of this DNS class.
188         */
189        public int getValue() {
190            return value;
191        }
192
193        /**
194         * Retrieve the symbolic DNS class for a binary class value.
195         * @param value The binary DNS class value.
196         * @return The symbolic class instance.
197         */
198        public static CLASS getClass(int value) {
199            return INVERSE_LUT.get(value);
200        }
201
202    }
203
204    /**
205     * The generic name of this record.
206     */
207    protected String name;
208
209    /**
210     * The type (and payload type) of this record.
211     */
212    protected TYPE type;
213
214    /**
215     * The record class (usually CLASS.IN).
216     */
217    protected CLASS clazz;
218
219    /**
220     * The ttl of this record.
221     */
222    protected long ttl;
223
224    /**
225     * The payload object of this record.
226     */
227    protected Data payloadData;
228
229    /**
230     * MDNS defines the highest bit of the class as the unicast query bit.
231     */
232    protected boolean unicastQuery;
233
234    /**
235     * Parse a given record based on the full message data and the current
236     * stream position.
237     * @param dis The DataInputStream positioned at the first record byte.
238     * @param data The full message data.
239     * @throws IOException In case of malformed replies.
240     */
241    public void parse(DataInputStream dis, byte[] data) throws IOException {
242        this.name = NameUtil.parse(dis, data);
243        this.type = TYPE.getType(dis.readUnsignedShort());
244        int clazzValue = dis.readUnsignedShort();
245        this.clazz = CLASS.getClass(clazzValue & 0x7fff);
246        this.unicastQuery = (clazzValue & 0x8000) > 0;
247        if (this.clazz == null) {
248            LOGGER.log(Level.FINE, "Unknown class " + clazzValue);
249        }
250        this.ttl = (((long)dis.readUnsignedShort()) << 32) +
251                   dis.readUnsignedShort();
252        int payloadLength = dis.readUnsignedShort();
253        switch (this.type) {
254        case SRV:
255            this.payloadData = new SRV();
256            break;
257        case AAAA:
258            this.payloadData = new AAAA();
259            break;
260        case A:
261            this.payloadData = new A();
262            break;
263        case NS:
264            this.payloadData = new NS();
265            break;
266        case CNAME:
267            this.payloadData = new CNAME();
268            break;
269        case PTR:
270            this.payloadData = new PTR();
271            break;
272        case TXT:
273            this.payloadData = new TXT();
274            break;
275        default:
276            LOGGER.log(Level.FINE, "Unparsed type " + type);
277            this.payloadData = null;
278            for (int i = 0; i < payloadLength; i++) {
279                dis.readByte();
280            }
281            break;
282        }
283        if (this.payloadData != null) {
284            this.payloadData.parse(dis, data, payloadLength);
285        }
286    }
287
288    /**
289     * Retrieve a textual representation of this resource record.
290     * @return String
291     */
292    @Override
293    public String toString() {
294        if (payloadData == null) {
295            return "RR " + type + "/" + clazz;
296        }
297        return "RR " + type + "/" + clazz + ": " + payloadData.toString();
298    };
299
300    /**
301     * Check if this record answers a given query.
302     * @param q The query.
303     * @return True if this record is a valid answer.
304     */
305    public boolean isAnswer(Question q) {
306        return ((q.getType() == type) || (q.getType() == TYPE.ANY)) &&
307               ((q.getClazz() == clazz) || (q.getClazz() == CLASS.ANY)) &&
308               (q.getName().equals(name));
309    }
310
311    /**
312     * See if this query/response was a unicast query (highest class bit set).
313     * @return True if it is a unicast query/response record.
314     */
315    public boolean isUnicastQuery() {
316        return unicastQuery;
317    }
318
319    /**
320     * The generic record name, e.g. "measite.de".
321     * @return The record name.
322     */
323    public String getName() {
324        return name;
325    }
326
327    /**
328     * The payload data, usually a subclass of data (A, AAAA, CNAME, ...).
329     * @return The payload data.
330     */
331    public Data getPayload() {
332        return payloadData;
333    }
334
335    /**
336     * Retrieve the record ttl.
337     * @return The record ttl.
338     */
339    public long getTtl() {
340        return ttl;
341    }
342
343}