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 java.io.ByteArrayOutputStream;
014import java.io.DataInputStream;
015import java.io.DataOutputStream;
016import java.io.IOException;
017import java.util.Arrays;
018
019import org.minidns.dnsname.DnsName;
020import org.minidns.record.Record.CLASS;
021import org.minidns.record.Record.TYPE;
022
023/**
024 * A DNS question (request).
025 */
026public class Question {
027
028    /**
029     * The question string (e.g. "measite.de").
030     */
031    public final DnsName name;
032
033    /**
034     * The question type (e.g. A).
035     */
036    public final TYPE type;
037
038    /**
039     * The question class (usually IN for Internet).
040     */
041    public final CLASS clazz;
042
043    /**
044     * UnicastQueries have the highest bit of the CLASS field set to 1.
045     */
046    private final boolean unicastQuery;
047
048    /**
049     * Cache for the serialized object.
050     */
051    private byte[] byteArray;
052
053    /**
054     * Create a dns question for the given name/type/class.
055     * @param name The name e.g. "measite.de".
056     * @param type The type, e.g. A.
057     * @param clazz The class, usually IN (internet).
058     * @param unicastQuery True if this is a unicast query.
059     */
060    public Question(CharSequence name, TYPE type, CLASS clazz, boolean unicastQuery) {
061        this(DnsName.from(name), type, clazz, unicastQuery);
062    }
063
064    public Question(DnsName name, TYPE type, CLASS clazz, boolean unicastQuery) {
065        assert name != null;
066        assert type != null;
067        assert clazz != null;
068        this.name = name;
069        this.type = type;
070        this.clazz = clazz;
071        this.unicastQuery = unicastQuery;
072    }
073
074    /**
075     * Create a dns question for the given name/type/class.
076     * @param name The name e.g. "measite.de".
077     * @param type The type, e.g. A.
078     * @param clazz The class, usually IN (internet).
079     */
080    public Question(DnsName name, TYPE type, CLASS clazz) {
081        this(name, type, clazz, false);
082    }
083
084    /**
085     * Create a dns question for the given name/type/IN (internet class).
086     * @param name The name e.g. "measite.de".
087     * @param type The type, e.g. A.
088     */
089    public Question(DnsName name, TYPE type) {
090        this(name, type, CLASS.IN);
091    }
092
093    /**
094     * Create a dns question for the given name/type/class.
095     * @param name The name e.g. "measite.de".
096     * @param type The type, e.g. A.
097     * @param clazz The class, usually IN (internet).
098     */
099    public Question(CharSequence name, TYPE type, CLASS clazz) {
100        this(DnsName.from(name), type, clazz);
101    }
102
103    /**
104     * Create a dns question for the given name/type/IN (internet class).
105     * @param name The name e.g. "measite.de".
106     * @param type The type, e.g. A.
107     */
108    public Question(CharSequence name, TYPE type) {
109        this(DnsName.from(name), type);
110    }
111
112    /**
113     * Parse a byte array and rebuild the dns question from it.
114     * @param dis The input stream.
115     * @param data The plain data (for dns name references).
116     * @throws IOException On errors (read outside of packet).
117     */
118    public Question(DataInputStream dis, byte[] data) throws IOException {
119        name = DnsName.parse(dis, data);
120        type = TYPE.getType(dis.readUnsignedShort());
121        clazz = CLASS.getClass(dis.readUnsignedShort());
122        unicastQuery = false;
123    }
124
125    /**
126     * Generate a binary paket for this dns question.
127     * @return The dns question.
128     */
129    public byte[] toByteArray() {
130        if (byteArray == null) {
131            ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
132            DataOutputStream dos = new DataOutputStream(baos);
133
134            try {
135                name.writeToStream(dos);
136                dos.writeShort(type.getValue());
137                dos.writeShort(clazz.getValue() | (unicastQuery ? (1 << 15) : 0));
138                dos.flush();
139            } catch (IOException e) {
140                // Should never happen
141                throw new RuntimeException(e);
142            }
143            byteArray = baos.toByteArray();
144        }
145        return byteArray;
146    }
147
148    @Override
149    public int hashCode() {
150        return Arrays.hashCode(toByteArray());
151    }
152
153    @Override
154    public boolean equals(Object other) {
155        if (this == other) {
156            return true;
157        }
158        if (!(other instanceof Question)) {
159            return false;
160        }
161        byte[] t = toByteArray();
162        byte[] o = ((Question) other).toByteArray();
163        return Arrays.equals(t, o);
164    }
165
166    @Override
167    public String toString() {
168        return name.getRawAce() + ".\t" + clazz + '\t' + type;
169    }
170
171    public DnsMessage.Builder asMessageBuilder() {
172        DnsMessage.Builder builder = DnsMessage.builder();
173        builder.setQuestion(this);
174        return builder;
175    }
176
177    public DnsMessage asQueryMessage() {
178        return asMessageBuilder().build();
179    }
180}