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