1use 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#[cfg_attr(feature = "zonefile", doc = "[zonefile][crate::zonefile]")]
77#[cfg_attr(not(feature = "zonefile"), doc = "zonefile")]
78#[derive(Clone)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
86pub struct Record<Name, Data> {
87 owner: Name,
89
90 class: Class,
92
93 ttl: Ttl,
95
96 data: Data,
98}
99
100impl<Name, Data> Record<Name, Data> {
103 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 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 pub fn owner(&self) -> &Name {
135 &self.owner
136 }
137
138 pub fn rtype(&self) -> Rtype
140 where
141 Data: RecordData,
142 {
143 self.data.rtype()
144 }
145
146 pub fn class(&self) -> Class {
148 self.class
149 }
150
151 pub fn set_class(&mut self, class: Class) {
153 self.class = class
154 }
155
156 pub fn ttl(&self) -> Ttl {
158 self.ttl
159 }
160
161 pub fn set_ttl(&mut self, ttl: Ttl) {
163 self.ttl = ttl
164 }
165
166 pub fn data(&self) -> &Data {
168 &self.data
169 }
170
171 pub fn data_mut(&mut self) -> &mut Data {
173 &mut self.data
174 }
175
176 pub fn into_data(self) -> Data {
178 self.data
179 }
180
181 pub fn into_owner_and_data(self) -> (Name, Data) {
183 (self.owner, self.data)
184 }
185}
186
187impl<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
225impl<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
245impl<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
291impl<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
308impl<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 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
374impl<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
389impl<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
424pub 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#[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 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 pub fn owner(&self) -> &Name {
577 &self.owner
578 }
579
580 pub fn rtype(&self) -> Rtype {
582 self.rtype
583 }
584
585 pub fn class(&self) -> Class {
587 self.class
588 }
589
590 pub fn ttl(&self) -> Ttl {
592 self.ttl
593 }
594
595 pub fn rdlen(&self) -> u16 {
597 self.rdlen
598 }
599
600 pub fn into_record<Data>(self, data: Data) -> Record<Name, Data> {
602 Record::new(self.owner, self.class, self.ttl, data)
603 }
604}
605
606impl<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 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 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 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
716impl<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
734impl<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
786impl<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
798impl<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#[derive(Clone)]
830pub struct ParsedRecord<'a, Octs: Octets + ?Sized> {
831 header: RecordHeader<ParsedDname<&'a Octs>>,
833
834 data: Parser<'a, Octs>,
836}
837
838impl<'a, Octs: Octets + ?Sized> ParsedRecord<'a, Octs> {
839 #[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 #[must_use]
853 pub fn owner(&self) -> ParsedDname<&'a Octs> {
854 *self.header.owner()
855 }
856
857 #[must_use]
859 pub fn rtype(&self) -> Rtype {
860 self.header.rtype()
861 }
862
863 #[must_use]
865 pub fn class(&self) -> Class {
866 self.header.class()
867 }
868
869 #[must_use]
871 pub fn ttl(&self) -> Ttl {
872 self.header.ttl()
873 }
874
875 #[must_use]
877 pub fn rdlen(&self) -> u16 {
878 self.header.rdlen()
879 }
880}
881
882impl<'a, Octs: Octets + ?Sized> ParsedRecord<'a, Octs> {
883 #[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 #[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 parser.advance(rdlen as usize)?;
940 Ok(())
941 }
942
943 }
946
947impl<'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#[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
1005const SECS_PER_MINUTE: u32 = 60;
1008const SECS_PER_HOUR: u32 = 3600;
1009const SECS_PER_DAY: u32 = 86400;
1010
1011#[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 pub const SECOND: Ttl = Ttl::from_secs(1);
1032
1033 pub const MINUTE: Ttl = Ttl::from_mins(1);
1035
1036 pub const HOUR: Ttl = Ttl::from_hours(1);
1038
1039 pub const DAY: Ttl = Ttl::from_days(1);
1041
1042 pub const ZERO: Ttl = Ttl::from_secs(0);
1044
1045 pub const MAX: Ttl = Ttl::from_secs(u32::MAX);
1047
1048 pub const CAP: Ttl = Ttl::from_secs(604_800);
1050
1051 pub const MAX_MINUTES: u32 = 71582788;
1053
1054 pub const MAX_HOURS: u32 = 1193046;
1056
1057 pub const MAX_DAYS: u16 = 49710;
1059
1060 pub const COMPOSE_LEN: u16 = 4;
1061
1062 #[must_use]
1073 #[inline]
1074 pub const fn as_secs(&self) -> u32 {
1075 self.0
1076 }
1077
1078 #[must_use]
1089 #[inline]
1090 pub const fn as_minutes(&self) -> u32 {
1091 self.0 / SECS_PER_MINUTE
1092 }
1093
1094 #[must_use]
1105 #[inline]
1106 pub const fn as_hours(&self) -> u32 {
1107 self.0 / SECS_PER_HOUR
1108 }
1109
1110 #[must_use]
1121 #[inline]
1122 pub const fn as_days(&self) -> u16 {
1123 (self.0 / SECS_PER_DAY) as u16
1124 }
1125
1126 #[must_use]
1139 #[inline]
1140 pub const fn into_duration(&self) -> Duration {
1141 Duration::from_secs(self.0 as u64)
1142 }
1143
1144 #[must_use]
1146 #[inline]
1147 pub const fn from_secs(secs: u32) -> Self {
1148 Self(secs)
1149 }
1150
1151 #[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 #[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 #[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 #[must_use]
1205 #[inline]
1206 pub const fn from_duration_lossy(duration: Duration) -> Self {
1207 Self(duration.as_secs() as u32)
1208 }
1209
1210 #[must_use]
1226 #[inline]
1227 pub const fn is_zero(&self) -> bool {
1228 self.0 == 0
1229 }
1230
1231 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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#[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#[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}