1package de.measite.minidns;
2
3import java.io.DataInputStream;
4import java.io.IOException;
5import java.util.HashMap;
6import java.util.logging.Level;
7import java.util.logging.Logger;
8
9import de.measite.minidns.record.A;
10import de.measite.minidns.record.AAAA;
11import de.measite.minidns.record.CNAME;
12import de.measite.minidns.record.Data;
13import de.measite.minidns.record.NS;
14import de.measite.minidns.record.PTR;
15import de.measite.minidns.record.SRV;
16import de.measite.minidns.record.TXT;
17import de.measite.minidns.util.NameUtil;
18
19/**
20 * A generic DNS record.
21 */
22public class Record {
23
24 private static final Logger LOGGER = Logger.getLogger(Client.class.getName());
25
26 /**
27 * The record type.
28 * @see <a href="http://www.iana.org/assignments/dns-parameters">IANA DNS Parameters</a>
29 */
30 public static enum TYPE {
31 A(1),
32 NS(2),
33 MD(3),
34 MF(4),
35 CNAME(5),
36 SOA(6),
37 MB(7),
38 MG(8),
39 MR(9),
40 NULL(10),
41 WKS(11),
42 PTR(12),
43 HINFO(13),
44 MINFO(14),
45 MX(15),
46 TXT(16),
47 RP(17),
48 AFSDB(18),
49 X25(19),
50 ISDN(20),
51 RT(21),
52 NSAP(22),
53 NSAP_PTR(23),
54 SIG(24),
55 KEY(25),
56 PX(26),
57 GPOS(27),
58 AAAA(28),
59 LOC(29),
60 NXT(30),
61 EID(31),
62 NIMLOC(32),
63 SRV(33),
64 ATMA(34),
65 NAPTR(35),
66 KX(36),
67 CERT(37),
68 A6(38),
69 DNAME(39),
70 SINK(40),
71 OPT(41),
72 APL(42),
73 DS(43),
74 SSHFP(44),
75 IPSECKEY(45),
76 RRSIG(46),
77 NSEC(47),
78 DNSKEY(48),
79 DHCID(49),
80 NSEC3(50),
81 NSEC3PARAM(51),
82 HIP(55),
83 NINFO(56),
84 RKEY(57),
85 TALINK(58),
86 SPF(99),
87 UINFO(100),
88 UID(101),
89 GID(102),
90 TKEY(249),
91 TSIG(250),
92 IXFR(251),
93 AXFR(252),
94 MAILB(253),
95 MAILA(254),
96 ANY(255),
97 TA(32768),
98 DLV(32769);
99
100 /**
101 * The value of this DNS record type.
102 */
103 private final int value;
104
105 /**
106 * Internal lookup table to map values to types.
107 */
108 private final static HashMap<Integer, TYPE> INVERSE_LUT =
109 new HashMap<Integer, TYPE>();
110
111 /**
112 * Initialize the reverse lookup table.
113 */
114 static {
115 for(TYPE t: TYPE.values()) {
116 INVERSE_LUT.put(t.getValue(), t);
117 }
118 }
119
120 /**
121 * Create a new record type.
122 * @param value The binary value of this type.
123 */
124 private TYPE(int value) {
125 this.value = value;
126 }
127
128 /**
129 * Retrieve the binary value of this type.
130 * @return The binary value.
131 */
132 public int getValue() {
133 return value;
134 }
135
136 /**
137 * Retrieve the symbolic type of the binary value.
138 * @param value The binary type value.
139 * @return The symbolic tpye.
140 */
141 public static TYPE getType(int value) {
142 return INVERSE_LUT.get(value);
143 }
144 };
145
146 /**
147 * The symbolic class of a DNS record (usually IN for Internet).
148 */
149 public static enum CLASS {
150 IN(1),
151 CH(3),
152 HS(4),
153 NONE(254),
154 ANY(255);
155
156 /**
157 * Internal reverse lookup table to map binary class values to symbolic
158 * names.
159 */
160 private final static HashMap<Integer, CLASS> INVERSE_LUT =
161 new HashMap<Integer, CLASS>();
162
163 /**
164 * Initialize the interal reverse lookup table.
165 */
166 static {
167 for(CLASS c: CLASS.values()) {
168 INVERSE_LUT.put(c.getValue(), c);
169 }
170 }
171
172 /**
173 * The binary value of this dns class.
174 */
175 private final int value;
176
177 /**
178 * Create a new DNS class based on a binary value.
179 * @param value The binary value of this DNS class.
180 */
181 private CLASS(int value) {
182 this.value = value;
183 }
184
185 /**
186 * Retrieve the binary value of this DNS class.
187 * @return The binary value of this DNS class.
188 */
189 public int getValue() {
190 return value;
191 }
192
193 /**
194 * Retrieve the symbolic DNS class for a binary class value.
195 * @param value The binary DNS class value.
196 * @return The symbolic class instance.
197 */
198 public static CLASS getClass(int value) {
199 return INVERSE_LUT.get(value);
200 }
201
202 }
203
204 /**
205 * The generic name of this record.
206 */
207 protected String name;
208
209 /**
210 * The type (and payload type) of this record.
211 */
212 protected TYPE type;
213
214 /**
215 * The record class (usually CLASS.IN).
216 */
217 protected CLASS clazz;
218
219 /**
220 * The ttl of this record.
221 */
222 protected long ttl;
223
224 /**
225 * The payload object of this record.
226 */
227 protected Data payloadData;
228
229 /**
230 * MDNS defines the highest bit of the class as the unicast query bit.
231 */
232 protected boolean unicastQuery;
233
234 /**
235 * Parse a given record based on the full message data and the current
236 * stream position.
237 * @param dis The DataInputStream positioned at the first record byte.
238 * @param data The full message data.
239 * @throws IOException In case of malformed replies.
240 */
241 public void parse(DataInputStream dis, byte[] data) throws IOException {
242 this.name = NameUtil.parse(dis, data);
243 this.type = TYPE.getType(dis.readUnsignedShort());
244 int clazzValue = dis.readUnsignedShort();
245 this.clazz = CLASS.getClass(clazzValue & 0x7fff);
246 this.unicastQuery = (clazzValue & 0x8000) > 0;
247 if (this.clazz == null) {
248 LOGGER.log(Level.FINE, "Unknown class " + clazzValue);
249 }
250 this.ttl = (((long)dis.readUnsignedShort()) << 32) +
251 dis.readUnsignedShort();
252 int payloadLength = dis.readUnsignedShort();
253 switch (this.type) {
254 case SRV:
255 this.payloadData = new SRV();
256 break;
257 case AAAA:
258 this.payloadData = new AAAA();
259 break;
260 case A:
261 this.payloadData = new A();
262 break;
263 case NS:
264 this.payloadData = new NS();
265 break;
266 case CNAME:
267 this.payloadData = new CNAME();
268 break;
269 case PTR:
270 this.payloadData = new PTR();
271 break;
272 case TXT:
273 this.payloadData = new TXT();
274 break;
275 default:
276 LOGGER.log(Level.FINE, "Unparsed type " + type);
277 this.payloadData = null;
278 for (int i = 0; i < payloadLength; i++) {
279 dis.readByte();
280 }
281 break;
282 }
283 if (this.payloadData != null) {
284 this.payloadData.parse(dis, data, payloadLength);
285 }
286 }
287
288 /**
289 * Retrieve a textual representation of this resource record.
290 * @return String
291 */
292 @Override
293 public String toString() {
294 if (payloadData == null) {
295 return "RR " + type + "/" + clazz;
296 }
297 return "RR " + type + "/" + clazz + ": " + payloadData.toString();
298 };
299
300 /**
301 * Check if this record answers a given query.
302 * @param q The query.
303 * @return True if this record is a valid answer.
304 */
305 public boolean isAnswer(Question q) {
306 return ((q.getType() == type) || (q.getType() == TYPE.ANY)) &&
307 ((q.getClazz() == clazz) || (q.getClazz() == CLASS.ANY)) &&
308 (q.getName().equals(name));
309 }
310
311 /**
312 * See if this query/response was a unicast query (highest class bit set).
313 * @return True if it is a unicast query/response record.
314 */
315 public boolean isUnicastQuery() {
316 return unicastQuery;
317 }
318
319 /**
320 * The generic record name, e.g. "measite.de".
321 * @return The record name.
322 */
323 public String getName() {
324 return name;
325 }
326
327 /**
328 * The payload data, usually a subclass of data (A, AAAA, CNAME, ...).
329 * @return The payload data.
330 */
331 public Data getPayload() {
332 return payloadData;
333 }
334
335 /**
336 * Retrieve the record ttl.
337 * @return The record ttl.
338 */
339 public long getTtl() {
340 return ttl;
341 }
342
343}