001/*
002 * Copyright 2015-2024 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.dnsmessage;
012
013import org.minidns.edns.Edns;
014import org.minidns.record.Data;
015import org.minidns.record.OPT;
016import org.minidns.record.Record;
017import org.minidns.record.Record.TYPE;
018
019import java.io.ByteArrayInputStream;
020import java.io.ByteArrayOutputStream;
021import java.io.DataInputStream;
022import java.io.DataOutputStream;
023import java.io.IOException;
024import java.io.OutputStream;
025import java.net.DatagramPacket;
026import java.net.InetAddress;
027import java.nio.ByteBuffer;
028import java.util.ArrayList;
029import java.util.Arrays;
030import java.util.Collection;
031import java.util.Collections;
032import java.util.Date;
033import java.util.HashMap;
034import java.util.HashSet;
035import java.util.List;
036import java.util.Map;
037import java.util.Set;
038import java.util.logging.Level;
039import java.util.logging.Logger;
040
041/**
042 * A DNS message as defined by RFC 1035. The message consists of a header and
043 * 4 sections: question, answer, nameserver and addition resource record
044 * section.
045 * A message can either be parsed ({@link #DnsMessage(byte[])}) or serialized
046 * ({@link DnsMessage#toArray()}).
047 * 
048 * @see <a href="https://www.ietf.org/rfc/rfc1035.txt">RFC 1035</a>
049 */
050public class DnsMessage {
051
052    private static final Logger LOGGER = Logger.getLogger(DnsMessage.class.getName());
053
054    /**
055     * Possible DNS response codes.
056     * 
057     * @see <a href=
058     *      "http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6">
059     *      IANA Domain Name System (DNS) Paramters - DNS RCODEs</a>
060     * @see <a href="http://tools.ietf.org/html/rfc6895#section-2.3">RFC 6895 § 2.3</a>
061     */
062    public enum RESPONSE_CODE {
063        NO_ERROR(0),
064        FORMAT_ERR(1),
065        SERVER_FAIL(2),
066        NX_DOMAIN(3),
067        NO_IMP(4),
068        REFUSED(5),
069        YXDOMAIN(6),
070        YXRRSET(7),
071        NXRRSET(8),
072        NOT_AUTH(9),
073        NOT_ZONE(10),
074        BADVERS_BADSIG(16),
075        BADKEY(17),
076        BADTIME(18),
077        BADMODE(19),
078        BADNAME(20),
079        BADALG(21),
080        BADTRUNC(22),
081        BADCOOKIE(23),
082        ;
083
084        /**
085         * Reverse lookup table for response codes.
086         */
087        private static final Map<Integer, RESPONSE_CODE> INVERSE_LUT = new HashMap<>(RESPONSE_CODE.values().length);
088
089        static {
090            for (RESPONSE_CODE responseCode : RESPONSE_CODE.values()) {
091                INVERSE_LUT.put((int) responseCode.value, responseCode);
092            }
093        }
094
095        /**
096         * The response code value.
097         */
098        private final byte value;
099
100        /**
101         * Create a new response code.
102         *
103         * @param value The response code value.
104         */
105        RESPONSE_CODE(int value) {
106            this.value = (byte) value;
107        }
108
109        /**
110         * Retrieve the byte value of the response code.
111         *
112         * @return the response code.
113         */
114        public byte getValue() {
115            return value;
116        }
117
118        /**
119         * Retrieve the response code for a byte value.
120         *
121         * @param value The byte value.
122         * @return The symbolic response code or null.
123         * @throws IllegalArgumentException if the value is not in the range of 0..15.
124         */
125        public static RESPONSE_CODE getResponseCode(int value) throws IllegalArgumentException {
126            if (value < 0 || value > 65535) {
127                throw new IllegalArgumentException();
128            }
129            return INVERSE_LUT.get(value);
130        }
131
132    }
133
134    /**
135     * Symbolic DNS Opcode values.
136     * 
137     * @see <a href=
138     *      "http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5">
139     *      IANA Domain Name System (DNS) Paramters - DNS OpCodes</a>
140     */
141    public enum OPCODE {
142        QUERY,
143        INVERSE_QUERY,
144        STATUS,
145        UNASSIGNED3,
146        NOTIFY,
147        UPDATE,
148        ;
149
150        /**
151         * Lookup table for for opcode resolution.
152         */
153        private static final OPCODE[] INVERSE_LUT = new OPCODE[OPCODE.values().length];
154
155        static {
156            for (OPCODE opcode : OPCODE.values()) {
157                if (INVERSE_LUT[opcode.getValue()] != null) {
158                    throw new IllegalStateException();
159                }
160                INVERSE_LUT[opcode.getValue()] = opcode;
161            }
162        }
163
164        /**
165         * The value of this opcode.
166         */
167        private final byte value;
168
169        /**
170         * Create a new opcode for a given byte value.
171         *
172         */
173        OPCODE() {
174            this.value = (byte) this.ordinal();
175        }
176
177        /**
178         * Retrieve the byte value of this opcode.
179         *
180         * @return The byte value of this opcode.
181         */
182        public byte getValue() {
183            return value;
184        }
185
186        /**
187         * Retrieve the symbolic name of an opcode byte.
188         *
189         * @param value The byte value of the opcode.
190         * @return The symbolic opcode or null.
191         * @throws IllegalArgumentException If the byte value is not in the
192         *                                  range 0..15.
193         */
194        public static OPCODE getOpcode(int value) throws IllegalArgumentException {
195            if (value < 0 || value > 15) {
196                throw new IllegalArgumentException();
197            }
198            if (value >= INVERSE_LUT.length) {
199                return null;
200            }
201            return INVERSE_LUT[value];
202        }
203
204    }
205
206    /**
207     * The DNS message id.
208     */
209    public final int id;
210
211    /**
212     * The DNS message opcode.
213     */
214    public final OPCODE opcode;
215
216    /**
217     * The response code of this dns message.
218     */
219    public final RESPONSE_CODE responseCode;
220
221    /**
222     * The QR flag of the DNS message header. Note that this will be <code>true</code> if the message is a
223     * <b>response</b> and <code>false</code> if it is a <b>query</b>.
224     * 
225     * @see <a href="https://www.ietf.org/rfc/rfc1035.txt">RFC 1035 § 4.1.1</a>
226     */
227    public final boolean qr;
228
229    /**
230     * True if this is a authorative response. If set, the responding nameserver is an authority for the domain name in
231     * the question section. Note that the answer section may have multiple owner names because of aliases. This flag
232     * corresponds to the name which matches the query name, or the first owner name in the query section.
233     *
234     * @see <a href="https://www.ietf.org/rfc/rfc1035.txt">RFC 1035 § 4.1.1. Header section format</a>
235     */
236    public final boolean authoritativeAnswer;
237
238    /**
239     * True if message is truncated. Then TCP should be used.
240     */
241    public final boolean truncated;
242
243    /**
244     * True if the server should recurse.
245     */
246    public final boolean recursionDesired;
247
248    /**
249     * True if recursion is possible.
250     */
251    public final boolean recursionAvailable;
252
253    /**
254     * True if the server regarded the response as authentic.
255     */
256    public final boolean authenticData;
257
258    /**
259     * True if the server should not perform DNSSEC validation before returning the result.
260     */
261    public final boolean checkingDisabled;
262
263    /**
264     * The question section content. Usually there will be only one question.
265     * <p>
266     * This list is unmodifiable.
267     * </p>
268     */
269    public final List<Question> questions;
270
271    /**
272     * The answers section records. Note that it is not guaranteed that all records found in this section will be direct
273     * answers to the question in the query. If DNSSEC is used, then this section also contains the RRSIG record.
274     * <p>
275     * This list is unmodifiable.
276     * </p>
277     */
278    public final List<Record<? extends Data>> answerSection;
279
280    /**
281     * The Authority Section. Note that it is not guaranteed that this section only contains nameserver records. If DNSSEC is used, then this section could also contain a NSEC(3) record.
282     * <p>
283     * This list is unmodifiable.
284     * </p>
285     */
286    public final List<Record<? extends Data>> authoritySection;
287
288    /**
289     * The additional section. It eventually contains RRs which relate to the query.
290     * <p>
291     * This list is unmodifiable.
292     * </p> 
293     */
294    public final List<Record<? extends Data>> additionalSection;
295
296    public final int optRrPosition;
297
298    /**
299     * The optional but very common EDNS information. Note that this field is lazily populated.
300     *
301     */
302    private Edns edns;
303
304    /**
305     * The receive timestamp. Set only if this message was created via parse.
306     * This should be used to evaluate TTLs.
307     */
308    public final long receiveTimestamp;
309
310    protected DnsMessage(Builder builder) {
311        this.id = builder.id;
312        this.opcode = builder.opcode;
313        this.responseCode = builder.responseCode;
314        this.receiveTimestamp = builder.receiveTimestamp;
315        this.qr = builder.query;
316        this.authoritativeAnswer = builder.authoritativeAnswer;
317        this.truncated = builder.truncated;
318        this.recursionDesired = builder.recursionDesired;
319        this.recursionAvailable = builder.recursionAvailable;
320        this.authenticData = builder.authenticData;
321        this.checkingDisabled = builder.checkingDisabled;
322
323        if (builder.questions == null) {
324            this.questions = Collections.emptyList();
325        } else {
326            List<Question> q = new ArrayList<>(builder.questions.size());
327            q.addAll(builder.questions);
328            this.questions = Collections.unmodifiableList(q);
329        }
330
331        if (builder.answerSection == null) {
332            this.answerSection = Collections.emptyList();
333        } else {
334            List<Record<? extends Data>> a = new ArrayList<>(builder.answerSection.size());
335            a.addAll(builder.answerSection);
336            this.answerSection = Collections.unmodifiableList(a);
337        }
338
339        if (builder.authoritySection == null) {
340            this.authoritySection = Collections.emptyList();
341        } else {
342            List<Record<? extends Data>> n = new ArrayList<>(builder.authoritySection.size());
343            n.addAll(builder.authoritySection);
344            this.authoritySection = Collections.unmodifiableList(n);
345        }
346
347        if (builder.additionalSection == null && builder.ednsBuilder == null) {
348            this.additionalSection = Collections.emptyList();
349        } else {
350            int size = 0;
351            if (builder.additionalSection != null) {
352                size += builder.additionalSection.size();
353            }
354            if (builder.ednsBuilder != null) {
355                size++;
356            }
357            List<Record<? extends Data>> a = new ArrayList<>(size);
358            if (builder.additionalSection != null) {
359                a.addAll(builder.additionalSection);
360            }
361            if (builder.ednsBuilder != null) {
362                Edns edns = builder.ednsBuilder.build();
363                this.edns = edns;
364                a.add(edns.asRecord());
365            }
366            this.additionalSection = Collections.unmodifiableList(a);
367        }
368
369        optRrPosition = getOptRrPosition(this.additionalSection);
370
371        if (optRrPosition != -1) {
372            // Verify that there are no further OPT records but the one we already found.
373            for (int i = optRrPosition + 1; i < this.additionalSection.size(); i++) {
374                if (this.additionalSection.get(i).type == TYPE.OPT) {
375                    throw new IllegalArgumentException("There must be only one OPT pseudo RR in the additional section");
376                }
377            }
378        }
379
380        // TODO Add verification of dns message state here
381    }
382
383    /**
384     * Build a DNS Message based on a binary DNS message.
385     *
386     * @param data The DNS message data.
387     * @throws IOException On read errors.
388     */
389    public DnsMessage(byte[] data) throws IOException {
390        ByteArrayInputStream bis = new ByteArrayInputStream(data);
391        DataInputStream dis = new DataInputStream(bis);
392        id = dis.readUnsignedShort();
393        int header = dis.readUnsignedShort();
394        qr = ((header >> 15) & 1) == 1;
395        opcode = OPCODE.getOpcode((header >> 11) & 0xf);
396        authoritativeAnswer = ((header >> 10) & 1) == 1;
397        truncated = ((header >> 9) & 1) == 1;
398        recursionDesired = ((header >> 8) & 1) == 1;
399        recursionAvailable = ((header >> 7) & 1) == 1;
400        authenticData = ((header >> 5) & 1) == 1;
401        checkingDisabled = ((header >> 4) & 1) == 1;
402        responseCode = RESPONSE_CODE.getResponseCode(header & 0xf);
403        receiveTimestamp = System.currentTimeMillis();
404        int questionCount = dis.readUnsignedShort();
405        int answerCount = dis.readUnsignedShort();
406        int nameserverCount = dis.readUnsignedShort();
407        int additionalResourceRecordCount = dis.readUnsignedShort();
408        questions = new ArrayList<>(questionCount);
409        for (int i = 0; i < questionCount; i++) {
410            questions.add(new Question(dis, data));
411        }
412        answerSection = new ArrayList<>(answerCount);
413        for (int i = 0; i < answerCount; i++) {
414            answerSection.add(Record.parse(dis, data));
415        }
416        authoritySection = new ArrayList<>(nameserverCount);
417        for (int i = 0; i < nameserverCount; i++) {
418            authoritySection.add(Record.parse(dis, data));
419        }
420        additionalSection = new ArrayList<>(additionalResourceRecordCount);
421        for (int i = 0; i < additionalResourceRecordCount; i++) {
422            additionalSection.add(Record.parse(dis, data));
423        }
424        optRrPosition = getOptRrPosition(additionalSection);
425    }
426
427    /**
428     * Constructs an normalized version of the given DnsMessage by setting the id to '0'.
429     *
430     * @param message the message of which normalized version should be constructed.
431     */
432    private DnsMessage(DnsMessage message) {
433        id = 0;
434        qr = message.qr;
435        opcode = message.opcode;
436        authoritativeAnswer = message.authoritativeAnswer;
437        truncated = message.truncated;
438        recursionDesired = message.recursionDesired;
439        recursionAvailable = message.recursionAvailable;
440        authenticData = message.authenticData;
441        checkingDisabled = message.checkingDisabled;
442        responseCode = message.responseCode;
443        receiveTimestamp = message.receiveTimestamp;
444        questions = message.questions;
445        answerSection = message.answerSection;
446        authoritySection = message.authoritySection;
447        additionalSection = message.additionalSection;
448        optRrPosition = message.optRrPosition;
449    }
450
451    private static int getOptRrPosition(List<Record<? extends Data>> additionalSection) {
452        int optRrPosition = -1;
453        for (int i = 0; i < additionalSection.size(); i++) {
454            Record<? extends Data> record = additionalSection.get(i);
455            if (record.type == Record.TYPE.OPT) {
456                optRrPosition = i;
457                break;
458            }
459        }
460        return optRrPosition;
461    }
462
463    /**
464     * Generate a binary dns packet out of this message.
465     *
466     * @return byte[] the binary representation.
467     */
468    public byte[] toArray() {
469        return serialize().clone();
470    }
471
472    public DatagramPacket asDatagram(InetAddress address, int port) {
473        byte[] bytes = serialize();
474        return new DatagramPacket(bytes, bytes.length, address, port);
475    }
476
477    public void writeTo(OutputStream outputStream) throws IOException {
478        writeTo(outputStream, true);
479    }
480
481    public void writeTo(OutputStream outputStream, boolean writeLength) throws IOException {
482        byte[] bytes = serialize();
483        DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
484        if (writeLength) {
485            dataOutputStream.writeShort(bytes.length);
486        }
487        dataOutputStream.write(bytes);
488    }
489
490    public ByteBuffer getInByteBuffer() {
491        byte[] bytes = serialize().clone();
492        return ByteBuffer.wrap(bytes);
493    }
494
495    private byte[] byteCache;
496
497    private byte[] serialize() {
498        if (byteCache != null) {
499            return byteCache;
500        }
501
502        ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
503        DataOutputStream dos = new DataOutputStream(baos);
504        int header = calculateHeaderBitmap();
505        try {
506            dos.writeShort((short) id);
507            dos.writeShort((short) header);
508            if (questions == null) {
509                dos.writeShort(0);
510            } else {
511                dos.writeShort((short) questions.size());
512            }
513            if (answerSection == null) {
514                dos.writeShort(0);
515            } else {
516                dos.writeShort((short) answerSection.size());
517            }
518            if (authoritySection == null) {
519                dos.writeShort(0);
520            } else {
521                dos.writeShort((short) authoritySection.size());
522            }
523            if (additionalSection == null) {
524                dos.writeShort(0);
525            } else {
526                dos.writeShort((short) additionalSection.size());
527            }
528            if (questions != null) {
529                for (Question question : questions) {
530                    dos.write(question.toByteArray());
531                }
532            }
533            if (answerSection != null) {
534                for (Record<? extends Data> answer : answerSection) {
535                    dos.write(answer.toByteArray());
536                }
537            }
538            if (authoritySection != null) {
539                for (Record<? extends Data> nameserverRecord : authoritySection) {
540                    dos.write(nameserverRecord.toByteArray());
541                }
542            }
543            if (additionalSection != null) {
544                for (Record<? extends Data> additionalResourceRecord : additionalSection) {
545                    dos.write(additionalResourceRecord.toByteArray());
546                }
547            }
548            dos.flush();
549        } catch (IOException e) {
550            // Should never happen.
551            throw new AssertionError(e);
552        }
553        byteCache = baos.toByteArray();
554        return byteCache;
555    }
556
557    int calculateHeaderBitmap() {
558        int header = 0;
559        if (qr) {
560            header += 1 << 15;
561        }
562        if (opcode != null) {
563            header += opcode.getValue() << 11;
564        }
565        if (authoritativeAnswer) {
566            header += 1 << 10;
567        }
568        if (truncated) {
569            header += 1 << 9;
570        }
571        if (recursionDesired) {
572            header += 1 << 8;
573        }
574        if (recursionAvailable) {
575            header += 1 << 7;
576        }
577        if (authenticData) {
578            header += 1 << 5;
579        }
580        if (checkingDisabled) {
581            header += 1 << 4;
582        }
583        if (responseCode != null) {
584            header += responseCode.getValue();
585        }
586        return header;
587    }
588
589    public Question getQuestion() {
590        return questions.get(0);
591    }
592
593    /**
594     * Copy the questions found in the question section.
595     *
596     * @return a copy of the question section questions.
597     * @see #questions
598     */
599    public List<Question> copyQuestions() {
600        List<Question> copy = new ArrayList<>(questions.size());
601        copy.addAll(questions);
602        return copy;
603    }
604
605    /**
606     * Copy the records found in the answer section into a new list.
607     *
608     * @return a copy of the answer section records.
609     * @see #answerSection
610     */
611    public List<Record<? extends Data>> copyAnswers() {
612        List<Record<? extends Data>> res = new ArrayList<>(answerSection.size());
613        res.addAll(answerSection);
614        return res;
615    }
616
617    /**
618     * Copy the records found in the authority section into a new list.
619     *
620     * @return a copy of the authority section records.
621     * @see #authoritySection
622     */
623    public List<Record<? extends Data>> copyAuthority() {
624        List<Record<? extends Data>> res = new ArrayList<>(authoritySection.size());
625        res.addAll(authoritySection);
626        return res;
627    }
628
629    public Edns getEdns() {
630        if (edns != null) return edns;
631
632        Record<OPT> optRecord = getOptPseudoRecord();
633        if (optRecord == null) return null;
634        edns = new Edns(optRecord);
635        return edns;
636    }
637
638    @SuppressWarnings("unchecked")
639    public Record<OPT> getOptPseudoRecord() {
640        if (optRrPosition == -1) return null;
641        return (Record<OPT>) additionalSection.get(optRrPosition);
642    }
643
644    /**
645     * Check if the EDNS DO (DNSSEC OK) flag is set.
646     *
647     * @return true if the DO flag is set.
648     */
649    public boolean isDnssecOk() {
650        Edns edns = getEdns();
651        if (edns == null)
652            return false;
653
654        return edns.dnssecOk;
655    }
656
657    private String toStringCache;
658
659    @Override
660    public String toString() {
661        if (toStringCache != null) return toStringCache;
662
663        StringBuilder sb = new StringBuilder("DnsMessage");
664        asBuilder().writeToStringBuilder(sb);
665
666        toStringCache = sb.toString();
667        return toStringCache;
668    }
669
670    private String terminalOutputCache;
671
672    /**
673     * Format the DnsMessage object in a way suitable for terminal output.
674     * The format is loosely based on the output provided by {@code dig}.
675     *
676     * @return This message as a String suitable for terminal output.
677     */
678    public String asTerminalOutput() {
679        if (terminalOutputCache != null) return terminalOutputCache;
680
681        StringBuilder sb = new StringBuilder(";; ->>HEADER<<-")
682                .append(" opcode: ").append(opcode)
683                .append(", status: ").append(responseCode)
684                .append(", id: ").append(id).append("\n")
685                .append(";; flags:");
686        if (!qr) sb.append(" qr");
687        if (authoritativeAnswer) sb.append(" aa");
688        if (truncated) sb.append(" tr");
689        if (recursionDesired) sb.append(" rd");
690        if (recursionAvailable) sb.append(" ra");
691        if (authenticData) sb.append(" ad");
692        if (checkingDisabled) sb.append(" cd");
693        sb.append("; QUERY: ").append(questions.size())
694                .append(", ANSWER: ").append(answerSection.size())
695                .append(", AUTHORITY: ").append(authoritySection.size())
696                .append(", ADDITIONAL: ").append(additionalSection.size())
697                .append("\n\n");
698        for (Record<? extends Data> record : additionalSection) {
699            Edns edns = Edns.fromRecord(record);
700            if (edns != null) {
701                sb.append(";; OPT PSEUDOSECTION:\n; ").append(edns.asTerminalOutput());
702                break;
703            }
704        }
705        if (questions.size() != 0) {
706            sb.append(";; QUESTION SECTION:\n");
707            for (Question question : questions) {
708                sb.append(';').append(question.toString()).append('\n');
709            }
710        }
711        if (authoritySection.size() != 0) {
712            sb.append("\n;; AUTHORITY SECTION:\n");
713            for (Record<? extends Data> record : authoritySection) {
714                sb.append(record.toString()).append('\n');
715            }
716        }
717        if (answerSection.size() != 0) {
718            sb.append("\n;; ANSWER SECTION:\n");
719            for (Record<? extends Data> record : answerSection) {
720                sb.append(record.toString()).append('\n');
721            }
722        }
723        if (additionalSection.size() != 0) {
724            boolean hasNonOptArr = false;
725            for (Record<? extends Data> record : additionalSection) {
726                if (record.type != Record.TYPE.OPT) {
727                    if (!hasNonOptArr) {
728                        hasNonOptArr = true;
729                        sb.append("\n;; ADDITIONAL SECTION:\n");
730                    }
731                    sb.append(record.toString()).append('\n');
732                }
733            }
734        }
735        if (receiveTimestamp > 0) {
736            sb.append("\n;; WHEN: ").append(new Date(receiveTimestamp).toString());
737        }
738        terminalOutputCache = sb.toString();
739        return terminalOutputCache;
740    }
741
742    public <D extends Data> Set<D> getAnswersFor(Question q) {
743        if (responseCode != RESPONSE_CODE.NO_ERROR) return null;
744
745        // It would be great if we could verify that D matches q.type at this
746        // point. But on the other hand, if it does not, then the cast to D
747        // below will fail.
748        Set<D> res = new HashSet<>(answerSection.size());
749        for (Record<? extends Data> record : answerSection) {
750            if (!record.isAnswer(q)) continue;
751
752            Data data = record.getPayload();
753            @SuppressWarnings("unchecked")
754            D d = (D) data;
755            boolean isNew = res.add(d);
756            if (!isNew) {
757                LOGGER.log(Level.WARNING, "DnsMessage contains duplicate answers. Record: " + record + "; DnsMessage: " + this);
758            }
759        }
760        return res;
761    }
762
763    private long answersMinTtlCache = -1;
764
765    /**
766     * Get the minimum TTL from all answers in seconds.
767     *
768     * @return the minimum TTL from all answers in seconds.
769     */
770    public long getAnswersMinTtl() {
771        if (answersMinTtlCache >= 0) {
772            return answersMinTtlCache;
773        }
774
775        answersMinTtlCache = Long.MAX_VALUE;
776        for (Record<? extends Data> r : answerSection) {
777            answersMinTtlCache = Math.min(answersMinTtlCache, r.ttl);
778        }
779        return answersMinTtlCache;
780    }
781
782    public Builder asBuilder() {
783        return new Builder(this);
784    }
785
786    private DnsMessage normalizedVersionCache;
787
788    public DnsMessage asNormalizedVersion() {
789        if (normalizedVersionCache == null) {
790            normalizedVersionCache = new DnsMessage(this);
791        }
792        return normalizedVersionCache;
793    }
794
795    public Builder getResponseBuilder(RESPONSE_CODE responseCode) {
796        if (qr) {
797            throw new IllegalStateException();
798        }
799        Builder responseBuilder = DnsMessage.builder()
800                .setQrFlag(true)
801                .setResponseCode(responseCode)
802                .setId(id)
803                .setQuestion(getQuestion());
804
805        return responseBuilder;
806    }
807
808    private transient Integer hashCodeCache;
809
810    @Override
811    public int hashCode() {
812        if (hashCodeCache == null) {
813            byte[] bytes = serialize();
814            hashCodeCache = Arrays.hashCode(bytes);
815        }
816        return hashCodeCache;
817    }
818
819    private enum SectionName {
820        answer,
821        authority,
822        additional,
823    }
824
825    private <D extends Data> List<Record<D>> filterSectionByType(boolean stopOnFirst, SectionName sectionName, Class<D> type) {
826        List<Record<?>> sectionToFilter;
827        switch (sectionName) {
828        case answer:
829            sectionToFilter = answerSection;
830            break;
831        case authority:
832            sectionToFilter = authoritySection;
833            break;
834        case additional:
835            sectionToFilter = additionalSection;
836            break;
837        default:
838            throw new AssertionError("Unknown section name " + sectionName);
839        }
840
841        List<Record<D>> res = new ArrayList<>(stopOnFirst ? 1 : sectionToFilter.size());
842
843        for (Record<?> record : sectionToFilter) {
844            Record<D> target = record.ifPossibleAs(type);
845            if (target != null) {
846                res.add(target);
847                if (stopOnFirst) {
848                    return res;
849                }
850            }
851        }
852
853        return res;
854    }
855
856    private <D extends Data> List<Record<D>> filterSectionByType(SectionName sectionName, Class<D> type) {
857        return filterSectionByType(false, sectionName, type);
858    }
859
860    private <D extends Data> Record<D> getFirstOfType(SectionName sectionName, Class<D> type) {
861        List<Record<D>> result = filterSectionByType(true, sectionName, type);
862        if (result.isEmpty()) {
863            return null;
864        }
865
866        return result.get(0);
867    }
868
869    public <D extends Data> List<Record<D>> filterAnswerSectionBy(Class<D> type) {
870        return filterSectionByType(SectionName.answer, type);
871    }
872
873    public <D extends Data> List<Record<D>> filterAuthoritySectionBy(Class<D> type) {
874        return filterSectionByType(SectionName.authority, type);
875    }
876
877    public <D extends Data> List<Record<D>> filterAdditionalSectionBy(Class<D> type) {
878        return filterSectionByType(SectionName.additional, type);
879    }
880
881    public <D extends Data> Record<D> getFirstOfTypeFromAnswerSection(Class<D> type) {
882        return getFirstOfType(SectionName.answer, type);
883    }
884
885    public <D extends Data> Record<D> getFirstOfTypeFromAuthoritySection(Class<D> type) {
886        return getFirstOfType(SectionName.authority, type);
887    }
888
889    public <D extends Data> Record<D> getFirstOfTypeFromAdditionalSection(Class<D> type) {
890        return getFirstOfType(SectionName.additional, type);
891    }
892
893    @Override
894    public boolean equals(Object other) {
895        if (!(other instanceof DnsMessage)) {
896            return false;
897        }
898        if (other == this) {
899            return true;
900        }
901        DnsMessage otherDnsMessage = (DnsMessage) other;
902        byte[] otherBytes = otherDnsMessage.serialize();
903        byte[] myBytes = serialize();
904        return Arrays.equals(myBytes, otherBytes);
905    }
906
907    public static Builder builder() {
908        return new DnsMessage.Builder();
909    }
910
911    public static final class Builder {
912
913        private Builder() {
914        }
915
916        private Builder(DnsMessage message) {
917            id = message.id;
918            opcode = message.opcode;
919            responseCode = message.responseCode;
920            query = message.qr;
921            authoritativeAnswer = message.authoritativeAnswer;
922            truncated = message.truncated;
923            recursionDesired = message.recursionDesired;
924            recursionAvailable = message.recursionAvailable;
925            authenticData = message.authenticData;
926            checkingDisabled = message.checkingDisabled;
927            receiveTimestamp = message.receiveTimestamp;
928
929            // Copy the unmodifiable lists over into this new builder.
930            questions = new ArrayList<>(message.questions.size());
931            questions.addAll(message.questions);
932            answerSection = new ArrayList<>(message.answerSection.size());
933            answerSection.addAll(message.answerSection);
934            authoritySection = new ArrayList<>(message.authoritySection.size());
935            authoritySection.addAll(message.authoritySection);
936            additionalSection = new ArrayList<>(message.additionalSection.size());
937            additionalSection.addAll(message.additionalSection);
938        }
939
940        private int id;
941        private OPCODE opcode = OPCODE.QUERY;
942        private RESPONSE_CODE responseCode = RESPONSE_CODE.NO_ERROR;
943        private boolean query;
944        private boolean authoritativeAnswer;
945        private boolean truncated;
946        private boolean recursionDesired;
947        private boolean recursionAvailable;
948        private boolean authenticData;
949        private boolean checkingDisabled;
950
951        private long receiveTimestamp = -1;
952
953        private List<Question> questions;
954        private List<Record<? extends Data>> answerSection;
955        private List<Record<? extends Data>> authoritySection;
956        private List<Record<? extends Data>> additionalSection;
957        private Edns.Builder ednsBuilder;
958
959        /**
960         * Set the current DNS message id.
961         *
962         * @param id The new DNS message id.
963         * @return a reference to this builder.
964         */
965        public Builder setId(int id) {
966            this.id = id & 0xffff;
967            return this;
968        }
969
970        public Builder setOpcode(OPCODE opcode) {
971            this.opcode = opcode;
972            return this;
973        }
974
975        public Builder setResponseCode(RESPONSE_CODE responseCode) {
976            this.responseCode = responseCode;
977            return this;
978        }
979
980        /**
981         * Set the QR flag. Note that this will be <code>true</code> if the message is a
982         * <b>response</b> and <code>false</code> if it is a <b>query</b>.
983         *
984         * @param query The new QR flag status.
985         * @return a reference to this builder.
986         */
987        public Builder setQrFlag(boolean query) {
988            this.query = query;
989            return this;
990        }
991
992        /**
993         * Set the authoritative answer flag.
994         *
995         * @param authoritativeAnswer Tge new authoritative answer value.
996         * @return a reference to this builder.
997         */
998        public Builder setAuthoritativeAnswer(boolean authoritativeAnswer) {
999            this.authoritativeAnswer = authoritativeAnswer;
1000            return this;
1001        }
1002
1003        /**
1004         * Set the truncation bit on this DNS message.
1005         *
1006         * @param truncated The new truncated bit status.
1007         * @return a reference to this builder.
1008         */
1009        public Builder setTruncated(boolean truncated) {
1010            this.truncated = truncated;
1011            return this;
1012        }
1013
1014        /**
1015         * Set the recursion desired flag on this message.
1016         *
1017         * @param recursionDesired The new recusrion setting.
1018         * @return a reference to this builder.
1019         */
1020        public Builder setRecursionDesired(boolean recursionDesired) {
1021            this.recursionDesired = recursionDesired;
1022            return this;
1023        }
1024
1025        /**
1026         * Set the recursion available flog from this DNS message.
1027         *
1028                 * @param recursionAvailable The new recursion available status.
1029         * @return a reference to this builder.
1030         */
1031        public Builder setRecursionAvailable(boolean recursionAvailable) {
1032            this.recursionAvailable = recursionAvailable;
1033            return this;
1034        }
1035
1036        /**
1037         * Set the authentic data flag on this DNS message.
1038         *
1039         * @param authenticData The new authentic data flag value.
1040         * @return a reference to this builder.
1041         */
1042        public Builder setAuthenticData(boolean authenticData) {
1043            this.authenticData = authenticData;
1044            return this;
1045        }
1046
1047        /**
1048         * Change the check status of this packet.
1049         *
1050         * @param checkingDisabled The new check disabled value.
1051         * @return a reference to this builder.
1052         */
1053        @Deprecated
1054        public Builder setCheckDisabled(boolean checkingDisabled) {
1055            this.checkingDisabled = checkingDisabled;
1056            return this;
1057        }
1058
1059        /**
1060         * Change the check status of this packet.
1061         *
1062         * @param checkingDisabled The new check disabled value.
1063         * @return a reference to this builder.
1064         */
1065        public Builder setCheckingDisabled(boolean checkingDisabled) {
1066            this.checkingDisabled = checkingDisabled;
1067            return this;
1068        }
1069
1070        public void copyFlagsFrom(DnsMessage dnsMessage) {
1071            this.query = dnsMessage.qr;
1072            this.authoritativeAnswer = dnsMessage.authenticData;
1073            this.truncated = dnsMessage.truncated;
1074            this.recursionDesired = dnsMessage.recursionDesired;
1075            this.recursionAvailable = dnsMessage.recursionAvailable;
1076            this.authenticData = dnsMessage.authenticData;
1077            this.checkingDisabled = dnsMessage.checkingDisabled;
1078        }
1079
1080        public Builder setReceiveTimestamp(long receiveTimestamp) {
1081            this.receiveTimestamp = receiveTimestamp;
1082            return this;
1083        }
1084
1085        public Builder addQuestion(Question question) {
1086            if (questions == null) {
1087                questions = new ArrayList<>(1);
1088            }
1089            questions.add(question);
1090            return this;
1091        }
1092
1093        /**
1094         * Set the question part of this message.
1095         *
1096         * @param questions The questions.
1097         * @return a reference to this builder.
1098         */
1099        public Builder setQuestions(List<Question> questions) {
1100            this.questions = questions;
1101            return this;
1102        }
1103
1104        /**
1105         * Set the question part of this message.
1106         *
1107         * @param question The question.
1108         * @return a reference to this builder.
1109         */
1110        public Builder setQuestion(Question question) {
1111            this.questions = new ArrayList<>(1);
1112            this.questions.add(question);
1113            return this;
1114        }
1115
1116        public Builder addAnswer(Record<? extends Data> answer) {
1117            if (answerSection == null) {
1118                answerSection = new ArrayList<>(1);
1119            }
1120            answerSection.add(answer);
1121            return this;
1122        }
1123
1124        public Builder addAnswers(Collection<Record<? extends Data>> records) {
1125            if (answerSection == null) {
1126                answerSection = new ArrayList<>(records.size());
1127            }
1128            answerSection.addAll(records);
1129            return this;
1130        }
1131
1132        public Builder setAnswers(Collection<Record<? extends Data>> records) {
1133            answerSection = new ArrayList<>(records.size());
1134            answerSection.addAll(records);
1135            return this;
1136        }
1137
1138        public List<Record<? extends Data>> getAnswers() {
1139            if (answerSection == null) {
1140                return Collections.emptyList();
1141            }
1142            return answerSection;
1143        }
1144
1145        public Builder addNameserverRecords(Record<? extends Data> record) {
1146            if (authoritySection == null) {
1147                authoritySection = new ArrayList<>(8);
1148            }
1149            authoritySection.add(record);
1150            return this;
1151        }
1152
1153        public Builder setNameserverRecords(Collection<Record<? extends Data>> records) {
1154            authoritySection = new ArrayList<>(records.size());
1155            authoritySection.addAll(records);
1156            return this;
1157        }
1158
1159        public Builder setAdditionalResourceRecords(Collection<Record<? extends Data>> records) {
1160            additionalSection = new ArrayList<>(records.size());
1161            additionalSection.addAll(records);
1162            return this;
1163        }
1164
1165        public Builder addAdditionalResourceRecord(Record<? extends Data> record) {
1166            if (additionalSection == null) {
1167                additionalSection = new ArrayList<>();
1168            }
1169            additionalSection.add(record);
1170            return this;
1171        }
1172
1173        public Builder addAdditionalResourceRecords(List<Record<? extends Data>> records) {
1174            if (additionalSection == null) {
1175                additionalSection = new ArrayList<>(records.size());
1176            }
1177            additionalSection.addAll(records);
1178            return this;
1179        }
1180
1181        public List<Record<? extends Data>> getAdditionalResourceRecords() {
1182            if (additionalSection == null) {
1183                return Collections.emptyList();
1184            }
1185            return additionalSection;
1186        }
1187
1188        /**
1189         * Get the @{link EDNS} builder. If no builder has been set so far, then a new one will be created.
1190         * <p>
1191         * The EDNS record can be used to announce the supported size of UDP payload as well as additional flags.
1192         * </p>
1193         * <p>
1194         * Note that some networks and firewalls are known to block big UDP payloads. 1280 should be a reasonable value,
1195         * everything below 512 is treated as 512 and should work on all networks.
1196         * </p>
1197         * 
1198         * @return a EDNS builder.
1199         */
1200        public Edns.Builder getEdnsBuilder() {
1201            if (ednsBuilder == null) {
1202                ednsBuilder = Edns.builder();
1203            }
1204            return ednsBuilder;
1205        }
1206
1207        public DnsMessage build() {
1208            return new DnsMessage(this);
1209        }
1210
1211        private void writeToStringBuilder(StringBuilder sb) {
1212            sb.append('(')
1213                .append(id)
1214                .append(' ')
1215                .append(opcode)
1216                .append(' ')
1217                .append(responseCode)
1218                .append(' ');
1219            if (query) {
1220                sb.append("resp[qr=1]");
1221            } else {
1222                sb.append("query[qr=0]");
1223            }
1224            if (authoritativeAnswer)
1225                sb.append(" aa");
1226            if (truncated)
1227                sb.append(" tr");
1228            if (recursionDesired)
1229                sb.append(" rd");
1230            if (recursionAvailable)
1231                sb.append(" ra");
1232            if (authenticData)
1233                sb.append(" ad");
1234            if (checkingDisabled)
1235                sb.append(" cd");
1236            sb.append(")\n");
1237            if (questions != null) {
1238                for (Question question : questions) {
1239                    sb.append("[Q: ").append(question).append("]\n");
1240                }
1241            }
1242            if (answerSection != null) {
1243                for (Record<? extends Data> record : answerSection) {
1244                    sb.append("[A: ").append(record).append("]\n");
1245                }
1246            }
1247            if (authoritySection != null) {
1248                for (Record<? extends Data> record : authoritySection) {
1249                    sb.append("[N: ").append(record).append("]\n");
1250                }
1251            }
1252            if (additionalSection != null) {
1253                for (Record<? extends Data> record : additionalSection) {
1254                    sb.append("[X: ");
1255                    Edns edns = Edns.fromRecord(record);
1256                    if (edns != null) {
1257                        sb.append(edns.toString());
1258                    } else {
1259                        sb.append(record);
1260                    }
1261                    sb.append("]\n");
1262                }
1263            }
1264
1265            // Strip trailing newline.
1266            if (sb.charAt(sb.length() - 1) == '\n') {
1267                sb.setLength(sb.length() - 1);
1268            }
1269        }
1270
1271        @Override
1272        public String toString() {
1273            StringBuilder sb = new StringBuilder("Builder of DnsMessage");
1274            writeToStringBuilder(sb);
1275            return sb.toString();
1276        }
1277    }
1278
1279}