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