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}