Skip to main content

hickory_proto/rr/
record.rs

1// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! resource record implementation
9
10use alloc::{borrow::ToOwned, boxed::Box};
11use core::{cmp::Ordering, convert::TryFrom, fmt};
12
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16#[cfg(feature = "__dnssec")]
17use crate::dnssec::{Proof, Proven};
18#[cfg(test)]
19use crate::rr::rdata::A;
20use crate::{
21    error::ProtoResult,
22    rr::{Name, RData, RecordData, RecordType, dns_class::DNSClass},
23    serialize::binary::{
24        BinDecodable, BinDecoder, BinEncodable, BinEncoder, DecodeError, Restrict,
25    },
26};
27
28#[cfg(feature = "mdns")]
29/// From [RFC 6762](https://tools.ietf.org/html/rfc6762#section-10.2)
30/// ```text
31/// The cache-flush bit is the most significant bit of the second
32/// 16-bit word of a resource record in a Resource Record Section of a
33/// Multicast DNS message (the field conventionally referred to as the
34/// rrclass field), and the actual resource record class is the least
35/// significant fifteen bits of this field.
36/// ```
37const MDNS_ENABLE_CACHE_FLUSH: u16 = 1 << 15;
38/// Resource records are storage value in DNS, into which all key/value pair data is stored.
39///
40/// # Generic type
41/// * `R` - the RecordData type this resource record represents, if unknown at runtime use the `RData` abstract enum type
42///
43/// [RFC 1035](https://tools.ietf.org/html/rfc1035), DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987
44///
45/// ```text
46/// 4.1.3. Resource record format
47///
48/// The answer, authority, and additional sections all share the same
49/// format: a variable number of resource records, where the number of
50/// records is specified in the corresponding count field in the header.
51/// Each resource record has the following format:
52///                                     1  1  1  1  1  1
53///       0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
54///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
55///     |                                               |
56///     /                                               /
57///     /                      NAME                     /
58///     |                                               |
59///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
60///     |                      TYPE                     |
61///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
62///     |                     CLASS                     |
63///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
64///     |                      TTL                      |
65///     |                                               |
66///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
67///     |                   RDLENGTH                    |
68///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
69///     /                     RDATA                     /
70///     /                                               /
71///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
72///
73/// ```
74#[non_exhaustive]
75#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
76#[derive(Eq, Debug, Clone)]
77// TODO: make Record carry a lifetime for more efficient storage options in the future
78pub struct Record<R: RecordData = RData> {
79    /// ```text
80    /// NAME            a domain name to which this resource record pertains.
81    /// ```
82    pub name: Name,
83    /// ```text
84    /// CLASS           two octets which specify the class of the data in the
85    ///                 RDATA field.
86    /// ```
87    pub dns_class: DNSClass,
88    /// ```text
89    /// TTL             a 32 bit unsigned integer that specifies the time
90    ///                 interval (in seconds) that the resource record may be
91    ///                 cached before it should be discarded.  Zero values are
92    ///                 interpreted to mean that the RR can only be used for the
93    ///                 transaction in progress, and should not be cached.
94    /// ```
95    pub ttl: u32,
96    /// ```text
97    /// RDATA           a variable length string of octets that describes the
98    ///                 resource.  The format of this information varies
99    ///                 according to the TYPE and CLASS of the resource record.
100    ///                 For example, the if the TYPE is A and the CLASS is IN,
101    ///                 the RDATA field is a 4 octet ARPA Internet address.
102    /// ```
103    pub data: R,
104    /// Whether the mDNS cache-flush bit is set
105    /// See [RFC 6762](https://tools.ietf.org/html/rfc6762#section-10.2)
106    #[cfg(feature = "mdns")]
107    pub mdns_cache_flush: bool,
108    /// The proof of DNSSEC validation for this record.
109    ///
110    /// This is only valid if some form of validation has occurred.
111    #[cfg(feature = "__dnssec")]
112    pub proof: Proof,
113}
114
115impl Record {
116    #[cfg(test)]
117    pub(crate) fn stub() -> Self {
118        Self {
119            name: Name::from_ascii(".").unwrap(),
120            dns_class: DNSClass::IN,
121            ttl: 0,
122            data: RData::A(A::new(0, 0, 0, 0)),
123            #[cfg(feature = "mdns")]
124            mdns_cache_flush: false,
125            #[cfg(feature = "__dnssec")]
126            proof: Proof::default(),
127        }
128    }
129}
130
131impl Record {
132    /// Creates an update record with RDLENGTH=0
133    pub fn update0(name: Name, ttl: u32, rr_type: RecordType) -> Self {
134        Self {
135            name,
136            dns_class: DNSClass::IN,
137            ttl,
138            data: RData::Update0(rr_type),
139            #[cfg(feature = "mdns")]
140            mdns_cache_flush: false,
141            #[cfg(feature = "__dnssec")]
142            proof: Proof::default(),
143        }
144    }
145
146    /// Tries the borrow this record as the specific record type, T
147    pub fn try_borrow<T>(&self) -> Option<RecordRef<'_, T>>
148    where
149        T: RecordData,
150    {
151        RecordRef::try_from(self).ok()
152    }
153}
154
155impl<R: RecordData> Record<R> {
156    /// Create a record with the specified initial values.
157    ///
158    /// # Arguments
159    ///
160    /// * `name` - name of the resource records
161    /// * `ttl` - time-to-live is the amount of time this record should be cached before refreshing
162    /// * `rdata` - record data to associate with the Record
163    pub fn from_rdata(name: Name, ttl: u32, rdata: R) -> Self {
164        Self {
165            name,
166            dns_class: DNSClass::IN,
167            ttl,
168            data: rdata,
169            #[cfg(feature = "mdns")]
170            mdns_cache_flush: false,
171            #[cfg(feature = "__dnssec")]
172            proof: Proof::default(),
173        }
174    }
175
176    /// Converts this Record into a more specific version of RData
177    pub fn map<N: RecordData>(self, f: impl FnOnce(R) -> Option<N>) -> Option<Record<N>> {
178        let Self {
179            name,
180            dns_class,
181            ttl,
182            data: rdata,
183            #[cfg(feature = "mdns")]
184            mdns_cache_flush,
185            #[cfg(feature = "__dnssec")]
186            proof,
187        } = self;
188
189        Some(Record {
190            name,
191            dns_class,
192            ttl,
193            data: f(rdata)?,
194            #[cfg(feature = "mdns")]
195            mdns_cache_flush,
196            #[cfg(feature = "__dnssec")]
197            proof,
198        })
199    }
200
201    /// Converts this Record into a generic version of RData
202    pub fn into_record_of_rdata(self) -> Record<RData> {
203        let Self {
204            name,
205            dns_class,
206            ttl,
207            data: rdata,
208            #[cfg(feature = "mdns")]
209            mdns_cache_flush,
210            #[cfg(feature = "__dnssec")]
211            proof,
212        } = self;
213
214        let rdata = RecordData::into_rdata(rdata);
215
216        Record {
217            name,
218            dns_class,
219            ttl,
220            data: rdata,
221            #[cfg(feature = "mdns")]
222            mdns_cache_flush,
223            #[cfg(feature = "__dnssec")]
224            proof,
225        }
226    }
227
228    /// Decrement the record TTL by `offset` seconds.  If offset is greater than the record TTL,
229    /// the record TTL will be set to 0.
230    pub fn decrement_ttl(&mut self, offset: u32) -> &mut Self {
231        self.ttl = self.ttl.saturating_sub(offset);
232        self
233    }
234
235    /// Returns the type of the RecordData in the record
236    #[inline]
237    pub fn record_type(&self) -> RecordType {
238        self.data.record_type()
239    }
240}
241
242impl<R: RecordData> BinEncodable for Record<R> {
243    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
244        self.name.emit(encoder)?;
245        self.record_type().emit(encoder)?;
246
247        #[cfg(not(feature = "mdns"))]
248        self.dns_class.emit(encoder)?;
249
250        #[cfg(feature = "mdns")]
251        {
252            if self.mdns_cache_flush {
253                encoder.emit_u16(u16::from(self.dns_class) | MDNS_ENABLE_CACHE_FLUSH)?;
254            } else {
255                self.dns_class.emit(encoder)?;
256            }
257        }
258
259        encoder.emit_u32(self.ttl)?;
260
261        // place the RData length
262        let place = encoder.place::<u16>()?;
263
264        // write the RData
265        //   the None case is handled below by writing `0` for the length of the RData
266        //   this is in turn read as `None` during the `read` operation.
267        if !self.data.is_update() {
268            self.data.emit(encoder)?;
269        }
270
271        // get the length written
272        let len = encoder.len_since_place(&place);
273        assert!(len <= u16::MAX as usize);
274
275        // replace the location with the length
276        place.replace(encoder, len as u16)?;
277        Ok(())
278    }
279}
280
281impl<'r> BinDecodable<'r> for Record<RData> {
282    /// parse a resource record line example:
283    ///  WARNING: the record_bytes is 100% consumed and destroyed in this parsing process
284    fn read(decoder: &mut BinDecoder<'r>) -> Result<Self, DecodeError> {
285        // NAME            an owner name, i.e., the name of the node to which this
286        //                 resource record pertains.
287        let name_labels: Name = Name::read(decoder)?;
288
289        // TYPE            two octets containing one of the RR TYPE codes.
290        let record_type: RecordType = RecordType::read(decoder)?;
291
292        #[cfg(feature = "mdns")]
293        let mut mdns_cache_flush = false;
294
295        // CLASS           two octets containing one of the RR CLASS codes.
296        let class: DNSClass = if record_type == RecordType::OPT {
297            // verify that the OPT record is Root
298            if !name_labels.is_root() {
299                return Err(DecodeError::EdnsNameNotRoot(Box::new(name_labels)));
300            }
301
302            //  DNS Class is overloaded for OPT records in EDNS - RFC 6891
303            DNSClass::for_opt(
304                decoder.read_u16()?.unverified(/*restricted to a min of 512 in for_opt*/),
305            )
306        } else {
307            #[cfg(not(feature = "mdns"))]
308            {
309                DNSClass::read(decoder)?
310            }
311
312            #[cfg(feature = "mdns")]
313            {
314                let dns_class_value =
315                    decoder.read_u16()?.unverified(/*DNSClass::from_u16 will verify the value*/);
316                if dns_class_value & MDNS_ENABLE_CACHE_FLUSH > 0 {
317                    mdns_cache_flush = true;
318                    DNSClass::from(dns_class_value & !MDNS_ENABLE_CACHE_FLUSH)
319                } else {
320                    DNSClass::from(dns_class_value)
321                }
322            }
323        };
324
325        // TTL             a 32 bit signed integer that specifies the time interval
326        //                that the resource record may be cached before the source
327        //                of the information should again be consulted.  Zero
328        //                values are interpreted to mean that the RR can only be
329        //                used for the transaction in progress, and should not be
330        //                cached.  For example, SOA records are always distributed
331        //                with a zero TTL to prohibit caching.  Zero values can
332        //                also be used for extremely volatile data.
333        // note: u32 seems more accurate given that it can only be positive
334        let ttl: u32 = decoder.read_u32()?.unverified(/*any u32 is valid*/);
335
336        // RDLENGTH        an unsigned 16 bit integer that specifies the length in
337        //                octets of the RDATA field.
338        let rd_length = decoder
339            .read_u16()?
340            .verify_unwrap(|u| (*u as usize) <= decoder.len())
341            .map_err(|u| DecodeError::IncorrectRDataLengthRead {
342                read: decoder.len(),
343                len: u as usize,
344            })?;
345
346        // this is to handle updates, RFC 2136, which uses 0 to indicate certain aspects of pre-requisites.
347        // Null represents any data. The caller should validate whether this is allowed.
348        let rdata = if rd_length == 0 {
349            RData::Update0(record_type)
350        } else {
351            // RDATA           a variable length string of octets that describes the
352            //                resource.  The format of this information varies
353            //                according to the TYPE and CLASS of the resource record.
354            // Adding restrict to the rdata length because it's used for many calculations later
355            //  and must be validated before hand
356            RData::read(decoder, record_type, Restrict::new(rd_length))?
357        };
358
359        Ok(Self {
360            name: name_labels,
361            dns_class: class,
362            ttl,
363            data: rdata,
364            #[cfg(feature = "mdns")]
365            mdns_cache_flush,
366            #[cfg(feature = "__dnssec")]
367            proof: Proof::default(),
368        })
369    }
370}
371
372/// [RFC 1033](https://tools.ietf.org/html/rfc1033), DOMAIN OPERATIONS GUIDE, November 1987
373///
374/// ```text
375///   RESOURCE RECORDS
376///
377///   Records in the zone data files are called resource records (RRs).
378///   They are specified in RFC-883 and RFC-973.  An RR has a standard
379///   format as shown:
380///
381///           <name>   [<ttl>]   [<class>]   <type>   <data>
382///
383///   The record is divided into fields which are separated by white space.
384///
385///      <name>
386///
387///         The name field defines what domain name applies to the given
388///         RR.  In some cases the name field can be left blank and it will
389///         default to the name field of the previous RR.
390///
391///      <ttl>
392///
393///         TTL stands for Time To Live.  It specifies how long a domain
394///         resolver should cache the RR before it throws it out and asks a
395///         domain server again.  See the section on TTL's.  If you leave
396///         the TTL field blank it will default to the minimum time
397///         specified in the SOA record (described later).
398///
399///      <class>
400///
401///         The class field specifies the protocol group.  If left blank it
402///         will default to the last class specified.
403///
404///      <type>
405///
406///         The type field specifies what type of data is in the RR.  See
407///         the section on types.
408///
409///      <data>
410///
411///         The data field is defined differently for each type and class
412///         of data.  Popular RR data formats are described later.
413/// ```
414impl<R: RecordData> fmt::Display for Record<R> {
415    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
416        write!(
417            f,
418            "{name} {ttl} {class} {ty} {rdata}",
419            name = self.name,
420            ttl = self.ttl,
421            class = self.dns_class,
422            ty = self.record_type(),
423            rdata = self.data,
424        )?;
425
426        Ok(())
427    }
428}
429
430impl<R: RecordData> PartialEq for Record<R> {
431    /// Equality of records, as defined by
432    ///  [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
433    ///
434    /// ```text
435    ///   1.1.1. Two RRs are considered equal if their NAME, CLASS, TYPE,
436    ///   RDLENGTH and RDATA fields are equal.  Note that the time-to-live
437    ///   (TTL) field is explicitly excluded from the comparison.
438    ///
439    ///   1.1.2. The rules for comparison of character strings in names are
440    ///   specified in [RFC1035 2.3.3]. i.e. case insensitive
441    /// ```
442    fn eq(&self, other: &Self) -> bool {
443        // self == other && // the same pointer
444        self.name == other.name && self.dns_class == other.dns_class && self.data == other.data
445    }
446}
447
448/// returns the value of the compare if the items are greater or lesser, but continues on equal
449macro_rules! compare_or_equal {
450    ($x:ident, $y:ident, $z:ident) => {
451        match ($x).$z.cmp(&($y).$z) {
452            o @ Ordering::Less | o @ Ordering::Greater => return o,
453            Ordering::Equal => (),
454        }
455    };
456}
457
458impl Ord for Record {
459    /// Canonical ordering as defined by
460    ///  [RFC 4034](https://tools.ietf.org/html/rfc4034#section-6), DNSSEC Resource Records, March 2005
461    ///
462    /// ```text
463    /// 6.2.  Canonical RR Form
464    ///
465    ///    For the purposes of DNS security, the canonical form of an RR is the
466    ///    wire format of the RR where:
467    ///
468    ///    1.  every domain name in the RR is fully expanded (no DNS name
469    ///        compression) and fully qualified;
470    ///
471    ///    2.  all uppercase US-ASCII letters in the owner name of the RR are
472    ///        replaced by the corresponding lowercase US-ASCII letters;
473    ///
474    ///    3.  if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
475    ///        HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
476    ///        SRV, DNAME, A6, RRSIG, or NSEC, all uppercase US-ASCII letters in
477    ///        the DNS names contained within the RDATA are replaced by the
478    ///        corresponding lowercase US-ASCII letters;
479    ///
480    ///    4.  if the owner name of the RR is a wildcard name, the owner name is
481    ///        in its original unexpanded form, including the "*" label (no
482    ///        wildcard substitution); and
483    ///
484    ///    5.  the RR's TTL is set to its original value as it appears in the
485    ///        originating authoritative zone or the Original TTL field of the
486    ///        covering RRSIG RR.
487    /// ```
488    fn cmp(&self, other: &Self) -> Ordering {
489        // TODO: given that the ordering of Resource Records is dependent on it's binary form and this
490        //  method will be used during insertion sort or similar, we should probably do this
491        //  conversion once somehow and store it separately. Or should the internal storage of all
492        //  resource records be maintained in binary?
493
494        compare_or_equal!(self, other, name);
495        match self.record_type().cmp(&other.record_type()) {
496            o @ Ordering::Less | o @ Ordering::Greater => return o,
497            Ordering::Equal => {}
498        }
499        compare_or_equal!(self, other, dns_class);
500        compare_or_equal!(self, other, ttl);
501        compare_or_equal!(self, other, data);
502        Ordering::Equal
503    }
504}
505
506impl PartialOrd<Self> for Record {
507    /// Canonical ordering as defined by
508    ///  [RFC 4034](https://tools.ietf.org/html/rfc4034#section-6), DNSSEC Resource Records, March 2005
509    ///
510    /// ```text
511    /// 6.2.  Canonical RR Form
512    ///
513    ///    For the purposes of DNS security, the canonical form of an RR is the
514    ///    wire format of the RR where:
515    ///
516    ///    1.  every domain name in the RR is fully expanded (no DNS name
517    ///        compression) and fully qualified;
518    ///
519    ///    2.  all uppercase US-ASCII letters in the owner name of the RR are
520    ///        replaced by the corresponding lowercase US-ASCII letters;
521    ///
522    ///    3.  if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
523    ///        HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
524    ///        SRV, DNAME, A6, RRSIG, or NSEC, all uppercase US-ASCII letters in
525    ///        the DNS names contained within the RDATA are replaced by the
526    ///        corresponding lowercase US-ASCII letters;
527    ///
528    ///    4.  if the owner name of the RR is a wildcard name, the owner name is
529    ///        in its original unexpanded form, including the "*" label (no
530    ///        wildcard substitution); and
531    ///
532    ///    5.  the RR's TTL is set to its original value as it appears in the
533    ///        originating authoritative zone or the Original TTL field of the
534    ///        covering RRSIG RR.
535    /// ```
536    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
537        Some(self.cmp(other))
538    }
539}
540
541#[cfg(feature = "__dnssec")]
542impl From<Record> for Proven<Record> {
543    fn from(record: Record) -> Self {
544        let proof = record.proof;
545        Self::new(proof, record)
546    }
547}
548
549#[cfg(feature = "__dnssec")]
550impl<'a> From<&'a Record> for Proven<&'a Record> {
551    fn from(record: &'a Record) -> Self {
552        let proof = record.proof;
553        Self::new(proof, record)
554    }
555}
556
557/// A Record where the RecordData type is already known
558pub struct RecordRef<'a, R: RecordData> {
559    name: &'a Name,
560    dns_class: DNSClass,
561    ttl: u32,
562    data: &'a R,
563    #[cfg(feature = "mdns")]
564    mdns_cache_flush: bool,
565    #[cfg(feature = "__dnssec")]
566    proof: Proof,
567}
568
569impl<R: RecordData> Clone for RecordRef<'_, R> {
570    fn clone(&self) -> Self {
571        *self
572    }
573}
574
575impl<R: RecordData> Copy for RecordRef<'_, R> {}
576
577impl<R: RecordData> RecordRef<'_, R> {
578    /// Allocates space for a Record with the same fields
579    pub fn to_owned(&self) -> Record<R> {
580        Record {
581            name: self.name.to_owned(),
582            dns_class: self.dns_class,
583            ttl: self.ttl,
584            data: self.data.clone(),
585            #[cfg(feature = "mdns")]
586            mdns_cache_flush: self.mdns_cache_flush,
587            #[cfg(feature = "__dnssec")]
588            proof: self.proof,
589        }
590    }
591
592    /// Returns the name of the record
593    #[inline]
594    pub fn name(&self) -> &Name {
595        self.name
596    }
597
598    /// Returns the type of the RecordData in the record
599    #[inline]
600    pub fn record_type(&self) -> RecordType {
601        self.data.record_type()
602    }
603
604    /// Returns the DNSClass of the Record, generally IN fro internet
605    #[inline]
606    pub fn dns_class(&self) -> DNSClass {
607        self.dns_class
608    }
609
610    /// Returns the time-to-live of the record, for caching purposes
611    #[inline]
612    pub fn ttl(&self) -> u32 {
613        self.ttl
614    }
615
616    /// Returns the Record Data, i.e. the record information
617    #[inline]
618    pub fn data(&self) -> &R {
619        self.data
620    }
621
622    /// Returns if the mDNS cache-flush bit is set or not
623    /// See [RFC 6762](https://tools.ietf.org/html/rfc6762#section-10.2)
624    #[cfg(feature = "mdns")]
625    #[inline]
626    pub fn mdns_cache_flush(&self) -> bool {
627        self.mdns_cache_flush
628    }
629
630    /// The Proof of DNSSEC validation for this record, this is only valid if some form of validation has occurred
631    #[cfg(feature = "__dnssec")]
632    #[inline]
633    pub fn proof(&self) -> Proof {
634        self.proof
635    }
636}
637
638impl<'a, R: RecordData> TryFrom<&'a Record> for RecordRef<'a, R> {
639    type Error = &'a Record;
640
641    fn try_from(record: &'a Record) -> Result<Self, Self::Error> {
642        let Record {
643            name,
644            dns_class,
645            ttl,
646            data: rdata,
647            #[cfg(feature = "mdns")]
648            mdns_cache_flush,
649            #[cfg(feature = "__dnssec")]
650            proof,
651        } = record;
652
653        match R::try_borrow(rdata) {
654            None => Err(record),
655            Some(rdata) => Ok(Self {
656                name,
657                dns_class: *dns_class,
658                ttl: *ttl,
659                data: rdata,
660                #[cfg(feature = "mdns")]
661                mdns_cache_flush: *mdns_cache_flush,
662                #[cfg(feature = "__dnssec")]
663                proof: *proof,
664            }),
665        }
666    }
667}
668
669#[cfg(test)]
670mod tests {
671    #![allow(clippy::dbg_macro, clippy::print_stdout)]
672
673    use alloc::vec::Vec;
674    use core::cmp::Ordering;
675    use core::str::FromStr;
676    #[cfg(feature = "std")]
677    use std::println;
678
679    use super::*;
680    use crate::rr::Name;
681    use crate::rr::dns_class::DNSClass;
682    use crate::rr::rdata::{A, AAAA};
683    use crate::rr::record_data::RData;
684
685    #[test]
686    fn test_emit_and_read() {
687        let record = Record::from_rdata(
688            Name::from_str("www.example.com.").unwrap(),
689            5,
690            RData::A(A::new(192, 168, 0, 1)),
691        );
692
693        let mut vec_bytes: Vec<u8> = Vec::with_capacity(512);
694        {
695            let mut encoder = BinEncoder::new(&mut vec_bytes);
696            record.emit(&mut encoder).unwrap();
697        }
698
699        let mut decoder = BinDecoder::new(&vec_bytes);
700
701        let got = Record::read(&mut decoder).unwrap();
702
703        assert_eq!(got, record);
704    }
705
706    #[test]
707    fn test_order() {
708        let record = Record::from_rdata(
709            Name::from_str("www.example.com").unwrap(),
710            5,
711            RData::A(A::new(192, 168, 0, 1)),
712        );
713
714        let mut greater_name = record.clone();
715        greater_name.name = Name::from_str("zzz.example.com").unwrap();
716
717        let mut greater_type = record.clone().into_record_of_rdata();
718        greater_type.data = RData::AAAA(AAAA::new(0, 0, 0, 0, 0, 0, 0, 0));
719
720        let mut greater_class = record.clone();
721        greater_class.dns_class = DNSClass::NONE;
722
723        let mut greater_rdata = record.clone();
724        greater_rdata.data = RData::A(A::new(192, 168, 0, 255));
725
726        let compares = vec![
727            (&record, &greater_name),
728            (&record, &greater_type),
729            (&record, &greater_class),
730            (&record, &greater_rdata),
731        ];
732
733        assert_eq!(record.clone(), record.clone());
734        for (r, g) in compares {
735            #[cfg(feature = "std")]
736            println!("r, g: {r:?}, {g:?}");
737            assert_eq!(r.cmp(g), Ordering::Less);
738        }
739    }
740
741    #[cfg(feature = "mdns")]
742    #[test]
743    fn test_mdns_cache_flush_bit_handling() {
744        const RR_CLASS_OFFSET: usize = 1 /* empty name */ +
745            size_of::<u16>() /* rr_type */;
746
747        let mut record = Record::<RData>::stub();
748        record.mdns_cache_flush = true;
749
750        let mut vec_bytes: Vec<u8> = Vec::with_capacity(512);
751        {
752            let mut encoder = BinEncoder::new(&mut vec_bytes);
753            record.emit(&mut encoder).unwrap();
754
755            let rr_class_slice = encoder.slice_of(RR_CLASS_OFFSET, RR_CLASS_OFFSET + 2);
756            assert_eq!(rr_class_slice, &[0x80, 0x01]);
757        }
758
759        let mut decoder = BinDecoder::new(&vec_bytes);
760
761        let got = Record::<RData>::read(&mut decoder).unwrap();
762
763        assert_eq!(got.dns_class, DNSClass::IN);
764        assert!(got.mdns_cache_flush);
765    }
766}