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