Skip to main content

dns_types/protocol/
types.rs

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