domain/base/
record.rs

1//! Resource Records.
2//!
3//! This module defines types and traits related to DNS resource records. The
4//! most complete type is [`Record`] which contains a complete record for a
5//! certain record type. [`RecordHeader`] contains the data from a record’s
6//! header, the first couple of octets common to all records. Finally,
7//! [`ParsedRecord`] is similar to [`Record`] but contains the record data
8//! in its raw, encoded form.
9//!
10//! The [`AsRecord`] trait is used by the message builder to consider
11//! different representations of records.
12//!
13//! [`AsRecord`]: trait.AsRecord.html
14//! [`Record`]: struct.Record.html
15//! [`RecordHeader`]: struct.RecordHeader.html
16//! [`ParsedRecord`]: struct.ParsedRecord.html
17
18use super::cmp::CanonicalOrd;
19use super::iana::{Class, Rtype};
20use super::name::{FlattenInto, ParsedDname, ToDname};
21use super::rdata::{ComposeRecordData, ParseRecordData, RecordData};
22use super::wire::{Compose, Composer, FormError, Parse, ParseError};
23use core::cmp::Ordering;
24use core::time::Duration;
25use core::{fmt, hash};
26use octseq::builder::ShortBuf;
27use octseq::octets::{Octets, OctetsFrom};
28use octseq::parse::Parser;
29use octseq::OctetsBuilder;
30
31//------------ Record --------------------------------------------------------
32
33/// A DNS resource record.
34///
35/// All information available through the DNS is stored in resource records.
36/// They have a three part key of a domain name, resource record type, and
37/// class. Data is arranged in a tree which is navigated using the domain
38/// name. Each node in the tree carries a label, starting with the root
39/// label as the top-most node. The tree is traversed by stepping through the
40/// name from right to left, finding a child node carring the label of each
41/// step. The domain name resulting from this traversal is part of the
42/// record itself. It is called the *owner* of the record.
43///
44/// The record type describes the kind of data the record holds, such as IP
45/// addresses. The class, finally, describes which sort of network the
46/// information is for. The DNS was originally intended to be used for
47/// networks other than the Internet as well. In practice, the only relevant
48/// class is IN, the Internet. Note that each class has its own tree of nodes.
49///
50/// The payload of a resource record is its data. Its purpose, meaning, and
51/// format is determined by the record type (technically, also its class).
52/// For each unique three-part key there can be multiple resource records.
53/// All these records for the same key are called *resource record sets,*
54/// most often shortened to ‘RRset.’
55///
56/// There is one more piece of data: the TTL or time to live. This value
57/// says how long a record remains valid before it should be refreshed from
58/// its original source. The TTL is used to add caching
59/// facilities to the DNS.
60///
61/// Values of the `Record` type represent one single resource record. Since
62/// there are currently more than eighty record types—see [`Rtype`] for a
63/// complete list—, the type is generic over a trait for record data. This
64/// trait holds both the record type value and the record data as they are
65/// inseparably entwined.
66///
67/// Because a record’s owner is a domain name, the `Record` type is
68/// additionally generic over the domain name type is for it.
69///
70/// There is three ways to create a record value. First, you can make one
71/// yourself using the [`new`] function. It will neatly take care of all
72/// the generics through type inference. Secondly, you can parse a record
73/// from an existing message. [`Message`] and its friends provide a way to
74/// do that; see there for all the details. Finally, you can scan a record
75/// from zone file format. See the crate’s
76#[cfg_attr(feature = "zonefile", doc = "[zonefile][crate::zonefile]")]
77#[cfg_attr(not(feature = "zonefile"), doc = "zonefile")]
78/// module for that.
79///
80/// [`new`]: #method.new
81/// [`Message`]: ../message/struct.Message.html
82/// [`MessageBuilder`]: ../message_builder/struct.MessageBuilder.html
83/// [`Rtype`]: ../../iana/enum.Rtype.html
84#[derive(Clone)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
86pub struct Record<Name, Data> {
87    /// The owner of the record.
88    owner: Name,
89
90    /// The class of the record.
91    class: Class,
92
93    /// The time-to-live value of the record.
94    ttl: Ttl,
95
96    /// The record data. The value also specifies the record’s type.
97    data: Data,
98}
99
100/// # Creation and Element Access
101///
102impl<Name, Data> Record<Name, Data> {
103    /// Creates a new record from its parts.
104    pub fn new(owner: Name, class: Class, ttl: Ttl, data: Data) -> Self {
105        Record {
106            owner,
107            class,
108            ttl,
109            data,
110        }
111    }
112
113    /// Creates a new record from a compatible record.
114    ///
115    /// This function only exists because the equivalent `From` implementation
116    /// is currently not possible,
117    pub fn from_record<NN, DD>(record: Record<NN, DD>) -> Self
118    where
119        Name: From<NN>,
120        Data: From<DD>,
121    {
122        Self::new(
123            record.owner.into(),
124            record.class,
125            record.ttl,
126            record.data.into(),
127        )
128    }
129
130    /// Returns a reference to the owner domain name.
131    ///
132    /// The owner of a record is the domain name that specifies the node in
133    /// the DNS tree this record belongs to.
134    pub fn owner(&self) -> &Name {
135        &self.owner
136    }
137
138    /// Returns the record type.
139    pub fn rtype(&self) -> Rtype
140    where
141        Data: RecordData,
142    {
143        self.data.rtype()
144    }
145
146    /// Returns the record class.
147    pub fn class(&self) -> Class {
148        self.class
149    }
150
151    /// Sets the record’s class.
152    pub fn set_class(&mut self, class: Class) {
153        self.class = class
154    }
155
156    /// Returns the record’s time-to-live.
157    pub fn ttl(&self) -> Ttl {
158        self.ttl
159    }
160
161    /// Sets the record’s time-to-live.
162    pub fn set_ttl(&mut self, ttl: Ttl) {
163        self.ttl = ttl
164    }
165
166    /// Return a reference to the record data.
167    pub fn data(&self) -> &Data {
168        &self.data
169    }
170
171    /// Returns a mutable reference to the record data.
172    pub fn data_mut(&mut self) -> &mut Data {
173        &mut self.data
174    }
175
176    /// Trades the record for its record data.
177    pub fn into_data(self) -> Data {
178        self.data
179    }
180
181    /// Trades the record for its owner name and data.
182    pub fn into_owner_and_data(self) -> (Name, Data) {
183        (self.owner, self.data)
184    }
185}
186
187/// Parsing and Composing
188///
189impl<Octs, Data> Record<ParsedDname<Octs>, Data> {
190    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + 'a>(
191        parser: &mut Parser<'a, Src>,
192    ) -> Result<Option<Self>, ParseError>
193    where
194        Data: ParseRecordData<'a, Src>,
195    {
196        let header = RecordHeader::parse(parser)?;
197        header.parse_into_record(parser)
198    }
199}
200
201impl<N: ToDname, D: RecordData + ComposeRecordData> Record<N, D> {
202    pub fn compose<Target: Composer + ?Sized>(
203        &self,
204        target: &mut Target,
205    ) -> Result<(), Target::AppendError> {
206        target.append_compressed_dname(&self.owner)?;
207        self.data.rtype().compose(target)?;
208        self.class.compose(target)?;
209        self.ttl.compose(target)?;
210        self.data.compose_len_rdata(target)
211    }
212
213    pub fn compose_canonical<Target: Composer + ?Sized>(
214        &self,
215        target: &mut Target,
216    ) -> Result<(), Target::AppendError> {
217        self.owner.compose_canonical(target)?;
218        self.data.rtype().compose(target)?;
219        self.class.compose(target)?;
220        self.ttl.compose(target)?;
221        self.data.compose_canonical_len_rdata(target)
222    }
223}
224
225//--- From
226
227impl<N, D> From<(N, Class, u32, D)> for Record<N, D> {
228    fn from((owner, class, ttl, data): (N, Class, u32, D)) -> Self {
229        Self::new(owner, class, Ttl::from_secs(ttl), data)
230    }
231}
232
233impl<N, D> From<(N, Class, Ttl, D)> for Record<N, D> {
234    fn from((owner, class, ttl, data): (N, Class, Ttl, D)) -> Self {
235        Self::new(owner, class, ttl, data)
236    }
237}
238
239impl<N, D> From<(N, u32, D)> for Record<N, D> {
240    fn from((owner, ttl, data): (N, u32, D)) -> Self {
241        Self::new(owner, Class::In, Ttl::from_secs(ttl), data)
242    }
243}
244
245//--- OctetsFrom and FlattenInto
246//
247// XXX We don’t have blanket FromOctets for a type T into itself, so this may
248//     not always work as expected. Not sure what we can do about it?
249
250impl<Name, Data, SrcName, SrcData> OctetsFrom<Record<SrcName, SrcData>>
251    for Record<Name, Data>
252where
253    Name: OctetsFrom<SrcName>,
254    Data: OctetsFrom<SrcData>,
255    Data::Error: From<Name::Error>,
256{
257    type Error = Data::Error;
258
259    fn try_octets_from(
260        source: Record<SrcName, SrcData>,
261    ) -> Result<Self, Self::Error> {
262        Ok(Record {
263            owner: Name::try_octets_from(source.owner)?,
264            class: source.class,
265            ttl: source.ttl,
266            data: Data::try_octets_from(source.data)?,
267        })
268    }
269}
270
271impl<Name, TName, Data, TData> FlattenInto<Record<TName, TData>>
272    for Record<Name, Data>
273where
274    Name: FlattenInto<TName>,
275    Data: FlattenInto<TData, AppendError = Name::AppendError>,
276{
277    type AppendError = Name::AppendError;
278
279    fn try_flatten_into(
280        self,
281    ) -> Result<Record<TName, TData>, Name::AppendError> {
282        Ok(Record::new(
283            self.owner.try_flatten_into()?,
284            self.class,
285            self.ttl,
286            self.data.try_flatten_into()?,
287        ))
288    }
289}
290
291//--- PartialEq and Eq
292
293impl<N, NN, D, DD> PartialEq<Record<NN, DD>> for Record<N, D>
294where
295    N: PartialEq<NN>,
296    D: RecordData + PartialEq<DD>,
297    DD: RecordData,
298{
299    fn eq(&self, other: &Record<NN, DD>) -> bool {
300        self.owner == other.owner
301            && self.class == other.class
302            && self.data == other.data
303    }
304}
305
306impl<N: Eq, D: RecordData + Eq> Eq for Record<N, D> {}
307
308//--- PartialOrd, Ord, and CanonicalOrd
309
310impl<N, NN, D, DD> PartialOrd<Record<NN, DD>> for Record<N, D>
311where
312    N: PartialOrd<NN>,
313    D: RecordData + PartialOrd<DD>,
314    DD: RecordData,
315{
316    fn partial_cmp(&self, other: &Record<NN, DD>) -> Option<Ordering> {
317        match self.owner.partial_cmp(&other.owner) {
318            Some(Ordering::Equal) => {}
319            res => return res,
320        }
321        match self.class.partial_cmp(&other.class) {
322            Some(Ordering::Equal) => {}
323            res => return res,
324        }
325        self.data.partial_cmp(&other.data)
326    }
327}
328
329impl<N, D> Ord for Record<N, D>
330where
331    N: Ord,
332    D: RecordData + Ord,
333{
334    fn cmp(&self, other: &Self) -> Ordering {
335        match self.owner.cmp(&other.owner) {
336            Ordering::Equal => {}
337            res => return res,
338        }
339        match self.class.cmp(&other.class) {
340            Ordering::Equal => {}
341            res => return res,
342        }
343        self.data.cmp(&other.data)
344    }
345}
346
347impl<N, NN, D, DD> CanonicalOrd<Record<NN, DD>> for Record<N, D>
348where
349    N: ToDname,
350    NN: ToDname,
351    D: RecordData + CanonicalOrd<DD>,
352    DD: RecordData,
353{
354    fn canonical_cmp(&self, other: &Record<NN, DD>) -> Ordering {
355        // This sort order will keep all records of a zone together. Ie.,
356        // all the records with the same zone and ending in a given name
357        // form one sequence.
358        match self.class.cmp(&other.class) {
359            Ordering::Equal => {}
360            res => return res,
361        }
362        match self.owner.name_cmp(&other.owner) {
363            Ordering::Equal => {}
364            res => return res,
365        }
366        match self.rtype().cmp(&other.rtype()) {
367            Ordering::Equal => {}
368            res => return res,
369        }
370        self.data.canonical_cmp(&other.data)
371    }
372}
373
374//--- Hash
375
376impl<Name, Data> hash::Hash for Record<Name, Data>
377where
378    Name: hash::Hash,
379    Data: hash::Hash,
380{
381    fn hash<H: hash::Hasher>(&self, state: &mut H) {
382        self.owner.hash(state);
383        self.class.hash(state);
384        self.ttl.hash(state);
385        self.data.hash(state);
386    }
387}
388
389//--- Display and Debug
390
391impl<Name, Data> fmt::Display for Record<Name, Data>
392where
393    Name: fmt::Display,
394    Data: RecordData + fmt::Display,
395{
396    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
397        write!(
398            f,
399            "{}. {} {} {} {}",
400            self.owner,
401            self.ttl.as_secs(),
402            self.class,
403            self.data.rtype(),
404            self.data
405        )
406    }
407}
408
409impl<Name, Data> fmt::Debug for Record<Name, Data>
410where
411    Name: fmt::Debug,
412    Data: fmt::Debug,
413{
414    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
415        f.debug_struct("Record")
416            .field("owner", &self.owner)
417            .field("class", &self.class)
418            .field("ttl", &self.ttl)
419            .field("data", &self.data)
420            .finish()
421    }
422}
423
424//------------ ComposeRecord -------------------------------------------------
425
426/// A helper trait allowing construction of records on the fly.
427///
428/// The trait’s primary users arer the three record section buider type of
429/// the [message builder] system. Their `push` methods accept anything that
430/// implements this trait.
431///
432/// Implementations are provided for [`Record`] values and references. In
433/// addition, a tuple of a domain name, class, TTL, and record data can be
434/// used as this trait, saving the detour of constructing a record first.
435/// Since the class is pretty much always `Class::In`, it can be left out in
436/// this case.
437///
438/// [`Class::In`]: ../iana/class/enum.Class.html#variant.In
439/// [`Record`]: struct.Record.html
440pub trait ComposeRecord {
441    fn compose_record<Target: Composer + ?Sized>(
442        &self,
443        target: &mut Target,
444    ) -> Result<(), Target::AppendError>;
445}
446
447impl<'a, T: ComposeRecord> ComposeRecord for &'a T {
448    fn compose_record<Target: Composer + ?Sized>(
449        &self,
450        target: &mut Target,
451    ) -> Result<(), Target::AppendError> {
452        (*self).compose_record(target)
453    }
454}
455
456impl<Name, Data> ComposeRecord for Record<Name, Data>
457where
458    Name: ToDname,
459    Data: ComposeRecordData,
460{
461    fn compose_record<Target: Composer + ?Sized>(
462        &self,
463        target: &mut Target,
464    ) -> Result<(), Target::AppendError> {
465        self.compose(target)
466    }
467}
468
469impl<Name, Data> ComposeRecord for (Name, Class, u32, Data)
470where
471    Name: ToDname,
472    Data: ComposeRecordData,
473{
474    fn compose_record<Target: Composer + ?Sized>(
475        &self,
476        target: &mut Target,
477    ) -> Result<(), Target::AppendError> {
478        Record::new(&self.0, self.1, Ttl::from_secs(self.2), &self.3)
479            .compose(target)
480    }
481}
482
483impl<Name, Data> ComposeRecord for (Name, Class, Ttl, Data)
484where
485    Name: ToDname,
486    Data: ComposeRecordData,
487{
488    fn compose_record<Target: Composer + ?Sized>(
489        &self,
490        target: &mut Target,
491    ) -> Result<(), Target::AppendError> {
492        Record::new(&self.0, self.1, self.2, &self.3).compose(target)
493    }
494}
495
496impl<Name, Data> ComposeRecord for (Name, u32, Data)
497where
498    Name: ToDname,
499    Data: ComposeRecordData,
500{
501    fn compose_record<Target: Composer + ?Sized>(
502        &self,
503        target: &mut Target,
504    ) -> Result<(), Target::AppendError> {
505        Record::new(&self.0, Class::In, Ttl::from_secs(self.1), &self.2)
506            .compose(target)
507    }
508}
509
510impl<Name, Data> ComposeRecord for (Name, Ttl, Data)
511where
512    Name: ToDname,
513    Data: ComposeRecordData,
514{
515    fn compose_record<Target: Composer + ?Sized>(
516        &self,
517        target: &mut Target,
518    ) -> Result<(), Target::AppendError> {
519        Record::new(&self.0, Class::In, self.1, &self.2).compose(target)
520    }
521}
522
523//------------ RecordHeader --------------------------------------------------
524
525/// The header of a resource record.
526///
527/// This type encapsulates the common header of a resource record. It consists
528/// of the owner, record type, class, TTL, and the length of the record data.
529/// It is effectively a helper type for dealing with resource records encoded
530/// in a DNS message.
531///
532/// See [`Record`] for more details about resource records.
533#[derive(Clone)]
534#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
535pub struct RecordHeader<Name> {
536    owner: Name,
537    rtype: Rtype,
538    class: Class,
539    ttl: Ttl,
540    rdlen: u16,
541}
542
543impl<Name> RecordHeader<Name> {
544    /// Creates a new record header from its components.
545    pub fn new(
546        owner: Name,
547        rtype: Rtype,
548        class: Class,
549        ttl: Ttl,
550        rdlen: u16,
551    ) -> Self {
552        RecordHeader {
553            owner,
554            rtype,
555            class,
556            ttl,
557            rdlen,
558        }
559    }
560}
561
562impl<'a, Octs: Octets + ?Sized> RecordHeader<ParsedDname<&'a Octs>> {
563    fn deref_owner(&self) -> RecordHeader<ParsedDname<Octs::Range<'a>>> {
564        RecordHeader {
565            owner: self.owner.deref_octets(),
566            rtype: self.rtype,
567            class: self.class,
568            ttl: self.ttl,
569            rdlen: self.rdlen,
570        }
571    }
572}
573
574impl<Name> RecordHeader<Name> {
575    /// Returns a reference to the owner of the record.
576    pub fn owner(&self) -> &Name {
577        &self.owner
578    }
579
580    /// Returns the record type of the record.
581    pub fn rtype(&self) -> Rtype {
582        self.rtype
583    }
584
585    /// Returns the class of the record.
586    pub fn class(&self) -> Class {
587        self.class
588    }
589
590    /// Returns the TTL of the record.
591    pub fn ttl(&self) -> Ttl {
592        self.ttl
593    }
594
595    /// Returns the data length of the record.
596    pub fn rdlen(&self) -> u16 {
597        self.rdlen
598    }
599
600    /// Converts the header into an actual record.
601    pub fn into_record<Data>(self, data: Data) -> Record<Name, Data> {
602        Record::new(self.owner, self.class, self.ttl, data)
603    }
604}
605
606/// # Parsing and Composing
607///
608impl<Octs> RecordHeader<ParsedDname<Octs>> {
609    pub fn parse<'a, Src: Octets<Range<'a> = Octs>>(
610        parser: &mut Parser<'a, Src>,
611    ) -> Result<Self, ParseError> {
612        RecordHeader::parse_ref(parser).map(|res| res.deref_owner())
613    }
614}
615
616impl<'a, Octs: AsRef<[u8]> + ?Sized> RecordHeader<ParsedDname<&'a Octs>> {
617    pub fn parse_ref(
618        parser: &mut Parser<'a, Octs>,
619    ) -> Result<Self, ParseError> {
620        Ok(RecordHeader::new(
621            ParsedDname::parse_ref(parser)?,
622            Rtype::parse(parser)?,
623            Class::parse(parser)?,
624            Ttl::parse(parser)?,
625            parser.parse_u16_be()?,
626        ))
627    }
628}
629
630impl<Name> RecordHeader<Name> {
631    /// Parses a record header and then skips over the data.
632    ///
633    /// If the function succeeds, the parser will be positioned right behind
634    /// the end of the record.
635    pub fn parse_and_skip<'a, Octs>(
636        parser: &mut Parser<'a, Octs>,
637    ) -> Result<Self, ParseError>
638    where
639        Self: Parse<'a, Octs>,
640        Octs: Octets,
641    {
642        let header = Self::parse(parser)?;
643        match parser.advance(header.rdlen() as usize) {
644            Ok(()) => Ok(header),
645            Err(_) => Err(ParseError::ShortInput),
646        }
647    }
648}
649
650impl RecordHeader<()> {
651    /// Parses only the record length and skips over all the other fields.
652    fn parse_rdlen<Octs: Octets + ?Sized>(
653        parser: &mut Parser<Octs>,
654    ) -> Result<u16, ParseError> {
655        ParsedDname::skip(parser)?;
656        parser.advance(
657            (Rtype::COMPOSE_LEN + Class::COMPOSE_LEN + u32::COMPOSE_LEN)
658                .into(),
659        )?;
660        u16::parse(parser)
661    }
662}
663
664impl<Octs> RecordHeader<ParsedDname<Octs>> {
665    /// Parses the remainder of the record and returns it.
666    ///
667    /// The method assumes that the parsers is currently positioned right
668    /// after the end of the record header. If the record data type `D`
669    /// feels capable of parsing a record with a header of `self`, the
670    /// method will parse the data and return a full `Record<D>`. Otherwise,
671    /// it skips over the record data.
672    pub fn parse_into_record<'a, Src, Data>(
673        self,
674        parser: &mut Parser<'a, Src>,
675    ) -> Result<Option<Record<ParsedDname<Octs>, Data>>, ParseError>
676    where
677        Src: AsRef<[u8]> + ?Sized,
678        Data: ParseRecordData<'a, Src>,
679    {
680        let mut parser = parser.parse_parser(self.rdlen as usize)?;
681        let res = Data::parse_rdata(self.rtype, &mut parser)?
682            .map(|data| Record::new(self.owner, self.class, self.ttl, data));
683        if res.is_some() && parser.remaining() > 0 {
684            return Err(ParseError::Form(FormError::new(
685                "trailing data in option",
686            )));
687        }
688        Ok(res)
689    }
690}
691
692impl<Name: ToDname> RecordHeader<Name> {
693    pub fn compose<Target: Composer + ?Sized>(
694        &self,
695        buf: &mut Target,
696    ) -> Result<(), Target::AppendError> {
697        buf.append_compressed_dname(&self.owner)?;
698        self.rtype.compose(buf)?;
699        self.class.compose(buf)?;
700        self.ttl.compose(buf)?;
701        self.rdlen.compose(buf)
702    }
703
704    pub fn compose_canonical<Target: Composer + ?Sized>(
705        &self,
706        buf: &mut Target,
707    ) -> Result<(), Target::AppendError> {
708        self.owner.compose_canonical(buf)?;
709        self.rtype.compose(buf)?;
710        self.class.compose(buf)?;
711        self.ttl.compose(buf)?;
712        self.rdlen.compose(buf)
713    }
714}
715
716//--- PartialEq and Eq
717
718impl<Name, NName> PartialEq<RecordHeader<NName>> for RecordHeader<Name>
719where
720    Name: ToDname,
721    NName: ToDname,
722{
723    fn eq(&self, other: &RecordHeader<NName>) -> bool {
724        self.owner.name_eq(&other.owner)
725            && self.rtype == other.rtype
726            && self.class == other.class
727            && self.ttl == other.ttl
728            && self.rdlen == other.rdlen
729    }
730}
731
732impl<Name: ToDname> Eq for RecordHeader<Name> {}
733
734//--- PartialOrd and Ord
735//
736// No CanonicalOrd because that doesn’t really make sense.
737
738impl<Name, NName> PartialOrd<RecordHeader<NName>> for RecordHeader<Name>
739where
740    Name: ToDname,
741    NName: ToDname,
742{
743    fn partial_cmp(&self, other: &RecordHeader<NName>) -> Option<Ordering> {
744        match self.owner.name_cmp(&other.owner) {
745            Ordering::Equal => {}
746            other => return Some(other),
747        }
748        match self.rtype.partial_cmp(&other.rtype) {
749            Some(Ordering::Equal) => {}
750            other => return other,
751        }
752        match self.class.partial_cmp(&other.class) {
753            Some(Ordering::Equal) => {}
754            other => return other,
755        }
756        match self.ttl.partial_cmp(&other.ttl) {
757            Some(Ordering::Equal) => {}
758            other => return other,
759        }
760        self.rdlen.partial_cmp(&other.rdlen)
761    }
762}
763
764impl<Name: ToDname> Ord for RecordHeader<Name> {
765    fn cmp(&self, other: &Self) -> Ordering {
766        match self.owner.name_cmp(&other.owner) {
767            Ordering::Equal => {}
768            other => return other,
769        }
770        match self.rtype.cmp(&other.rtype) {
771            Ordering::Equal => {}
772            other => return other,
773        }
774        match self.class.cmp(&other.class) {
775            Ordering::Equal => {}
776            other => return other,
777        }
778        match self.ttl.cmp(&other.ttl) {
779            Ordering::Equal => {}
780            other => return other,
781        }
782        self.rdlen.cmp(&other.rdlen)
783    }
784}
785
786//--- Hash
787
788impl<Name: hash::Hash> hash::Hash for RecordHeader<Name> {
789    fn hash<H: hash::Hasher>(&self, state: &mut H) {
790        self.owner.hash(state);
791        self.rtype.hash(state);
792        self.class.hash(state);
793        self.ttl.hash(state);
794        self.rdlen.hash(state);
795    }
796}
797
798//--- Debug
799
800impl<Name: fmt::Debug> fmt::Debug for RecordHeader<Name> {
801    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
802        f.debug_struct("RecordHeader")
803            .field("owner", &self.owner)
804            .field("rtype", &self.rtype)
805            .field("class", &self.class)
806            .field("ttl", &self.ttl)
807            .field("rdlen", &self.rdlen)
808            .finish()
809    }
810}
811
812//------------ ParsedRecord --------------------------------------------------
813
814/// A raw record parsed from a message.
815///
816/// A value of this type contains the record header and the raw record data.
817/// It is mainly used as an intermediary type when turning raw message data
818/// into [`Record`]s.
819///
820/// It allows access to the header only but can be traded for a real record
821/// of a specific type of [`ParseRecordData`] (i.e., some type that knowns
822/// how to parse record data) via the [`to_record`] and [`into_record`]
823/// methods.
824///
825/// [`Record`]: struct.Record.html
826/// [`ParseRecordData`]: trait.ParseRecordData.html
827/// [`to_record`]: #method.to_record
828/// [`into_record`]: #method.into_record
829#[derive(Clone)]
830pub struct ParsedRecord<'a, Octs: Octets + ?Sized> {
831    /// The record’s header.
832    header: RecordHeader<ParsedDname<&'a Octs>>,
833
834    /// A parser positioned at the beginning of the record’s data.
835    data: Parser<'a, Octs>,
836}
837
838impl<'a, Octs: Octets + ?Sized> ParsedRecord<'a, Octs> {
839    /// Creates a new parsed record from a header and the record data.
840    ///
841    /// The record data is provided via a parser that is positioned at the
842    /// first byte of the record data.
843    #[must_use]
844    pub fn new(
845        header: RecordHeader<ParsedDname<&'a Octs>>,
846        data: Parser<'a, Octs>,
847    ) -> Self {
848        ParsedRecord { header, data }
849    }
850
851    /// Returns a reference to the owner of the record.
852    #[must_use]
853    pub fn owner(&self) -> ParsedDname<&'a Octs> {
854        *self.header.owner()
855    }
856
857    /// Returns the record type of the record.
858    #[must_use]
859    pub fn rtype(&self) -> Rtype {
860        self.header.rtype()
861    }
862
863    /// Returns the class of the record.
864    #[must_use]
865    pub fn class(&self) -> Class {
866        self.header.class()
867    }
868
869    /// Returns the TTL of the record.
870    #[must_use]
871    pub fn ttl(&self) -> Ttl {
872        self.header.ttl()
873    }
874
875    /// Returns the data length of the record.
876    #[must_use]
877    pub fn rdlen(&self) -> u16 {
878        self.header.rdlen()
879    }
880}
881
882impl<'a, Octs: Octets + ?Sized> ParsedRecord<'a, Octs> {
883    /// Creates a real resource record from the parsed record.
884    ///
885    /// The method is generic over a type that knows how to parse record
886    /// data via the [`ParseRecordData`] trait. The record data is given to
887    /// this trait for parsing. If the trait feels capable of parsing this
888    /// type of record (as indicated by the record type) and parsing succeeds,
889    /// the method returns `Ok(Some(_))`. It returns `Ok(None)` if the trait
890    /// doesn’t know how to parse this particular record type. It returns
891    /// an error if parsing fails.
892    ///
893    /// [`ParseRecordData`]: ../rdata/trait.ParseRecordData.html
894    #[allow(clippy::type_complexity)]
895    pub fn to_record<Data>(
896        &self,
897    ) -> Result<Option<Record<ParsedDname<Octs::Range<'_>>, Data>>, ParseError>
898    where
899        Data: ParseRecordData<'a, Octs>,
900    {
901        self.header
902            .deref_owner()
903            .parse_into_record(&mut self.data.clone())
904    }
905
906    /// Trades the parsed record for a real resource record.
907    ///
908    /// The method is generic over a type that knows how to parse record
909    /// data via the [`ParseRecordData`] trait. The record data is given to
910    /// this trait for parsing. If the trait feels capable of parsing this
911    /// type of record (as indicated by the record type) and parsing succeeds,
912    /// the method returns `Ok(Some(_))`. It returns `Ok(None)` if the trait
913    /// doesn’t know how to parse this particular record type. It returns
914    /// an error if parsing fails.
915    ///
916    /// [`ParseRecordData`]: ../rdata/trait.ParseRecordData.html
917    #[allow(clippy::type_complexity)]
918    pub fn into_record<Data>(
919        mut self,
920    ) -> Result<Option<Record<ParsedDname<Octs::Range<'a>>, Data>>, ParseError>
921    where
922        Data: ParseRecordData<'a, Octs>,
923    {
924        self.header.deref_owner().parse_into_record(&mut self.data)
925    }
926}
927
928impl<'a, Octs: Octets + ?Sized> ParsedRecord<'a, Octs> {
929    pub fn parse(parser: &mut Parser<'a, Octs>) -> Result<Self, ParseError> {
930        let header = RecordHeader::parse_ref(parser)?;
931        let data = *parser;
932        parser.advance(header.rdlen() as usize)?;
933        Ok(Self::new(header, data))
934    }
935
936    pub fn skip(parser: &mut Parser<'a, Octs>) -> Result<(), ParseError> {
937        let rdlen = RecordHeader::parse_rdlen(parser)?;
938        //let rdlen = RecordHeader::parse(parser)?.rdlen();
939        parser.advance(rdlen as usize)?;
940        Ok(())
941    }
942
943    // No compose because the data may contain compressed domain
944    // names.
945}
946
947//--- PartialEq and Eq
948
949impl<'a, 'o, Octs, Other> PartialEq<ParsedRecord<'o, Other>>
950    for ParsedRecord<'a, Octs>
951where
952    Octs: Octets + ?Sized,
953    Other: Octets + ?Sized,
954{
955    fn eq(&self, other: &ParsedRecord<'o, Other>) -> bool {
956        self.header == other.header
957            && self
958                .data
959                .peek(self.header.rdlen() as usize)
960                .eq(&other.data.peek(other.header.rdlen() as usize))
961    }
962}
963
964impl<'a, Octs: Octets + ?Sized> Eq for ParsedRecord<'a, Octs> {}
965
966//------------ RecordParseError ----------------------------------------------
967
968#[derive(Clone, Copy, Debug, Eq, PartialEq)]
969pub enum RecordParseError<N, D> {
970    Name(N),
971    Data(D),
972    ShortBuf,
973}
974
975impl<N, D> fmt::Display for RecordParseError<N, D>
976where
977    N: fmt::Display,
978    D: fmt::Display,
979{
980    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
981        match *self {
982            RecordParseError::Name(ref name) => name.fmt(f),
983            RecordParseError::Data(ref data) => data.fmt(f),
984            RecordParseError::ShortBuf => {
985                f.write_str("unexpected end of buffer")
986            }
987        }
988    }
989}
990
991#[cfg(feature = "std")]
992impl<N, D> std::error::Error for RecordParseError<N, D>
993where
994    N: std::error::Error,
995    D: std::error::Error,
996{
997}
998
999impl<N, D> From<ShortBuf> for RecordParseError<N, D> {
1000    fn from(_: ShortBuf) -> Self {
1001        RecordParseError::ShortBuf
1002    }
1003}
1004
1005//------------ Ttl ----------------------------------------------
1006
1007const SECS_PER_MINUTE: u32 = 60;
1008const SECS_PER_HOUR: u32 = 3600;
1009const SECS_PER_DAY: u32 = 86400;
1010
1011/// A span of time, typically used to describe the time a given DNS record is valid.
1012///
1013/// `Ttl` implements many common traits, including [`core::ops::Add`], [`core::ops::Sub`], and other [`core::ops`] traits. It implements Default by returning a zero-length `Ttl`.
1014///
1015/// # Why not [`std::time::Duration`]?
1016///
1017/// Two reasons make [`std::time::Duration`] not suited for representing DNS TTL values:
1018/// 1. According to [RFC 2181](https://datatracker.ietf.org/doc/html/rfc2181#section-8) TTL values have second-level precision while [`std::time::Duration`] can represent time down to the nanosecond level.
1019///     This amount of precision is simply not needed and might cause confusion when sending `Duration`s over the network.
1020/// 2. When working with DNS TTL values it's common to want to know a time to live in minutes or hours. [`std::time::Duration`] does not expose easy to use methods for this purpose, while `Ttl` does.
1021///
1022/// `Ttl` provides two methods [`Ttl::from_duration_lossy`] and [`Ttl::into_duration`] to convert between `Duration` and `Ttl`.
1023#[derive(
1024    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default,
1025)]
1026#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1027pub struct Ttl(u32);
1028
1029impl Ttl {
1030    /// A time-to-live of one second.
1031    pub const SECOND: Ttl = Ttl::from_secs(1);
1032
1033    /// A time-to-live of one minute.
1034    pub const MINUTE: Ttl = Ttl::from_mins(1);
1035
1036    /// A time-to-live of one hour.
1037    pub const HOUR: Ttl = Ttl::from_hours(1);
1038
1039    /// A time-to-live of one day.
1040    pub const DAY: Ttl = Ttl::from_days(1);
1041
1042    /// A duration of zero time.
1043    pub const ZERO: Ttl = Ttl::from_secs(0);
1044
1045    /// The maximum theoretical time to live.
1046    pub const MAX: Ttl = Ttl::from_secs(u32::MAX);
1047
1048    /// The practical maximum time to live as recommended by [RFC 8767](https://datatracker.ietf.org/doc/html/rfc8767#section-4).
1049    pub const CAP: Ttl = Ttl::from_secs(604_800);
1050
1051    /// The maximum number of minutes that a `Ttl` can represent.
1052    pub const MAX_MINUTES: u32 = 71582788;
1053
1054    /// The maximum number of hours that a `Ttl` can represent.
1055    pub const MAX_HOURS: u32 = 1193046;
1056
1057    /// The maximum number of days that a `Ttl` can represent.
1058    pub const MAX_DAYS: u16 = 49710;
1059
1060    pub const COMPOSE_LEN: u16 = 4;
1061
1062    /// Returns the total time to live in seconds.
1063    ///
1064    /// # Examples
1065    ///
1066    /// ```
1067    /// use domain::base::Ttl;
1068    ///
1069    /// let ttl = Ttl::from_secs(120);
1070    /// assert_eq!(ttl.as_secs(), 120);
1071    /// ```
1072    #[must_use]
1073    #[inline]
1074    pub const fn as_secs(&self) -> u32 {
1075        self.0
1076    }
1077
1078    /// Returns the total time to live in minutes.
1079    ///
1080    /// # Examples
1081    ///
1082    /// ```
1083    /// use domain::base::Ttl;
1084    ///
1085    /// let ttl = Ttl::from_secs(120);
1086    /// assert_eq!(ttl.as_minutes(), 2);
1087    /// ```
1088    #[must_use]
1089    #[inline]
1090    pub const fn as_minutes(&self) -> u32 {
1091        self.0 / SECS_PER_MINUTE
1092    }
1093
1094    /// Returns the total time to live in hours.
1095    ///
1096    /// # Examples
1097    ///
1098    /// ```
1099    /// use domain::base::Ttl;
1100    ///
1101    /// let ttl = Ttl::from_secs(7200);
1102    /// assert_eq!(ttl.as_hours(), 2);
1103    /// ```
1104    #[must_use]
1105    #[inline]
1106    pub const fn as_hours(&self) -> u32 {
1107        self.0 / SECS_PER_HOUR
1108    }
1109
1110    /// Returns the total time to live in days.
1111    ///
1112    /// # Examples
1113    ///
1114    /// ```
1115    /// use domain::base::Ttl;
1116    ///
1117    /// let ttl = Ttl::from_secs(172800);
1118    /// assert_eq!(ttl.as_days(), 2);
1119    /// ```
1120    #[must_use]
1121    #[inline]
1122    pub const fn as_days(&self) -> u16 {
1123        (self.0 / SECS_PER_DAY) as u16
1124    }
1125
1126    /// Converts a `Ttl` into a [`std::time::Duration`].
1127    ///
1128    /// # Examples
1129    ///
1130    /// ```
1131    /// use domain::base::Ttl;
1132    /// use std::time::Duration;
1133    ///
1134    /// let ttl = Ttl::from_mins(2);
1135    /// let duration = ttl.into_duration();
1136    /// assert_eq!(duration.as_secs(), 120);
1137    /// ```
1138    #[must_use]
1139    #[inline]
1140    pub const fn into_duration(&self) -> Duration {
1141        Duration::from_secs(self.0 as u64)
1142    }
1143
1144    /// Creates a new `Ttl` from the specified number of seconds.
1145    #[must_use]
1146    #[inline]
1147    pub const fn from_secs(secs: u32) -> Self {
1148        Self(secs)
1149    }
1150
1151    /// Creates a new `Ttl` from the specified number of minutes.
1152    ///
1153    /// # Panics
1154    ///
1155    /// The maximum number of days that a `Ttl` can represent is `71582788`.
1156    /// This method will panic if it is being called with a value greater than that.
1157    #[must_use]
1158    #[inline]
1159    pub const fn from_mins(minutes: u32) -> Self {
1160        assert!(minutes <= 71582788);
1161        Self(minutes * SECS_PER_MINUTE)
1162    }
1163
1164    /// Creates a new `Ttl` from the specified number of hours.
1165    ///
1166    /// # Panics
1167    ///
1168    /// The maximum number of hours that a `Ttl` can represent is `1193046`.
1169    /// This method will panic if it is being called with a value greater than that.
1170    #[must_use]
1171    #[inline]
1172    pub const fn from_hours(hours: u32) -> Self {
1173        assert!(hours <= 1193046);
1174        Self(hours * SECS_PER_HOUR)
1175    }
1176
1177    /// Creates a new `Ttl` from the specified number of days.
1178    ///
1179    /// # Panics
1180    ///
1181    /// The maximum number of days that a `Ttl` can represent is `49710`.
1182    /// This method will panic if it is being called with a value greater than that.
1183    #[must_use]
1184    #[inline]
1185    pub const fn from_days(days: u16) -> Self {
1186        assert!(days <= 49710);
1187        Self(days as u32 * SECS_PER_DAY)
1188    }
1189
1190    /// Creates a new `Ttl` from a [`std::time::Duration`].
1191    ///
1192    /// This operation is lossy as [`Duration`] stores seconds as `u64`, while `Ttl` stores seconds as `u32` to comply with the DNS specifications.
1193    /// [`Duration`] also represents time using sub-second precision, which is not kept when converting into a `Ttl`.
1194    ///
1195    /// # Examples
1196    ///
1197    /// ```
1198    /// use domain::base::Ttl;
1199    /// use std::time::Duration;
1200    ///
1201    /// assert_eq!(Ttl::from_duration_lossy(Duration::new(1, 0)), Ttl::from_secs(1));
1202    /// assert_eq!(Ttl::from_duration_lossy(Duration::new(1, 6000)), Ttl::from_secs(1));
1203    /// ```
1204    #[must_use]
1205    #[inline]
1206    pub const fn from_duration_lossy(duration: Duration) -> Self {
1207        Self(duration.as_secs() as u32)
1208    }
1209
1210    /// Returns true if this `Tll` spans no time.
1211    ///
1212    /// This usually indicates a given record should not be cached.
1213    ///
1214    /// # Examples
1215    ///
1216    /// ```
1217    /// use domain::base::Ttl;
1218    ///
1219    /// assert!(Ttl::ZERO.is_zero());
1220    /// assert!(Ttl::from_secs(0).is_zero());
1221    /// assert!(Ttl::from_mins(0).is_zero());
1222    /// assert!(Ttl::from_hours(0).is_zero());
1223    /// assert!(Ttl::from_days(0).is_zero());
1224    /// ```
1225    #[must_use]
1226    #[inline]
1227    pub const fn is_zero(&self) -> bool {
1228        self.0 == 0
1229    }
1230
1231    /// Checked `Ttl` addition. Computes `self + other`, returning [`None`]
1232    /// if overflow occurred.
1233    ///
1234    /// # Examples
1235    ///
1236    /// ```
1237    /// use domain::base::Ttl;
1238    ///
1239    /// assert_eq!(Ttl::from_secs(0).checked_add(Ttl::from_secs(1)), Some(Ttl::from_secs(1)));
1240    /// assert_eq!(Ttl::from_secs(1).checked_add(Ttl::MAX), None);
1241    /// ```
1242    #[must_use = "this returns the result of the operation, \
1243                  without modifying the original"]
1244    #[inline]
1245    pub const fn checked_add(self, rhs: Ttl) -> Option<Ttl> {
1246        if let Some(secs) = self.0.checked_add(rhs.0) {
1247            Some(Ttl(secs))
1248        } else {
1249            None
1250        }
1251    }
1252
1253    /// Saturating `Ttl` addition. Computes `self + other`, returning [`Ttl::MAX`]
1254    /// if overflow occurred.
1255    ///
1256    /// # Examples
1257    ///
1258    /// ```
1259    /// use domain::base::Ttl;
1260    ///
1261    /// assert_eq!(Ttl::from_secs(0).saturating_add(Ttl::from_secs(1)), Ttl::from_secs(1));
1262    /// assert_eq!(Ttl::from_secs(1).saturating_add(Ttl::MAX), Ttl::MAX);
1263    /// ```
1264    #[must_use = "this returns the result of the operation, \
1265    without modifying the original"]
1266    #[inline]
1267    pub const fn saturating_add(self, rhs: Ttl) -> Ttl {
1268        match self.0.checked_add(rhs.0) {
1269            Some(secs) => Ttl(secs),
1270            None => Ttl::MAX,
1271        }
1272    }
1273
1274    /// Checked `Ttl` subtraction. Computes `self - other`, returning [`None`]
1275    /// if the result would be negative or if overflow occurred.
1276    ///
1277    /// # Examples
1278    ///
1279    /// ```
1280    /// use domain::base::Ttl;
1281    ///
1282    /// assert_eq!(Ttl::from_secs(1).checked_sub(Ttl::from_secs(0)), Some(Ttl::from_secs(1)));
1283    /// assert_eq!(Ttl::from_secs(0).checked_sub(Ttl::from_secs(1)), None);
1284    /// ```
1285    #[must_use = "this returns the result of the operation, \
1286                  without modifying the original"]
1287    #[inline]
1288    pub const fn checked_sub(self, rhs: Ttl) -> Option<Ttl> {
1289        if let Some(secs) = self.0.checked_sub(rhs.0) {
1290            Some(Ttl(secs))
1291        } else {
1292            None
1293        }
1294    }
1295
1296    /// Saturating `Ttl` subtraction. Computes `self - other`, returning [`Ttl::ZERO`]
1297    /// if the result would be negative or if overflow occurred.
1298    ///
1299    /// # Examples
1300    ///
1301    /// ```
1302    /// use domain::base::Ttl;
1303    ///
1304    /// assert_eq!(Ttl::from_secs(1).saturating_sub(Ttl::from_secs(0)), Ttl::from_secs(1));
1305    /// assert_eq!(Ttl::from_secs(0).saturating_sub(Ttl::from_secs(1)), Ttl::ZERO);
1306    /// ```
1307    #[must_use = "this returns the result of the operation, \
1308    without modifying the original"]
1309    #[inline]
1310    pub const fn saturating_sub(self, rhs: Ttl) -> Ttl {
1311        match self.0.checked_sub(rhs.0) {
1312            Some(secs) => Ttl(secs),
1313            None => Ttl::ZERO,
1314        }
1315    }
1316
1317    /// Checked `Ttl` multiplication. Computes `self * other`, returning
1318    /// [`None`] if overflow occurred.
1319    ///
1320    /// # Examples
1321    ///
1322    /// ```
1323    /// use domain::base::Ttl;
1324    ///
1325    /// assert_eq!(Ttl::from_secs(5).checked_mul(2), Some(Ttl::from_secs(10)));
1326    /// assert_eq!(Ttl::from_secs(u32::MAX - 1).checked_mul(2), None);
1327    /// ```
1328    #[must_use = "this returns the result of the operation, \
1329                  without modifying the original"]
1330    #[inline]
1331    pub const fn checked_mul(self, rhs: u32) -> Option<Ttl> {
1332        if let Some(secs) = self.0.checked_mul(rhs) {
1333            Some(Ttl(secs))
1334        } else {
1335            None
1336        }
1337    }
1338
1339    /// Saturating `Duration` multiplication. Computes `self * other`, returning
1340    /// [`Duration::MAX`] if overflow occurred.
1341    ///
1342    /// # Examples
1343    ///
1344    /// ```
1345    /// use domain::base::Ttl;
1346    ///
1347    /// assert_eq!(Ttl::from_secs(5).saturating_mul(2), Ttl::from_secs(10));
1348    /// assert_eq!(Ttl::from_secs(u32::MAX - 1).saturating_mul(2), Ttl::MAX);
1349    /// ```
1350    #[must_use = "this returns the result of the operation, \
1351    without modifying the original"]
1352    #[inline]
1353    pub const fn saturating_mul(self, rhs: u32) -> Ttl {
1354        match self.0.checked_mul(rhs) {
1355            Some(secs) => Ttl(secs),
1356            None => Ttl::MAX,
1357        }
1358    }
1359
1360    /// Checked `Duration` division. Computes `self / other`, returning [`None`]
1361    /// if `other == 0`.
1362    ///
1363    /// # Examples
1364    ///
1365    /// ```
1366    /// use domain::base::Ttl;
1367    ///
1368    /// assert_eq!(Ttl::from_secs(10).checked_div(2), Some(Ttl::from_secs(5)));
1369    /// assert_eq!(Ttl::from_mins(1).checked_div(2), Some(Ttl::from_secs(30)));
1370    /// assert_eq!(Ttl::from_secs(2).checked_div(0), None);
1371    /// ```
1372    #[must_use = "this returns the result of the operation, \
1373    without modifying the original"]
1374    #[inline]
1375    pub const fn checked_div(self, rhs: u32) -> Option<Ttl> {
1376        if rhs != 0 {
1377            Some(Ttl(self.0 / rhs))
1378        } else {
1379            None
1380        }
1381    }
1382
1383    /// Caps the value of `Ttl` at 7 days (604800 seconds) as recommended by [RFC 8767](https://datatracker.ietf.org/doc/html/rfc8767#name-standards-action).
1384    ///
1385    /// # Examples
1386    ///
1387    /// ```
1388    /// use domain::base::Ttl;
1389    ///
1390    /// assert_eq!(Ttl::from_mins(5).cap(), Ttl::from_mins(5));
1391    /// assert_eq!(Ttl::from_days(50).cap(), Ttl::from_days(7));
1392    /// ```
1393    #[must_use = "this returns the result of the operation, \
1394    without modifying the original"]
1395    #[inline]
1396    pub const fn cap(self) -> Ttl {
1397        if self.0 > Self::CAP.0 {
1398            Self::CAP
1399        } else {
1400            self
1401        }
1402    }
1403
1404    pub fn compose<Target: OctetsBuilder + ?Sized>(
1405        &self,
1406        target: &mut Target,
1407    ) -> Result<(), Target::AppendError> {
1408        target.append_slice(&(self.as_secs()).to_be_bytes())
1409    }
1410
1411    pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
1412        parser: &mut Parser<'_, Octs>,
1413    ) -> Result<Self, ParseError> {
1414        parser
1415            .parse_u32_be()
1416            .map(Ttl::from_secs)
1417            .map_err(Into::into)
1418    }
1419}
1420
1421impl core::ops::Add for Ttl {
1422    type Output = Ttl;
1423
1424    fn add(self, rhs: Self) -> Self::Output {
1425        self.checked_add(rhs)
1426            .expect("overflow when adding durations")
1427    }
1428}
1429
1430impl core::ops::AddAssign for Ttl {
1431    fn add_assign(&mut self, rhs: Ttl) {
1432        *self = *self + rhs;
1433    }
1434}
1435
1436impl core::ops::Sub for Ttl {
1437    type Output = Ttl;
1438
1439    fn sub(self, rhs: Self) -> Self::Output {
1440        self.checked_sub(rhs)
1441            .expect("overflow when subtracting durations")
1442    }
1443}
1444
1445impl core::ops::SubAssign for Ttl {
1446    fn sub_assign(&mut self, rhs: Ttl) {
1447        *self = *self - rhs;
1448    }
1449}
1450
1451impl core::ops::Mul<u32> for Ttl {
1452    type Output = Ttl;
1453
1454    fn mul(self, rhs: u32) -> Self::Output {
1455        self.checked_mul(rhs)
1456            .expect("overflow when multiplying duration by scalar")
1457    }
1458}
1459
1460impl core::ops::MulAssign<u32> for Ttl {
1461    fn mul_assign(&mut self, rhs: u32) {
1462        *self = *self * rhs;
1463    }
1464}
1465
1466impl core::ops::Div<u32> for Ttl {
1467    type Output = Ttl;
1468
1469    fn div(self, rhs: u32) -> Ttl {
1470        self.checked_div(rhs)
1471            .expect("divide by zero error when dividing duration by scalar")
1472    }
1473}
1474
1475impl core::ops::DivAssign<u32> for Ttl {
1476    fn div_assign(&mut self, rhs: u32) {
1477        *self = *self / rhs;
1478    }
1479}
1480
1481macro_rules! sum_durations {
1482    ($iter:expr) => {{
1483        let mut total_secs: u32 = 0;
1484
1485        for entry in $iter {
1486            total_secs = total_secs
1487                .checked_add(entry.0)
1488                .expect("overflow in iter::sum over durations");
1489        }
1490
1491        Ttl(total_secs)
1492    }};
1493}
1494
1495impl core::iter::Sum for Ttl {
1496    fn sum<I: Iterator<Item = Ttl>>(iter: I) -> Ttl {
1497        sum_durations!(iter)
1498    }
1499}
1500
1501impl<'a> core::iter::Sum<&'a Ttl> for Ttl {
1502    fn sum<I: Iterator<Item = &'a Ttl>>(iter: I) -> Ttl {
1503        sum_durations!(iter)
1504    }
1505}
1506
1507// No From impl because conversion is lossy
1508#[allow(clippy::from_over_into)]
1509impl Into<Duration> for Ttl {
1510    fn into(self) -> Duration {
1511        Duration::from_secs(u64::from(self.0))
1512    }
1513}
1514
1515//============ Testing ======================================================
1516
1517#[cfg(test)]
1518mod test {
1519    #[test]
1520    #[cfg(feature = "bytes")]
1521    fn ds_octets_into() {
1522        use super::*;
1523        use crate::base::iana::{Class, DigestAlg, SecAlg};
1524        use crate::base::name::Dname;
1525        use crate::rdata::Ds;
1526        use bytes::Bytes;
1527        use octseq::octets::OctetsInto;
1528
1529        let ds: Record<Dname<&[u8]>, Ds<&[u8]>> = Record::new(
1530            Dname::from_octets(b"\x01a\x07example\0".as_ref()).unwrap(),
1531            Class::In,
1532            Ttl::from_secs(86400),
1533            Ds::new(
1534                12,
1535                SecAlg::RsaSha256,
1536                DigestAlg::Sha256,
1537                b"something".as_ref(),
1538            )
1539            .unwrap(),
1540        );
1541        let ds_bytes: Record<Dname<Bytes>, Ds<Bytes>> =
1542            ds.clone().octets_into();
1543        assert_eq!(ds.owner(), ds_bytes.owner());
1544        assert_eq!(ds.data().digest(), ds_bytes.data().digest());
1545    }
1546}