dns_types/protocol/
types.rs

1use bytes::{BufMut, Bytes, BytesMut};
2use std::fmt;
3use std::net::{Ipv4Addr, Ipv6Addr};
4use std::str::FromStr;
5
6/// Maximum encoded length of a domain name.  The number of labels
7/// plus sum of the lengths of the labels.
8pub const DOMAINNAME_MAX_LEN: usize = 255;
9
10/// Maximum length of a single label in a domain name.
11pub const LABEL_MAX_LEN: usize = 63;
12
13/// Octet mask for the QR flag being set (response).
14pub const HEADER_MASK_QR: u8 = 0b1000_0000;
15
16/// Octet mask for the opcode field.
17pub const HEADER_MASK_OPCODE: u8 = 0b0111_1000;
18
19/// Offset for the opcode field.
20pub const HEADER_OFFSET_OPCODE: usize = 3;
21
22/// Octet mask for the AA flag being set (authoritative)
23pub const HEADER_MASK_AA: u8 = 0b0000_0100;
24
25/// Octet mask for the TC flag being set (truncated)
26pub const HEADER_MASK_TC: u8 = 0b0000_0010;
27
28/// Octet mask for the RD flag being set (desired)
29pub const HEADER_MASK_RD: u8 = 0b0000_0001;
30
31/// Octet mask for the RA flag being set (available)
32pub const HEADER_MASK_RA: u8 = 0b1000_0000;
33
34/// Octet mask for the rcode field.
35pub const HEADER_MASK_RCODE: u8 = 0b0000_1111;
36
37/// Offset for the rcode field.
38pub const HEADER_OFFSET_RCODE: usize = 0;
39
40/// Basic DNS message format, used for both queries and responses.
41///
42/// ```text
43///     +---------------------+
44///     |        Header       |
45///     +---------------------+
46///     |       Question      | the question for the name server
47///     +---------------------+
48///     |        Answer       | RRs answering the question
49///     +---------------------+
50///     |      Authority      | RRs pointing toward an authority
51///     +---------------------+
52///     |      Additional     | RRs holding additional information
53///     +---------------------+
54/// ```
55///
56/// See section 4.1 of RFC 1035.
57#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
58#[cfg_attr(any(feature = "test-util", test), derive(arbitrary::Arbitrary))]
59pub struct Message {
60    pub header: Header,
61    pub questions: Vec<Question>,
62    pub answers: Vec<ResourceRecord>,
63    pub authority: Vec<ResourceRecord>,
64    pub additional: Vec<ResourceRecord>,
65}
66
67impl Message {
68    pub fn make_response(&self) -> Self {
69        Self {
70            header: Header {
71                id: self.header.id,
72                is_response: true,
73                opcode: self.header.opcode,
74                is_authoritative: false,
75                is_truncated: false,
76                recursion_desired: self.header.recursion_desired,
77                recursion_available: true,
78                rcode: Rcode::NoError,
79            },
80            questions: self.questions.clone(),
81            answers: Vec::new(),
82            authority: Vec::new(),
83            additional: Vec::new(),
84        }
85    }
86
87    pub fn make_format_error_response(id: u16) -> Self {
88        Self {
89            header: Header {
90                id,
91                is_response: true,
92                opcode: Opcode::Standard,
93                is_authoritative: false,
94                is_truncated: false,
95                recursion_desired: false,
96                recursion_available: true,
97                rcode: Rcode::FormatError,
98            },
99            questions: Vec::new(),
100            answers: Vec::new(),
101            authority: Vec::new(),
102            additional: Vec::new(),
103        }
104    }
105
106    pub fn from_question(id: u16, question: Question) -> Self {
107        Self {
108            header: Header {
109                id,
110                is_response: false,
111                opcode: Opcode::Standard,
112                is_authoritative: false,
113                is_truncated: false,
114                recursion_desired: false,
115                recursion_available: false,
116                rcode: Rcode::NoError,
117            },
118            questions: vec![question],
119            answers: Vec::new(),
120            authority: Vec::new(),
121            additional: Vec::new(),
122        }
123    }
124}
125
126/// Common header type for all messages.
127///
128/// ```text
129///                                     1  1  1  1  1  1
130///       0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
131///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
132///     |                      ID                       |
133///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
134///     |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
135///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
136///     |                    QDCOUNT                    |
137///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
138///     |                    ANCOUNT                    |
139///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
140///     |                    NSCOUNT                    |
141///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
142///     |                    ARCOUNT                    |
143///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
144/// ```
145///
146/// See section 4.1.1 of RFC 1035.
147///
148/// The QECOUNT, ANCOUNT, NSCOUNT, and ARCOUNT fields are omitted from this
149/// type, as they are only used during serialisation and deserialisation and can
150/// be inferred from the other `Message` fields.
151#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
152#[cfg_attr(any(feature = "test-util", test), derive(arbitrary::Arbitrary))]
153pub struct Header {
154    /// A 16 bit identifier assigned by the program that generates any
155    /// kind of query.  This identifier is copied the corresponding
156    /// reply and can be used by the requester to match up replies to
157    /// outstanding queries.
158    pub id: u16,
159
160    /// A one bit field that specifies whether this message is a query
161    /// (0), or a response (1).
162    pub is_response: bool,
163
164    /// A four bit field that specifies kind of query in this message.
165    /// This value is set by the originator of a query and copied into
166    /// the response.  The values are:
167    ///
168    /// - `0` a standard query (`QUERY`)
169    ///
170    /// - `1` an inverse query (`IQUERY`)
171    ///
172    /// - `2` a server status request (`STATUS`)
173    ///
174    /// - `3-15` reserved for future use
175    pub opcode: Opcode,
176
177    /// Authoritative Answer - this bit is valid in responses, and
178    /// specifies that the responding name server is an authority for
179    /// the domain name in question section.
180    ///
181    /// Note that the contents of the answer section may have multiple
182    /// owner names because of aliases.  The AA bit corresponds to the
183    /// name which matches the query name, or the first owner name in
184    /// the answer section.
185    pub is_authoritative: bool,
186
187    /// Truncation - specifies that this message was truncated due to
188    /// length greater than that permitted on the transmission
189    /// channel.
190    pub is_truncated: bool,
191
192    /// Recursion Desired - this bit may be set in a query and is
193    /// copied into the response.  If RD is set, it directs the name
194    /// server to pursue the query recursively.  Recursive query
195    /// support is optional.
196    pub recursion_desired: bool,
197
198    /// Recursion Available - this be is set or cleared in a response,
199    /// and denotes whether recursive query support is available in
200    /// the name server.
201    pub recursion_available: bool,
202
203    /// Response code - this 4 bit field is set as part of responses.
204    /// The values have the following interpretation:
205    ///
206    /// - `0` No error condition
207    ///
208    /// - `1` Format error - The name server was unable to interpret
209    ///   the query.
210    ///
211    /// - `2` Server failure - The name server was unable to process this query
212    ///   due to a problem with the name server.
213    ///
214    /// - `3` Name Error - Meaningful only for responses from an authoritative
215    ///   name server, this code signifies that the domain name referenced in
216    ///   the query does not exist.
217    ///
218    /// - `4` Not Implemented - The name server does not support the requested
219    ///   kind of query.
220    ///
221    /// - `5` Refused - The name server refuses to perform the specified
222    ///   operation for policy reasons.  For example, a name server may not wish
223    ///   to provide the information to the particular requester, or a name
224    ///   server may not wish to perform a particular operation (e.g., zone
225    ///   transfer) for particular data.
226    ///
227    /// - `6-15` Reserved for future use.
228    pub rcode: Rcode,
229}
230
231/// The question section has a list of questions (usually 1 but
232/// possibly more) being asked.  This is the structure for a single
233/// question.
234///
235/// ```text
236///                                     1  1  1  1  1  1
237///       0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
238///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
239///     |                                               |
240///     /                     QNAME                     /
241///     /                                               /
242///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
243///     |                     QTYPE                     |
244///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
245///     |                     QCLASS                    |
246///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
247/// ```
248///
249/// See section 4.1.2 of RFC 1035.
250#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
251#[cfg_attr(any(feature = "test-util", test), derive(arbitrary::Arbitrary))]
252pub struct Question {
253    /// a domain name represented as a sequence of labels, where each
254    /// label consists of a length octet followed by that number of
255    /// octets.  The domain name terminates with the zero length octet
256    /// for the null label of the root.  Note that this field may be
257    /// an odd number of octets; no padding is used.
258    pub name: DomainName,
259
260    /// a two octet code which specifies the type of the query.  The
261    /// values for this field include all codes valid for a TYPE
262    /// field, together with some more general codes which can match
263    /// more than one type of RR.
264    pub qtype: QueryType,
265
266    /// a two octet code that specifies the class of the query.  For
267    /// example, the QCLASS field is IN for the Internet.
268    pub qclass: QueryClass,
269}
270
271impl Question {
272    pub fn is_unknown(&self) -> bool {
273        self.qtype.is_unknown() || self.qclass.is_unknown()
274    }
275}
276
277impl fmt::Display for Question {
278    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
279        write!(
280            f,
281            "{} {} {}",
282            self.name.to_dotted_string(),
283            self.qclass,
284            self.qtype
285        )
286    }
287}
288
289/// The answer, authority, and additional sections are all the same
290/// format: a variable number of resource records.  This is the
291/// structure for a single resource record.
292///
293/// ```text
294///                                     1  1  1  1  1  1
295///       0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
296///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
297///     |                                               |
298///     /                                               /
299///     /                      NAME                     /
300///     |                                               |
301///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
302///     |                      TYPE                     |
303///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
304///     |                     CLASS                     |
305///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
306///     |                      TTL                      |
307///     |                                               |
308///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
309///     |                   RDLENGTH                    |
310///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
311///     /                     RDATA                     /
312///     /                                               /
313///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
314/// ```
315///
316/// See section 4.1.3 of RFC 1035.
317#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
318#[cfg_attr(any(feature = "test-util", test), derive(arbitrary::Arbitrary))]
319pub struct ResourceRecord {
320    /// a domain name to which this resource record pertains.
321    pub name: DomainName,
322
323    /// A combination of the RTYPE and RDATA fields
324    pub rtype_with_data: RecordTypeWithData,
325
326    /// two octets which specify the class of the data in the RDATA
327    /// field.
328    pub rclass: RecordClass,
329
330    /// a 32 bit unsigned integer that specifies the time interval (in
331    /// seconds) that the resource record may be cached before it
332    /// should be discarded.  Zero values are interpreted to mean that
333    /// the RR can only be used for the transaction in progress, and
334    /// should not be cached.
335    pub ttl: u32,
336}
337
338impl ResourceRecord {
339    pub fn is_unknown(&self) -> bool {
340        self.rtype_with_data.is_unknown() || self.rclass.is_unknown()
341    }
342
343    pub fn matches(&self, question: &Question) -> bool {
344        self.rtype_with_data.matches(question.qtype) && self.rclass.matches(question.qclass)
345    }
346}
347
348/// A record type with its associated, deserialised, data.
349#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
350pub enum RecordTypeWithData {
351    /// ```text
352    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
353    ///     |                    ADDRESS                    |
354    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
355    /// ```
356    ///
357    /// Where `ADDRESS` is a 32 bit Internet address.
358    A { address: Ipv4Addr },
359
360    /// ```text
361    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
362    ///     /                   NSDNAME                     /
363    ///     /                                               /
364    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
365    /// ```
366    ///
367    /// Where `NSDNAME` is a domain name which specifies a host which
368    /// should be authoritative for the specified class and domain.
369    NS { nsdname: DomainName },
370
371    /// ```text
372    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
373    ///     /                   MADNAME                     /
374    ///     /                                               /
375    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
376    /// ```
377    ///
378    /// Where `MADNAME` is a domain name which specifies a host which
379    /// has a mail agent for the domain which should be able to
380    /// deliver mail for the domain.
381    MD { madname: DomainName },
382
383    /// ```text
384    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
385    ///     /                   MADNAME                     /
386    ///     /                                               /
387    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
388    /// ```
389    ///
390    /// Where `MADNAME` is a domain name which specifies a host which
391    /// has a mail agent for the domain which will accept mail for
392    /// forwarding to the domain.
393    MF { madname: DomainName },
394
395    /// ```text
396    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
397    ///     /                     CNAME                     /
398    ///     /                                               /
399    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
400    /// ```
401    ///
402    /// Where `CNAME` is a domain name which specifies the canonical
403    /// or primary name for the owner.  The owner name is an alias.
404    CNAME { cname: DomainName },
405
406    /// ```text
407    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
408    ///     /                     MNAME                     /
409    ///     /                                               /
410    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
411    ///     /                     RNAME                     /
412    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
413    ///     |                    SERIAL                     |
414    ///     |                                               |
415    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
416    ///     |                    REFRESH                    |
417    ///     |                                               |
418    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
419    ///     |                     RETRY                     |
420    ///     |                                               |
421    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
422    ///     |                    EXPIRE                     |
423    ///     |                                               |
424    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
425    ///     |                    MINIMUM                    |
426    ///     |                                               |
427    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
428    /// ```
429    ///
430    /// Where `MNAME` is the domain name of the name server that was
431    /// the original or primary source of data for this zone.
432    ///
433    /// Where `RNAME` is a domain name which specifies the mailbox of
434    /// the person responsible for this zone.
435    ///
436    /// Where `SERIAL` is the unsigned 32 bit version number of the
437    /// original copy of the zone.  Zone transfers preserve this
438    /// value.  This value wraps and should be compared using sequence
439    /// space arithmetic.
440    ///
441    /// Where `REFRESH` is a 32 bit time interval before the zone
442    /// should be refreshed.
443    ///
444    /// Where `RETRY` is a 32 bit time interval that should elapse
445    /// before a failed refresh should be retried.
446    ///
447    /// Where `EXPIRE` is a 32 bit time value that specifies an upper
448    /// limit on the time interval that can elapse before the zone is
449    /// no longer authoritative.
450    ///
451    /// Where `MINIMUM` is the unsigned 32 bit minimum TTL field that
452    /// should be exported with any RR from this zone.
453    ///
454    /// All times are in units of seconds.
455    SOA {
456        mname: DomainName,
457        rname: DomainName,
458        serial: u32,
459        refresh: u32,
460        retry: u32,
461        expire: u32,
462        minimum: u32,
463    },
464
465    /// ```text
466    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
467    ///     /                   MADNAME                     /
468    ///     /                                               /
469    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
470    /// ```
471    ///
472    /// Where `MADNAME` is a domain name which specifies a host which
473    /// has the specified mailbox.
474    MB { madname: DomainName },
475
476    /// ```text
477    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
478    ///     /                   MGMNAME                     /
479    ///     /                                               /
480    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
481    /// ```
482    ///
483    /// Where `MGMNAME` is a domain name which specifies a mailbox
484    /// which is a member of the mail group specified by the domain
485    /// name.
486    MG { mdmname: DomainName },
487
488    /// ```text
489    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
490    ///     /                   NEWNAME                     /
491    ///     /                                               /
492    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
493    /// ```
494    ///
495    /// Where `NEWNAME` is a domain name which specifies a mailbox
496    /// which is the proper rename of the specifies mailbox.
497    MR { newname: DomainName },
498
499    /// ```text
500    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
501    ///     /                  <anything>                   /
502    ///     /                                               /
503    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
504    /// ```
505    ///
506    /// Anything at all may be in the RDATA field so long as it is
507    /// 65535 octets or less.
508    NULL { octets: Bytes },
509
510    /// This application does not interpret `WKS` records.
511    WKS { octets: Bytes },
512
513    /// ```text
514    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
515    ///     /                   PTRDNAME                    /
516    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
517    /// ```
518    ///
519    /// Where `PTRDNAME` is a domain name which points to some
520    /// location in the domain name space.
521    PTR { ptrdname: DomainName },
522
523    /// This application does not interpret `HINFO` records.
524    HINFO { octets: Bytes },
525
526    /// ```text
527    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
528    ///     /                    RMAILBX                    /
529    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
530    ///     /                    EMAILBX                    /
531    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
532    /// ```
533    ///
534    /// Where `RMAILBX` is a domain name which specifies a mailbox
535    /// which is responsible for the mailing list or mailbox.  If this
536    /// domain name names the root, the owner of the `MINFO` RR is
537    /// responsible for itself.
538    ///
539    /// Where `EMAILBX` is a domain name which specifies a mailbox
540    /// which is to receive error messages related to the mailing list
541    /// or mailbox specified by the owner of the `MINFO` RR (similar
542    /// to the `ERRORS-TO`: field which has been proposed).  If this
543    /// domain name names the root, errors should be returned to the
544    /// sender of the message.
545    MINFO {
546        rmailbx: DomainName,
547        emailbx: DomainName,
548    },
549
550    /// ```text
551    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
552    ///     |                  PREFERENCE                   |
553    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
554    ///     /                   EXCHANGE                    /
555    ///     /                                               /
556    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
557    /// ```
558    ///
559    /// Where `PREFERENCE` is a 16 bit integer which specifies the
560    /// preference given to this RR among others at the same owner.
561    /// Lower values are preferred.
562    ///
563    /// Where `EXCHANGE` is a domain name which specifies a host
564    /// willing to act as a mail exchange for the owner name.
565    MX {
566        preference: u16,
567        exchange: DomainName,
568    },
569
570    /// ```text
571    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
572    ///     /                   TXT-DATA                    /
573    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
574    /// ```
575    ///
576    /// Where `TXT-DATA` is one or more character strings.
577    TXT { octets: Bytes },
578
579    /// ```text
580    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
581    ///     |                    ADDRESS                    |
582    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
583    /// ```
584    ///
585    /// Where `ADDRESS` is a 128 bit Internet address.
586    AAAA { address: Ipv6Addr },
587
588    /// ```text
589    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
590    ///     |                   PRIORITY                    |
591    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
592    ///     |                    WEIGHT                     |
593    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
594    ///     |                     PORT                      |
595    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
596    ///     /                    TARGET                     /
597    ///     /                                               /
598    ///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
599    /// ```
600    ///
601    /// Where `PRIORITY` is a 16 bit integer which specifies the order
602    /// (lowest first) in which clients must attempt to use these RRs.
603    ///
604    /// Where `WEIGHT` is a 16 bit integer which specifies the
605    /// preference given to this RR amongst others of the same
606    /// priority.
607    ///
608    /// Where `PORT` is a 16 bit integer defining the port to contact
609    /// the service on.
610    ///
611    /// Where `TARGET` is the domain name the service may be found at.
612    /// This should point to a domain name that has an address record
613    /// (A or AAAA) directly, rather than a domain name which has a
614    /// CNAME or other alias type.  But this is not enforced.
615    SRV {
616        priority: u16,
617        weight: u16,
618        port: u16,
619        target: DomainName,
620    },
621
622    /// Any other record.
623    Unknown {
624        tag: RecordTypeUnknown,
625        octets: Bytes,
626    },
627}
628
629impl RecordTypeWithData {
630    pub fn is_unknown(&self) -> bool {
631        self.rtype().is_unknown()
632    }
633
634    pub fn matches(&self, qtype: QueryType) -> bool {
635        self.rtype().matches(qtype)
636    }
637
638    pub fn rtype(&self) -> RecordType {
639        match self {
640            RecordTypeWithData::A { .. } => RecordType::A,
641            RecordTypeWithData::NS { .. } => RecordType::NS,
642            RecordTypeWithData::MD { .. } => RecordType::MD,
643            RecordTypeWithData::MF { .. } => RecordType::MF,
644            RecordTypeWithData::CNAME { .. } => RecordType::CNAME,
645            RecordTypeWithData::SOA { .. } => RecordType::SOA,
646            RecordTypeWithData::MB { .. } => RecordType::MB,
647            RecordTypeWithData::MG { .. } => RecordType::MG,
648            RecordTypeWithData::MR { .. } => RecordType::MR,
649            RecordTypeWithData::NULL { .. } => RecordType::NULL,
650            RecordTypeWithData::WKS { .. } => RecordType::WKS,
651            RecordTypeWithData::PTR { .. } => RecordType::PTR,
652            RecordTypeWithData::HINFO { .. } => RecordType::HINFO,
653            RecordTypeWithData::MINFO { .. } => RecordType::MINFO,
654            RecordTypeWithData::MX { .. } => RecordType::MX,
655            RecordTypeWithData::TXT { .. } => RecordType::TXT,
656            RecordTypeWithData::AAAA { .. } => RecordType::AAAA,
657            RecordTypeWithData::SRV { .. } => RecordType::SRV,
658            RecordTypeWithData::Unknown { tag, .. } => RecordType::Unknown(*tag),
659        }
660    }
661}
662
663#[cfg(any(feature = "test-util", test))]
664impl<'a> arbitrary::Arbitrary<'a> for RecordTypeWithData {
665    // this is pretty verbose but it feels like a better way to guarantee the
666    // max size of the `Bytes`s than adding a wrapper type
667    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
668        let len = u.int_in_range(0..=128)?;
669        let octets = Bytes::copy_from_slice(u.bytes(len)?);
670
671        let rtype_with_data = match u.arbitrary::<RecordType>()? {
672            RecordType::A => RecordTypeWithData::A {
673                address: u.arbitrary()?,
674            },
675            RecordType::NS => RecordTypeWithData::NS {
676                nsdname: u.arbitrary()?,
677            },
678            RecordType::MD => RecordTypeWithData::MD {
679                madname: u.arbitrary()?,
680            },
681            RecordType::MF => RecordTypeWithData::MF {
682                madname: u.arbitrary()?,
683            },
684            RecordType::CNAME => RecordTypeWithData::CNAME {
685                cname: u.arbitrary()?,
686            },
687            RecordType::SOA => RecordTypeWithData::SOA {
688                mname: u.arbitrary()?,
689                rname: u.arbitrary()?,
690                serial: u.arbitrary()?,
691                refresh: u.arbitrary()?,
692                retry: u.arbitrary()?,
693                expire: u.arbitrary()?,
694                minimum: u.arbitrary()?,
695            },
696            RecordType::MB => RecordTypeWithData::MB {
697                madname: u.arbitrary()?,
698            },
699            RecordType::MG => RecordTypeWithData::MG {
700                mdmname: u.arbitrary()?,
701            },
702            RecordType::MR => RecordTypeWithData::MR {
703                newname: u.arbitrary()?,
704            },
705            RecordType::NULL => RecordTypeWithData::NULL { octets },
706            RecordType::WKS => RecordTypeWithData::WKS { octets },
707            RecordType::PTR => RecordTypeWithData::PTR {
708                ptrdname: u.arbitrary()?,
709            },
710            RecordType::HINFO => RecordTypeWithData::HINFO { octets },
711            RecordType::MINFO => RecordTypeWithData::MINFO {
712                rmailbx: u.arbitrary()?,
713                emailbx: u.arbitrary()?,
714            },
715            RecordType::MX => RecordTypeWithData::MX {
716                preference: u.arbitrary()?,
717                exchange: u.arbitrary()?,
718            },
719            RecordType::TXT => RecordTypeWithData::TXT { octets },
720            RecordType::AAAA => RecordTypeWithData::AAAA {
721                address: u.arbitrary()?,
722            },
723            RecordType::SRV => RecordTypeWithData::SRV {
724                priority: u.arbitrary()?,
725                weight: u.arbitrary()?,
726                port: u.arbitrary()?,
727                target: u.arbitrary()?,
728            },
729            RecordType::Unknown(tag) => RecordTypeWithData::Unknown { tag, octets },
730        };
731        Ok(rtype_with_data)
732    }
733}
734
735/// What sort of query this is.
736#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
737pub enum Opcode {
738    Standard,
739    Inverse,
740    Status,
741    Reserved(OpcodeReserved),
742}
743
744/// A struct with a private constructor, to ensure invalid `Opcode`s
745/// cannot be created.
746#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
747pub struct OpcodeReserved(u8);
748
749impl Opcode {
750    pub fn is_reserved(&self) -> bool {
751        matches!(self, Opcode::Reserved(_))
752    }
753}
754
755impl From<u8> for Opcode {
756    fn from(octet: u8) -> Self {
757        match octet & 0b0000_1111 {
758            0 => Opcode::Standard,
759            1 => Opcode::Inverse,
760            2 => Opcode::Status,
761            other => Opcode::Reserved(OpcodeReserved(other)),
762        }
763    }
764}
765
766impl From<Opcode> for u8 {
767    fn from(value: Opcode) -> Self {
768        match value {
769            Opcode::Standard => 0,
770            Opcode::Inverse => 1,
771            Opcode::Status => 2,
772            Opcode::Reserved(OpcodeReserved(octet)) => octet,
773        }
774    }
775}
776
777#[cfg(any(feature = "test-util", test))]
778impl<'a> arbitrary::Arbitrary<'a> for Opcode {
779    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
780        Ok(Self::from(u.arbitrary::<u8>()?))
781    }
782}
783
784/// What sort of response this is.
785#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
786pub enum Rcode {
787    NoError,
788    FormatError,
789    ServerFailure,
790    NameError,
791    NotImplemented,
792    Refused,
793    Reserved(RcodeReserved),
794}
795
796/// A struct with a private constructor, to ensure invalid `Rcode`s
797/// cannot be created.
798#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
799pub struct RcodeReserved(u8);
800
801impl Rcode {
802    pub fn is_reserved(&self) -> bool {
803        matches!(self, Rcode::Reserved(_))
804    }
805}
806
807impl fmt::Display for Rcode {
808    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
809        match self {
810            Rcode::NoError => write!(f, "no-error"),
811            Rcode::FormatError => write!(f, "format-error"),
812            Rcode::ServerFailure => write!(f, "server-failure"),
813            Rcode::NameError => write!(f, "name-error"),
814            Rcode::NotImplemented => write!(f, "not-implemented"),
815            Rcode::Refused => write!(f, "refused"),
816            Rcode::Reserved(_) => write!(f, "reserved"),
817        }
818    }
819}
820
821impl From<u8> for Rcode {
822    fn from(octet: u8) -> Self {
823        match octet & 0b0000_1111 {
824            0 => Rcode::NoError,
825            1 => Rcode::FormatError,
826            2 => Rcode::ServerFailure,
827            3 => Rcode::NameError,
828            4 => Rcode::NotImplemented,
829            5 => Rcode::Refused,
830            other => Rcode::Reserved(RcodeReserved(other)),
831        }
832    }
833}
834
835impl From<Rcode> for u8 {
836    fn from(value: Rcode) -> Self {
837        match value {
838            Rcode::NoError => 0,
839            Rcode::FormatError => 1,
840            Rcode::ServerFailure => 2,
841            Rcode::NameError => 3,
842            Rcode::NotImplemented => 4,
843            Rcode::Refused => 5,
844            Rcode::Reserved(RcodeReserved(octet)) => octet,
845        }
846    }
847}
848
849#[cfg(any(feature = "test-util", test))]
850impl<'a> arbitrary::Arbitrary<'a> for Rcode {
851    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
852        Ok(Self::from(u.arbitrary::<u8>()?))
853    }
854}
855
856/// A domain name is a sequence of labels, where each label is a
857/// length octet followed by that number of octets.
858///
859/// A label must be 63 octets or shorter.  A name must be 255 octets
860/// or shorter in total, including both length and label octets.
861#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
862pub struct DomainName {
863    pub labels: Vec<Label>,
864    // INVARIANT: len == len(labels) + sum(map(len, labels))
865    pub len: usize,
866}
867
868impl DomainName {
869    pub fn root_domain() -> Self {
870        DomainName {
871            labels: vec![Label::new()],
872            len: 1,
873        }
874    }
875
876    pub fn is_root(&self) -> bool {
877        self.len == 1 && self.labels[0].is_empty()
878    }
879
880    pub fn is_subdomain_of(&self, other: &DomainName) -> bool {
881        self.labels.ends_with(&other.labels)
882    }
883
884    pub fn make_subdomain_of(&self, origin: &Self) -> Option<Self> {
885        let mut labels = self.labels.clone();
886        labels.pop();
887        labels.append(&mut origin.labels.clone());
888        DomainName::from_labels(labels)
889    }
890
891    pub fn to_dotted_string(&self) -> String {
892        if self.is_root() {
893            return ".".to_string();
894        }
895
896        let mut out = String::with_capacity(self.len);
897        let mut first = true;
898        for label in &self.labels {
899            if first {
900                first = false;
901            } else {
902                out.push('.');
903            }
904            for octet in &label.octets {
905                out.push(*octet as char);
906            }
907        }
908
909        out
910    }
911
912    pub fn from_relative_dotted_string(origin: &Self, s: &str) -> Option<Self> {
913        if s.is_empty() {
914            Some(origin.clone())
915        } else if s.to_string().ends_with('.') {
916            Self::from_dotted_string(s)
917        } else {
918            let suffix = origin.to_dotted_string();
919            if suffix.starts_with('.') {
920                Self::from_dotted_string(&format!("{s}{suffix}"))
921            } else {
922                Self::from_dotted_string(&format!("{s}.{suffix}"))
923            }
924        }
925    }
926
927    pub fn from_dotted_string(s: &str) -> Option<Self> {
928        if s == "." {
929            return Some(Self::root_domain());
930        }
931
932        let chunks = s.split('.').collect::<Vec<_>>();
933        let mut labels = Vec::with_capacity(chunks.len());
934
935        for (i, label_chars) in chunks.iter().enumerate() {
936            if label_chars.is_empty() && i != chunks.len() - 1 {
937                return None;
938            }
939
940            match label_chars.as_bytes().try_into() {
941                Ok(label) => labels.push(label),
942                Err(_) => return None,
943            }
944        }
945
946        Self::from_labels(labels)
947    }
948
949    pub fn from_labels(labels: Vec<Label>) -> Option<Self> {
950        if labels.is_empty() {
951            return None;
952        }
953
954        let mut len = labels.len();
955        let mut blank_label = false;
956
957        for label in &labels {
958            if blank_label {
959                return None;
960            }
961
962            blank_label |= label.is_empty();
963            len += label.len() as usize;
964        }
965
966        if blank_label && len <= DOMAINNAME_MAX_LEN {
967            Some(Self { labels, len })
968        } else {
969            None
970        }
971    }
972}
973
974impl fmt::Debug for DomainName {
975    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
976        f.debug_struct("DomainName")
977            .field("to_dotted_string()", &self.to_dotted_string())
978            .finish()
979    }
980}
981
982impl fmt::Display for DomainName {
983    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
984        write!(f, "{}", &self.to_dotted_string())
985    }
986}
987
988impl FromStr for DomainName {
989    type Err = DomainNameFromStr;
990
991    fn from_str(s: &str) -> Result<Self, Self::Err> {
992        if let Some(domain) = DomainName::from_dotted_string(s) {
993            Ok(domain)
994        } else {
995            Err(DomainNameFromStr::NoParse)
996        }
997    }
998}
999
1000/// Errors that can arise when converting a `&str` into a `DomainName`.
1001#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1002pub enum DomainNameFromStr {
1003    NoParse,
1004}
1005
1006impl fmt::Display for DomainNameFromStr {
1007    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1008        write!(f, "could not parse string to domain name")
1009    }
1010}
1011
1012impl std::error::Error for DomainNameFromStr {
1013    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1014        None
1015    }
1016}
1017
1018#[cfg(any(feature = "test-util", test))]
1019impl<'a> arbitrary::Arbitrary<'a> for DomainName {
1020    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1021        let num_labels = u.int_in_range::<usize>(0..=10)?;
1022        let mut labels = Vec::new();
1023        for _ in 0..num_labels {
1024            labels.push(u.arbitrary()?);
1025        }
1026        labels.push(Label::new());
1027        Ok(DomainName::from_labels(labels).unwrap())
1028    }
1029}
1030
1031/// A label is just a sequence of octets, which are compared as
1032/// case-insensitive ASCII.  A label can be no longer than 63 octets.
1033#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1034pub struct Label {
1035    /// Private to this module so constructing an invalid `Label` is
1036    /// impossible.
1037    octets: Bytes,
1038}
1039
1040impl Label {
1041    /// Create a new, empty, label.
1042    pub fn new() -> Self {
1043        Self {
1044            octets: Bytes::new(),
1045        }
1046    }
1047
1048    #[allow(clippy::missing_panics_doc)]
1049    pub fn len(&self) -> u8 {
1050        // safe as the `TryFrom` ensures a label is <= 63 bytes
1051        self.octets.len().try_into().unwrap()
1052    }
1053
1054    pub fn is_empty(&self) -> bool {
1055        self.octets.is_empty()
1056    }
1057
1058    pub fn octets(&self) -> &Bytes {
1059        &self.octets
1060    }
1061}
1062
1063impl Default for Label {
1064    fn default() -> Self {
1065        Self::new()
1066    }
1067}
1068
1069impl TryFrom<&[u8]> for Label {
1070    type Error = LabelTryFromOctetsError;
1071
1072    fn try_from(mixed_case_octets: &[u8]) -> Result<Self, Self::Error> {
1073        if mixed_case_octets.len() > LABEL_MAX_LEN {
1074            return Err(LabelTryFromOctetsError::TooLong);
1075        }
1076
1077        Ok(Self {
1078            octets: Bytes::copy_from_slice(&mixed_case_octets.to_ascii_lowercase()),
1079        })
1080    }
1081}
1082
1083#[cfg(any(feature = "test-util", test))]
1084impl<'a> arbitrary::Arbitrary<'a> for Label {
1085    // only generates non-empty labels
1086    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Label> {
1087        let label_len = u.int_in_range::<u8>(1..=20)?;
1088        let mut octets = BytesMut::with_capacity(label_len.into());
1089        let bs = u.bytes(label_len.into())?;
1090        for b in bs {
1091            let ascii_byte = if b.is_ascii() { *b } else { *b % 128 };
1092            octets.put_u8(
1093                if ascii_byte == b'.'
1094                    || ascii_byte == b'*'
1095                    || ascii_byte == b'@'
1096                    || ascii_byte == b'#'
1097                    || (ascii_byte as char).is_whitespace()
1098                {
1099                    b'x'
1100                } else {
1101                    ascii_byte.to_ascii_lowercase()
1102                },
1103            );
1104        }
1105        Ok(Self {
1106            octets: octets.freeze(),
1107        })
1108    }
1109}
1110
1111/// Errors that can arise when converting a `[u8]` into a `Label`.
1112#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1113pub enum LabelTryFromOctetsError {
1114    TooLong,
1115}
1116
1117/// Query types are a superset of record types.
1118#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1119pub enum QueryType {
1120    Record(RecordType),
1121    AXFR,
1122    MAILB,
1123    MAILA,
1124    Wildcard,
1125}
1126
1127impl QueryType {
1128    pub fn is_unknown(&self) -> bool {
1129        match self {
1130            QueryType::Record(rtype) => rtype.is_unknown(),
1131            _ => false,
1132        }
1133    }
1134}
1135
1136impl fmt::Display for QueryType {
1137    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1138        match self {
1139            QueryType::Record(rtype) => rtype.fmt(f),
1140            QueryType::AXFR => write!(f, "AXFR"),
1141            QueryType::MAILA => write!(f, "MAILA"),
1142            QueryType::MAILB => write!(f, "MAILB"),
1143            QueryType::Wildcard => write!(f, "ANY"),
1144        }
1145    }
1146}
1147
1148impl FromStr for QueryType {
1149    type Err = RecordTypeFromStr;
1150
1151    fn from_str(s: &str) -> Result<Self, Self::Err> {
1152        match s {
1153            "AXFR" => Ok(QueryType::AXFR),
1154            "MAILA" => Ok(QueryType::MAILA),
1155            "MAILB" => Ok(QueryType::MAILB),
1156            "ANY" => Ok(QueryType::Wildcard),
1157            _ => RecordType::from_str(s).map(QueryType::Record),
1158        }
1159    }
1160}
1161
1162impl From<u16> for QueryType {
1163    fn from(value: u16) -> Self {
1164        match value {
1165            252 => QueryType::AXFR,
1166            253 => QueryType::MAILB,
1167            254 => QueryType::MAILA,
1168            255 => QueryType::Wildcard,
1169            _ => QueryType::Record(RecordType::from(value)),
1170        }
1171    }
1172}
1173
1174impl From<QueryType> for u16 {
1175    fn from(value: QueryType) -> Self {
1176        match value {
1177            QueryType::AXFR => 252,
1178            QueryType::MAILB => 253,
1179            QueryType::MAILA => 254,
1180            QueryType::Wildcard => 255,
1181            QueryType::Record(rtype) => rtype.into(),
1182        }
1183    }
1184}
1185
1186#[cfg(any(feature = "test-util", test))]
1187impl<'a> arbitrary::Arbitrary<'a> for QueryType {
1188    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1189        Ok(Self::from(u.arbitrary::<u16>()?))
1190    }
1191}
1192
1193/// Query classes are a superset of record classes.
1194#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1195pub enum QueryClass {
1196    Record(RecordClass),
1197    Wildcard,
1198}
1199
1200impl QueryClass {
1201    pub fn is_unknown(&self) -> bool {
1202        match self {
1203            QueryClass::Record(rclass) => rclass.is_unknown(),
1204            QueryClass::Wildcard => false,
1205        }
1206    }
1207}
1208
1209impl fmt::Display for QueryClass {
1210    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1211        match self {
1212            QueryClass::Record(rclass) => rclass.fmt(f),
1213            QueryClass::Wildcard => write!(f, "ANY"),
1214        }
1215    }
1216}
1217
1218impl FromStr for QueryClass {
1219    type Err = RecordClassFromStr;
1220
1221    fn from_str(s: &str) -> Result<Self, Self::Err> {
1222        match s {
1223            "ANY" => Ok(QueryClass::Wildcard),
1224            _ => RecordClass::from_str(s).map(QueryClass::Record),
1225        }
1226    }
1227}
1228
1229impl From<u16> for QueryClass {
1230    fn from(value: u16) -> Self {
1231        match value {
1232            255 => QueryClass::Wildcard,
1233            _ => QueryClass::Record(RecordClass::from(value)),
1234        }
1235    }
1236}
1237
1238impl From<QueryClass> for u16 {
1239    fn from(value: QueryClass) -> Self {
1240        match value {
1241            QueryClass::Wildcard => 255,
1242            QueryClass::Record(rclass) => rclass.into(),
1243        }
1244    }
1245}
1246
1247#[cfg(any(feature = "test-util", test))]
1248impl<'a> arbitrary::Arbitrary<'a> for QueryClass {
1249    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1250        Ok(Self::from(u.arbitrary::<u16>()?))
1251    }
1252}
1253
1254/// Record types are used by resource records and by queries.
1255#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1256pub enum RecordType {
1257    A,
1258    NS,
1259    MD,
1260    MF,
1261    CNAME,
1262    SOA,
1263    MB,
1264    MG,
1265    MR,
1266    NULL,
1267    WKS,
1268    PTR,
1269    HINFO,
1270    MINFO,
1271    MX,
1272    TXT,
1273    AAAA,
1274    SRV,
1275    Unknown(RecordTypeUnknown),
1276}
1277
1278/// A struct with a private constructor, to ensure invalid `RecordType`s
1279/// cannot be created.
1280#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1281pub struct RecordTypeUnknown(u16);
1282
1283impl RecordType {
1284    pub fn is_unknown(&self) -> bool {
1285        matches!(self, RecordType::Unknown(_))
1286    }
1287
1288    pub fn matches(&self, qtype: QueryType) -> bool {
1289        match qtype {
1290            QueryType::Wildcard => true,
1291            QueryType::Record(rtype) => rtype == *self,
1292            _ => false,
1293        }
1294    }
1295}
1296
1297impl fmt::Display for RecordType {
1298    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1299        match self {
1300            RecordType::A => write!(f, "A"),
1301            RecordType::NS => write!(f, "NS"),
1302            RecordType::MD => write!(f, "MD"),
1303            RecordType::MF => write!(f, "MF"),
1304            RecordType::CNAME => write!(f, "CNAME"),
1305            RecordType::SOA => write!(f, "SOA"),
1306            RecordType::MB => write!(f, "MB"),
1307            RecordType::MG => write!(f, "MG"),
1308            RecordType::MR => write!(f, "MR"),
1309            RecordType::NULL => write!(f, "NULL"),
1310            RecordType::WKS => write!(f, "WKS"),
1311            RecordType::PTR => write!(f, "PTR"),
1312            RecordType::HINFO => write!(f, "HINFO"),
1313            RecordType::MINFO => write!(f, "MINFO"),
1314            RecordType::MX => write!(f, "MX"),
1315            RecordType::TXT => write!(f, "TXT"),
1316            RecordType::AAAA => write!(f, "AAAA"),
1317            RecordType::SRV => write!(f, "SRV"),
1318            RecordType::Unknown(RecordTypeUnknown(n)) => write!(f, "TYPE{n}"),
1319        }
1320    }
1321}
1322
1323impl FromStr for RecordType {
1324    type Err = RecordTypeFromStr;
1325
1326    fn from_str(s: &str) -> Result<Self, Self::Err> {
1327        match s {
1328            "A" => Ok(RecordType::A),
1329            "NS" => Ok(RecordType::NS),
1330            "MD" => Ok(RecordType::MD),
1331            "MF" => Ok(RecordType::MF),
1332            "CNAME" => Ok(RecordType::CNAME),
1333            "SOA" => Ok(RecordType::SOA),
1334            "MB" => Ok(RecordType::MB),
1335            "MG" => Ok(RecordType::MG),
1336            "MR" => Ok(RecordType::MR),
1337            "NULL" => Ok(RecordType::NULL),
1338            "WKS" => Ok(RecordType::WKS),
1339            "PTR" => Ok(RecordType::PTR),
1340            "HINFO" => Ok(RecordType::HINFO),
1341            "MINFO" => Ok(RecordType::MINFO),
1342            "MX" => Ok(RecordType::MX),
1343            "TXT" => Ok(RecordType::TXT),
1344            "AAAA" => Ok(RecordType::AAAA),
1345            "SRV" => Ok(RecordType::SRV),
1346            _ => {
1347                if let Some(type_str) = s.strip_prefix("TYPE") {
1348                    if let Ok(type_num) = u16::from_str(type_str) {
1349                        Ok(RecordType::from(type_num))
1350                    } else {
1351                        Err(RecordTypeFromStr::BadType)
1352                    }
1353                } else {
1354                    Err(RecordTypeFromStr::NoParse)
1355                }
1356            }
1357        }
1358    }
1359}
1360
1361/// Errors that can arise when converting a `&str` into a `RecordType`.
1362#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1363pub enum RecordTypeFromStr {
1364    BadType,
1365    NoParse,
1366}
1367
1368impl fmt::Display for RecordTypeFromStr {
1369    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1370        match self {
1371            RecordTypeFromStr::BadType => write!(f, "TYPE<num> number must be a u16"),
1372            RecordTypeFromStr::NoParse => write!(f, "could not parse string to type"),
1373        }
1374    }
1375}
1376
1377impl std::error::Error for RecordTypeFromStr {
1378    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1379        None
1380    }
1381}
1382
1383impl From<u16> for RecordType {
1384    fn from(value: u16) -> Self {
1385        match value {
1386            1 => RecordType::A,
1387            2 => RecordType::NS,
1388            3 => RecordType::MD,
1389            4 => RecordType::MF,
1390            5 => RecordType::CNAME,
1391            6 => RecordType::SOA,
1392            7 => RecordType::MB,
1393            8 => RecordType::MG,
1394            9 => RecordType::MR,
1395            10 => RecordType::NULL,
1396            11 => RecordType::WKS,
1397            12 => RecordType::PTR,
1398            13 => RecordType::HINFO,
1399            14 => RecordType::MINFO,
1400            15 => RecordType::MX,
1401            16 => RecordType::TXT,
1402            28 => RecordType::AAAA,
1403            33 => RecordType::SRV,
1404            _ => RecordType::Unknown(RecordTypeUnknown(value)),
1405        }
1406    }
1407}
1408
1409impl From<RecordType> for u16 {
1410    fn from(value: RecordType) -> Self {
1411        match value {
1412            RecordType::A => 1,
1413            RecordType::NS => 2,
1414            RecordType::MD => 3,
1415            RecordType::MF => 4,
1416            RecordType::CNAME => 5,
1417            RecordType::SOA => 6,
1418            RecordType::MB => 7,
1419            RecordType::MG => 8,
1420            RecordType::MR => 9,
1421            RecordType::NULL => 10,
1422            RecordType::WKS => 11,
1423            RecordType::PTR => 12,
1424            RecordType::HINFO => 13,
1425            RecordType::MINFO => 14,
1426            RecordType::MX => 15,
1427            RecordType::TXT => 16,
1428            RecordType::AAAA => 28,
1429            RecordType::SRV => 33,
1430            RecordType::Unknown(RecordTypeUnknown(value)) => value,
1431        }
1432    }
1433}
1434
1435#[cfg(any(feature = "test-util", test))]
1436impl<'a> arbitrary::Arbitrary<'a> for RecordType {
1437    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1438        Ok(Self::from(u.arbitrary::<u16>()?))
1439    }
1440}
1441
1442/// Record classes are used by resource records and by queries.
1443#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1444pub enum RecordClass {
1445    IN,
1446    Unknown(RecordClassUnknown),
1447}
1448
1449/// A struct with a private constructor, to ensure invalid
1450/// `RecordClass`es cannot be created.
1451#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1452pub struct RecordClassUnknown(u16);
1453
1454impl RecordClass {
1455    pub fn is_unknown(&self) -> bool {
1456        matches!(self, RecordClass::Unknown(_))
1457    }
1458
1459    pub fn matches(&self, qclass: QueryClass) -> bool {
1460        match qclass {
1461            QueryClass::Wildcard => true,
1462            QueryClass::Record(rclass) => rclass == *self,
1463        }
1464    }
1465}
1466
1467impl fmt::Display for RecordClass {
1468    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1469        match self {
1470            RecordClass::IN => write!(f, "IN"),
1471            RecordClass::Unknown(RecordClassUnknown(n)) => write!(f, "CLASS{n}"),
1472        }
1473    }
1474}
1475
1476impl FromStr for RecordClass {
1477    type Err = RecordClassFromStr;
1478
1479    fn from_str(s: &str) -> Result<Self, Self::Err> {
1480        match s {
1481            "IN" => Ok(RecordClass::IN),
1482            _ => {
1483                if let Some(class_str) = s.strip_prefix("CLASS") {
1484                    if let Ok(class_num) = u16::from_str(class_str) {
1485                        Ok(RecordClass::from(class_num))
1486                    } else {
1487                        Err(RecordClassFromStr::BadClass)
1488                    }
1489                } else {
1490                    Err(RecordClassFromStr::NoParse)
1491                }
1492            }
1493        }
1494    }
1495}
1496
1497/// Errors that can arise when converting a `&str` into a `RecordClass`.
1498#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1499pub enum RecordClassFromStr {
1500    BadClass,
1501    NoParse,
1502}
1503
1504impl fmt::Display for RecordClassFromStr {
1505    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1506        match self {
1507            RecordClassFromStr::BadClass => write!(f, "CLASS<num> number must be a u16"),
1508            RecordClassFromStr::NoParse => write!(f, "could not parse string to class"),
1509        }
1510    }
1511}
1512
1513impl std::error::Error for RecordClassFromStr {
1514    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1515        None
1516    }
1517}
1518
1519impl From<u16> for RecordClass {
1520    fn from(value: u16) -> Self {
1521        match value {
1522            1 => RecordClass::IN,
1523            _ => RecordClass::Unknown(RecordClassUnknown(value)),
1524        }
1525    }
1526}
1527
1528impl From<RecordClass> for u16 {
1529    fn from(value: RecordClass) -> Self {
1530        match value {
1531            RecordClass::IN => 1,
1532            RecordClass::Unknown(RecordClassUnknown(value)) => value,
1533        }
1534    }
1535}
1536
1537#[cfg(any(feature = "test-util", test))]
1538impl<'a> arbitrary::Arbitrary<'a> for RecordClass {
1539    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1540        Ok(Self::from(u.arbitrary::<u16>()?))
1541    }
1542}
1543
1544#[cfg(test)]
1545mod tests {
1546    use rand::Rng;
1547
1548    use super::test_util::*;
1549    use super::*;
1550
1551    #[test]
1552    fn u8_opcode_roundtrip() {
1553        for i in 0..15 {
1554            assert_eq!(u8::from(Opcode::from(i)), i);
1555        }
1556    }
1557
1558    #[test]
1559    fn u8_rcode_roundtrip() {
1560        for i in 0..15 {
1561            assert_eq!(u8::from(Rcode::from(i)), i);
1562        }
1563    }
1564
1565    #[test]
1566    fn u16_querytype_roundtrip() {
1567        for i in 0..100 {
1568            assert_eq!(u16::from(QueryType::from(i)), i);
1569        }
1570    }
1571
1572    #[test]
1573    fn u16_queryclass_roundtrip() {
1574        for i in 0..100 {
1575            assert_eq!(u16::from(QueryClass::from(i)), i);
1576        }
1577    }
1578
1579    #[test]
1580    fn u16_recordtype_roundtrip() {
1581        for i in 0..100 {
1582            assert_eq!(u16::from(RecordType::from(i)), i);
1583        }
1584    }
1585
1586    #[test]
1587    fn recordtype_unknown_implies_querytype_unknown() {
1588        for i in 0..100 {
1589            if RecordType::from(i).is_unknown() {
1590                assert!(QueryType::from(i).is_unknown());
1591            }
1592        }
1593    }
1594
1595    #[test]
1596    fn u16_recordclass_roundtrip() {
1597        for i in 0..100 {
1598            assert_eq!(u16::from(RecordClass::from(i)), i);
1599        }
1600    }
1601
1602    #[test]
1603    fn recordclass_unknown_implies_queryclass_unknown() {
1604        for i in 0..100 {
1605            if RecordClass::from(i).is_unknown() {
1606                assert!(QueryClass::from(i).is_unknown());
1607            }
1608        }
1609    }
1610
1611    #[test]
1612    fn domainname_root_conversions() {
1613        assert_eq!(
1614            Some(DomainName::root_domain()),
1615            DomainName::from_dotted_string(".")
1616        );
1617
1618        assert_eq!(
1619            Some(DomainName::root_domain()),
1620            DomainName::from_labels(vec![Label::new()])
1621        );
1622
1623        assert_eq!(".", DomainName::root_domain().to_dotted_string());
1624    }
1625
1626    #[test]
1627    fn from_relative_dotted_string_empty() {
1628        let origin = domain("com.");
1629        assert_eq!(
1630            Some(domain("com.")),
1631            DomainName::from_relative_dotted_string(&origin, "")
1632        );
1633    }
1634
1635    #[test]
1636    fn from_relative_dotted_string_absolute() {
1637        let origin = domain("com.");
1638        assert_eq!(
1639            Some(domain("www.example.com.")),
1640            DomainName::from_relative_dotted_string(&origin, "www.example.com.")
1641        );
1642    }
1643
1644    #[test]
1645    fn from_relative_dotted_string_relative() {
1646        let origin = domain("com.");
1647        assert_eq!(
1648            Some(domain("www.example.com.")),
1649            DomainName::from_relative_dotted_string(&origin, "www.example")
1650        );
1651    }
1652
1653    #[test]
1654    fn make_subdomain_is_subdomain() {
1655        let sub = domain("foo.");
1656        let apex = domain("bar.");
1657        let combined = sub.make_subdomain_of(&apex);
1658
1659        assert_eq!(Some(domain("foo.bar.")), combined);
1660        assert!(combined.unwrap().is_subdomain_of(&apex));
1661    }
1662
1663    #[test]
1664    fn domainname_conversions() {
1665        let mut rng = rand::rng();
1666        for _ in 0..100 {
1667            let labels_len = rng.random_range(0..5);
1668
1669            let mut dotted_string_input = String::new();
1670            let mut labels_input = Vec::with_capacity(labels_len);
1671            let mut output = String::new();
1672
1673            for i in 0..labels_len {
1674                let label_len = rng.random_range(1..10);
1675
1676                if i > 0 {
1677                    dotted_string_input.push('.');
1678                    output.push('.');
1679                }
1680
1681                let mut octets = BytesMut::with_capacity(label_len);
1682                for _ in 0..label_len {
1683                    let mut chr = rng.random_range(32..126);
1684
1685                    if chr == b'.'
1686                        || chr == b'*'
1687                        || chr == b'@'
1688                        || chr == b'#'
1689                        || (chr as char).is_whitespace()
1690                    {
1691                        chr = b'X';
1692                    }
1693
1694                    octets.put_u8(chr);
1695                    dotted_string_input.push(chr as char);
1696                    output.push(chr.to_ascii_lowercase() as char);
1697                }
1698                labels_input.push(Label::try_from(&octets.freeze()[..]).unwrap());
1699            }
1700
1701            labels_input.push(Label::new());
1702            dotted_string_input.push('.');
1703            output.push('.');
1704
1705            assert_eq!(
1706                Some(output.clone()),
1707                DomainName::from_dotted_string(&dotted_string_input).map(|d| d.to_dotted_string())
1708            );
1709
1710            assert_eq!(
1711                Some(output),
1712                DomainName::from_labels(labels_input.clone()).map(|d| d.to_dotted_string())
1713            );
1714
1715            assert_eq!(
1716                DomainName::from_dotted_string(&dotted_string_input).map(|d| d.to_dotted_string()),
1717                DomainName::from_labels(labels_input).map(|d| d.to_dotted_string())
1718            );
1719        }
1720    }
1721}
1722
1723#[cfg(any(feature = "test-util", test))]
1724#[allow(clippy::missing_panics_doc)]
1725pub mod test_util {
1726    use super::*;
1727
1728    use arbitrary::{Arbitrary, Unstructured};
1729    use rand::Rng;
1730
1731    pub fn arbitrary_resourcerecord() -> ResourceRecord {
1732        let mut rng = rand::rng();
1733        for size in [128, 256, 512, 1024, 2048, 4096] {
1734            let mut buf = BytesMut::with_capacity(size);
1735            for _ in 0..size {
1736                buf.put_u8(rng.random());
1737            }
1738
1739            if let Ok(rr) = ResourceRecord::arbitrary(&mut Unstructured::new(&buf.freeze())) {
1740                return rr;
1741            }
1742        }
1743
1744        panic!("could not generate arbitrary value!");
1745    }
1746
1747    pub fn domain(name: &str) -> DomainName {
1748        DomainName::from_dotted_string(name).unwrap()
1749    }
1750
1751    pub fn a_record(name: &str, address: Ipv4Addr) -> ResourceRecord {
1752        ResourceRecord {
1753            name: domain(name),
1754            rtype_with_data: RecordTypeWithData::A { address },
1755            rclass: RecordClass::IN,
1756            ttl: 300,
1757        }
1758    }
1759
1760    pub fn aaaa_record(name: &str, address: Ipv6Addr) -> ResourceRecord {
1761        ResourceRecord {
1762            name: domain(name),
1763            rtype_with_data: RecordTypeWithData::AAAA { address },
1764            rclass: RecordClass::IN,
1765            ttl: 300,
1766        }
1767    }
1768
1769    pub fn cname_record(name: &str, target_name: &str) -> ResourceRecord {
1770        ResourceRecord {
1771            name: domain(name),
1772            rtype_with_data: RecordTypeWithData::CNAME {
1773                cname: domain(target_name),
1774            },
1775            rclass: RecordClass::IN,
1776            ttl: 300,
1777        }
1778    }
1779
1780    pub fn ns_record(superdomain_name: &str, nameserver_name: &str) -> ResourceRecord {
1781        ResourceRecord {
1782            name: domain(superdomain_name),
1783            rtype_with_data: RecordTypeWithData::NS {
1784                nsdname: domain(nameserver_name),
1785            },
1786            rclass: RecordClass::IN,
1787            ttl: 300,
1788        }
1789    }
1790
1791    pub fn unknown_record(name: &str, octets: &[u8]) -> ResourceRecord {
1792        ResourceRecord {
1793            name: domain(name),
1794            rtype_with_data: RecordTypeWithData::Unknown {
1795                tag: RecordTypeUnknown(100),
1796                octets: Bytes::copy_from_slice(octets),
1797            },
1798            rclass: RecordClass::IN,
1799            ttl: 300,
1800        }
1801    }
1802}