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