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}