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