001/*
002 * Copyright 2015-2018 the original author or authors
003 *
004 * This software is licensed under the Apache License, Version 2.0,
005 * the GNU Lesser General Public License version 2 or later ("LGPL")
006 * and the WTFPL.
007 * You may choose either license to govern your use of this software only
008 * upon the condition that you accept all of the terms of either
009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
010 */
011package org.minidns.record;
012
013import java.io.ByteArrayOutputStream;
014import java.io.DataInputStream;
015import java.io.DataOutputStream;
016import java.io.IOException;
017import java.util.ArrayList;
018import java.util.Collection;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022
023import org.minidns.dnsmessage.DnsMessage;
024import org.minidns.dnsmessage.Question;
025import org.minidns.dnsname.DnsName;
026
027/**
028 * A generic DNS record.
029 */
030public final class Record<D extends Data> {
031
032    /**
033     * The resource record type.
034     * 
035     * @see <a href=
036     *      "http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4">
037     *      IANA DNS Parameters - Resource Record (RR) TYPEs</a>
038     */
039    public static enum TYPE {
040        UNKNOWN(-1),
041        A(1, A.class),
042        NS(2, NS.class),
043        MD(3),
044        MF(4),
045        CNAME(5, CNAME.class),
046        SOA(6, SOA.class),
047        MB(7),
048        MG(8),
049        MR(9),
050        NULL(10),
051        WKS(11),
052        PTR(12, PTR.class),
053        HINFO(13),
054        MINFO(14),
055        MX(15, MX.class),
056        TXT(16, TXT.class),
057        RP(17),
058        AFSDB(18),
059        X25(19),
060        ISDN(20),
061        RT(21),
062        NSAP(22),
063        NSAP_PTR(23),
064        SIG(24),
065        KEY(25),
066        PX(26),
067        GPOS(27),
068        AAAA(28, AAAA.class),
069        LOC(29),
070        NXT(30),
071        EID(31),
072        NIMLOC(32),
073        SRV(33, SRV.class),
074        ATMA(34),
075        NAPTR(35),
076        KX(36),
077        CERT(37),
078        A6(38),
079        DNAME(39, DNAME.class),
080        SINK(40),
081        OPT(41, OPT.class),
082        APL(42),
083        DS(43, DS.class),
084        SSHFP(44),
085        IPSECKEY(45),
086        RRSIG(46, RRSIG.class),
087        NSEC(47, NSEC.class),
088        DNSKEY(48, DNSKEY.class),
089        DHCID(49),
090        NSEC3(50, NSEC3.class),
091        NSEC3PARAM(51, NSEC3PARAM.class),
092        TLSA(52, TLSA.class),
093        HIP(55),
094        NINFO(56),
095        RKEY(57),
096        TALINK(58),
097        CDS(59),
098        CDNSKEY(60),
099        OPENPGPKEY(61, OPENPGPKEY.class),
100        CSYNC(62),
101        SPF(99),
102        UINFO(100),
103        UID(101),
104        GID(102),
105        UNSPEC(103),
106        NID(104),
107        L32(105),
108        L64(106),
109        LP(107),
110        EUI48(108),
111        EUI64(109),
112        TKEY(249),
113        TSIG(250),
114        IXFR(251),
115        AXFR(252),
116        MAILB(253),
117        MAILA(254),
118        ANY(255),
119        URI(256),
120        CAA(257),
121        TA(32768),
122        DLV(32769, DLV.class),
123        ;
124
125        /**
126         * The value of this DNS record type.
127         */
128        private final int value;
129
130        private final Class<?> dataClass;
131
132        /**
133         * Internal lookup table to map values to types.
134         */
135        private final static Map<Integer, TYPE> INVERSE_LUT = new HashMap<>();
136
137        private final static Map<Class<?>, TYPE> DATA_LUT = new HashMap<>();
138
139        static {
140            // Initialize the reverse lookup table.
141            for(TYPE t: TYPE.values()) {
142                INVERSE_LUT.put(t.getValue(), t);
143                if (t.dataClass != null) {
144                    DATA_LUT.put(t.dataClass, t);
145                }
146            }
147        }
148
149        /**
150         * Create a new record type.
151         * 
152         * @param value The binary value of this type.
153         */
154        private TYPE(int value) {
155            this(value, null);
156        }
157
158        /**
159         * Create a new record type.
160         *
161         * @param <D> The class for this type.
162         * @param dataClass The class for this type.
163         * @param value The binary value of this type.
164         */
165        private <D extends Data> TYPE(int value, Class<D> dataClass) {
166            this.value = value;
167            this.dataClass = dataClass;
168        }
169
170        /**
171         * Retrieve the binary value of this type.
172         * @return The binary value.
173         */
174        public int getValue() {
175            return value;
176        }
177
178        /**
179         * Get the {@link Data} class for this type.
180         *
181         * @param <D> The class for this type.
182         * @return the {@link Data} class for this type.
183         */
184        @SuppressWarnings("unchecked")
185        public <D extends Data> Class<D> getDataClass() {
186            return (Class<D>) dataClass;
187        }
188
189        /**
190         * Retrieve the symbolic type of the binary value.
191         * @param value The binary type value.
192         * @return The symbolic tpye.
193         */
194        public static TYPE getType(int value) {
195            TYPE type = INVERSE_LUT.get(value);
196            if (type == null) return UNKNOWN;
197            return type;
198        }
199
200        /**
201         * Retrieve the type for a given {@link Data} class.
202         *
203         * @param <D> The class for this type.
204         * @param dataClass the class to lookup the type for.
205         * @return the type for the given data class.
206         */
207        public static <D extends Data> TYPE getType(Class<D> dataClass) {
208            return DATA_LUT.get(dataClass);
209        }
210    }
211
212    /**
213     * The symbolic class of a DNS record (usually {@link CLASS#IN} for Internet).
214     *
215     * @see <a href="http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2">IANA Domain Name System (DNS) Parameters - DNS CLASSes</a>
216     */
217    public static enum CLASS {
218
219        /**
220         * The Internet class. This is the most common class used by todays DNS systems.
221         */
222        IN(1),
223
224        /**
225         * The Chaos class.
226         */
227        CH(3),
228
229        /**
230         * The Hesiod class.
231         */
232        HS(4),
233        NONE(254),
234        ANY(255);
235
236        /**
237         * Internal reverse lookup table to map binary class values to symbolic
238         * names.
239         */
240        private final static HashMap<Integer, CLASS> INVERSE_LUT =
241                                            new HashMap<Integer, CLASS>();
242
243        static {
244            // Initialize the interal reverse lookup table.
245            for(CLASS c: CLASS.values()) {
246                INVERSE_LUT.put(c.getValue(), c);
247            }
248        }
249
250        /**
251         * The binary value of this dns class.
252         */
253        private final int value;
254
255        /**
256         * Create a new DNS class based on a binary value.
257         * @param value The binary value of this DNS class.
258         */
259        private CLASS(int value) {
260            this.value = value;
261        }
262
263        /**
264         * Retrieve the binary value of this DNS class.
265         * @return The binary value of this DNS class.
266         */
267        public int getValue() {
268            return value;
269        }
270
271        /**
272         * Retrieve the symbolic DNS class for a binary class value.
273         * @param value The binary DNS class value.
274         * @return The symbolic class instance.
275         */
276        public static CLASS getClass(int value) {
277            return INVERSE_LUT.get(value);
278        }
279
280    }
281
282    /**
283     * The generic name of this record.
284     */
285    public final DnsName name;
286
287    /**
288     * The type (and payload type) of this record.
289     */
290    public final TYPE type;
291
292    /**
293     * The record class (usually CLASS.IN).
294     */
295    public final CLASS clazz;
296
297    /**
298     * The value of the class field of a RR.
299     * 
300     * According to RFC 2671 (OPT RR) this is not necessarily representable
301     * using clazz field and unicastQuery bit
302     */
303    public final int clazzValue;
304
305    /**
306     * The ttl of this record.
307     */
308    public final long ttl;
309
310    /**
311     * The payload object of this record.
312     */
313    public final D payloadData;
314
315    /**
316     * MDNS defines the highest bit of the class as the unicast query bit.
317     */
318    public final boolean unicastQuery;
319
320    /**
321     * Parse a given record based on the full message data and the current
322     * stream position.
323     *
324     * @param dis The DataInputStream positioned at the first record byte.
325     * @param data The full message data.
326     * @return the record which was parsed.
327     * @throws IOException In case of malformed replies.
328     */
329    public static Record<Data> parse(DataInputStream dis, byte[] data) throws IOException {
330        DnsName name = DnsName.parse(dis, data);
331        int typeValue = dis.readUnsignedShort();
332        TYPE type = TYPE.getType(typeValue);
333        int clazzValue = dis.readUnsignedShort();
334        CLASS clazz = CLASS.getClass(clazzValue & 0x7fff);
335        boolean unicastQuery = (clazzValue & 0x8000) > 0;
336        long ttl = (((long)dis.readUnsignedShort()) << 16) +
337                   dis.readUnsignedShort();
338        int payloadLength = dis.readUnsignedShort();
339        Data payloadData;
340        switch (type) {
341            case SOA:
342                payloadData = SOA.parse(dis, data);
343                break;
344            case SRV:
345                payloadData = SRV.parse(dis, data);
346                break;
347            case MX:
348                payloadData = MX.parse(dis, data);
349                break;
350            case AAAA:
351                payloadData = AAAA.parse(dis);
352                break;
353            case A:
354                payloadData = A.parse(dis);
355                break;
356            case NS:
357                payloadData = NS.parse(dis, data);
358                break;
359            case CNAME:
360                payloadData = CNAME.parse(dis, data);
361                break;
362            case DNAME:
363                payloadData = DNAME.parse(dis, data);
364                break;
365            case PTR:
366                payloadData = PTR.parse(dis, data);
367                break;
368            case TXT:
369                payloadData = TXT.parse(dis, payloadLength);
370                break;
371            case OPT:
372                payloadData = OPT.parse(dis, payloadLength);
373                break;
374            case DNSKEY:
375                payloadData = DNSKEY.parse(dis, payloadLength);
376                break;
377            case RRSIG:
378                payloadData = RRSIG.parse(dis, data, payloadLength);
379                break;
380            case DS:
381                payloadData = DS.parse(dis, payloadLength);
382                break;
383            case NSEC:
384                payloadData = NSEC.parse(dis, data, payloadLength);
385                break;
386            case NSEC3:
387                payloadData = NSEC3.parse(dis, payloadLength);
388                break;
389            case NSEC3PARAM:
390                payloadData = NSEC3PARAM.parse(dis);
391                break;
392            case TLSA:
393                payloadData = TLSA.parse(dis, payloadLength);
394                break;
395            case OPENPGPKEY:
396                payloadData = OPENPGPKEY.parse(dis, payloadLength);
397                break;
398            case DLV:
399                payloadData = DLV.parse(dis, payloadLength);
400                break;
401            case UNKNOWN:
402            default:
403                payloadData = UNKNOWN.parse(dis, payloadLength, type);
404                break;
405        }
406        return new Record<>(name, type, clazz, clazzValue, ttl, payloadData, unicastQuery);
407    }
408
409    public Record(DnsName name, TYPE type, CLASS clazz, long ttl, D payloadData, boolean unicastQuery) {
410        this(name, type, clazz, clazz.getValue() + (unicastQuery ? 0x8000 : 0), ttl, payloadData, unicastQuery);
411    }
412
413    public Record(String name, TYPE type, CLASS clazz, long ttl, D payloadData, boolean unicastQuery) {
414        this(DnsName.from(name), type, clazz, ttl, payloadData, unicastQuery);
415    }
416
417    public Record(String name, TYPE type, int clazzValue, long ttl, D payloadData) {
418        this(DnsName.from(name), type, CLASS.NONE, clazzValue, ttl, payloadData, false);
419    }
420
421    public Record(DnsName name, TYPE type, int clazzValue, long ttl, D payloadData) {
422        this(name, type, CLASS.NONE, clazzValue, ttl, payloadData, false);
423    }
424
425    private Record(DnsName name, TYPE type, CLASS clazz, int clazzValue, long ttl, D payloadData, boolean unicastQuery) {
426        this.name = name;
427        this.type = type;
428        this.clazz = clazz;
429        this.clazzValue = clazzValue;
430        this.ttl = ttl;
431        this.payloadData = payloadData;
432        this.unicastQuery = unicastQuery;
433    }
434
435    public void toOutputStream(DataOutputStream dos) throws IOException {
436        if (payloadData == null) {
437            throw new IllegalStateException("Empty Record has no byte representation");
438        }
439
440        name.writeToStream(dos);
441        dos.writeShort(type.getValue());
442        dos.writeShort(clazzValue);
443        dos.writeInt((int) ttl);
444
445        dos.writeShort(payloadData.length());
446        payloadData.toOutputStream(dos);
447    }
448
449    private byte[] bytes;
450
451    public byte[] toByteArray() {
452        if (bytes == null) {
453            ByteArrayOutputStream baos = new ByteArrayOutputStream(name.size() + 8 + payloadData.length());
454            DataOutputStream dos = new DataOutputStream(baos);
455            try {
456                toOutputStream(dos);
457            } catch (IOException e) {
458                // Should never happen.
459                throw new AssertionError(e);
460            }
461            bytes = baos.toByteArray();
462        }
463        return bytes.clone();
464    }
465
466    /**
467     * Retrieve a textual representation of this resource record.
468     * @return String
469     */
470    @Override
471    public String toString() {
472        return name.getRawAce() + ".\t" + ttl + '\t' + clazz + '\t' + type + '\t' + payloadData;
473    }
474
475    /**
476     * Check if this record answers a given query.
477     * @param q The query.
478     * @return True if this record is a valid answer.
479     */
480    public boolean isAnswer(Question q) {
481        return ((q.type == type) || (q.type == TYPE.ANY)) &&
482               ((q.clazz == clazz) || (q.clazz == CLASS.ANY)) &&
483               (q.name.equals(name));
484    }
485
486    /**
487     * See if this query/response was a unicast query (highest class bit set).
488     * @return True if it is a unicast query/response record.
489     */
490    public boolean isUnicastQuery() {
491        return unicastQuery;
492    }
493
494    /**
495     * The payload data, usually a subclass of data (A, AAAA, CNAME, ...).
496     * @return The payload data.
497     */
498    public D getPayload() {
499        return payloadData;
500    }
501
502    /**
503     * Retrieve the record ttl.
504     * @return The record ttl.
505     */
506    public long getTtl() {
507        return ttl;
508    }
509
510    /**
511     * Get the question asking for this resource record. This will return <code>null</code> if the record is not retrievable, i.e.
512     * {@link TYPE#OPT}.
513     *
514     * @return the question for this resource record or <code>null</code>.
515     */
516    public Question getQuestion() {
517        switch (type) {
518        case OPT:
519            // OPT records are not retrievable.
520            return null;
521        case RRSIG:
522            RRSIG rrsig = (RRSIG) payloadData;
523            return new Question(name, rrsig.typeCovered, clazz);
524        default:
525            return new Question(name, type, clazz);
526        }
527    }
528
529    public DnsMessage.Builder getQuestionMessage() {
530        Question question = getQuestion();
531        if (question == null) {
532            return null;
533        }
534        return question.asMessageBuilder();
535    }
536
537    private transient Integer hashCodeCache;
538
539    @Override
540    public int hashCode() {
541        if (hashCodeCache == null) {
542            int hashCode = 1;
543            hashCode = 37 * hashCode + name.hashCode();
544            hashCode = 37 * hashCode + type.hashCode();
545            hashCode = 37 * hashCode + clazz.hashCode();
546            hashCode = 37 * hashCode + payloadData.hashCode();
547            hashCodeCache = hashCode;
548        }
549        return hashCodeCache;
550    }
551
552    @Override
553    public boolean equals(Object other) {
554        if (!(other instanceof Record)) {
555            return false;
556        }
557        if (other == this) {
558            return true;
559        }
560        Record<?> otherRecord = (Record<?>) other;
561        if (!name.equals(otherRecord.name)) return false;
562        if (type != otherRecord.type) return false;
563        if (clazz != otherRecord.clazz) return false;
564        // Note that we do not compare the TTL here, since we consider two Records with everything but the TTL equal to
565        // be equal too.
566        if (!payloadData.equals(otherRecord.payloadData)) return false;
567
568        return true;
569    }
570
571    /**
572     * Return the record if possible as record with the given {@link Data} class. If the record does not hold payload of
573     * the given data class type, then {@code null} will be returned.
574     *
575     * @param dataClass a class of the {@link Data} type.
576     * @param <E> a subtype of {@link Data}.
577     * @return the record with a specialized payload type or {@code null}.
578     */
579    @SuppressWarnings("unchecked")
580    public <E extends Data> Record<E> ifPossibleAs(Class<E> dataClass) {
581        if (type.dataClass == dataClass) {
582            return (Record<E>) this;
583        }
584        return null;
585    }
586
587    public static <E extends Data> void filter(Collection<Record<E>> result, Class<E> dataClass,
588            Collection<Record<? extends Data>> input) {
589        for (Record<? extends Data> record : input) {
590            Record<E> filteredRecord = record.ifPossibleAs(dataClass);
591            if (filteredRecord == null)
592                continue;
593
594            result.add(filteredRecord);
595        }
596    }
597
598    public static <E extends Data> List<Record<E>> filter(Class<E> dataClass,
599            Collection<Record<? extends Data>> input) {
600        List<Record<E>> result = new ArrayList<>(input.size());
601        filter(result, dataClass, input);
602        return result;
603    }
604}