1use 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#[cfg_attr(feature = "zonefile", doc = "[zonefile][crate::zonefile]")]
80#[cfg_attr(not(feature = "zonefile"), doc = "zonefile")]
81#[derive(Clone)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89pub struct Record<Name, Data> {
90 owner: Name,
92
93 class: Class,
95
96 ttl: Ttl,
98
99 data: Data,
101}
102
103impl<Name, Data> Record<Name, Data> {
106 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 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 pub fn owner(&self) -> &Name {
138 &self.owner
139 }
140
141 pub fn rtype(&self) -> Rtype
143 where
144 Data: RecordData,
145 {
146 self.data.rtype()
147 }
148
149 pub fn class(&self) -> Class {
151 self.class
152 }
153
154 pub fn set_class(&mut self, class: Class) {
156 self.class = class
157 }
158
159 pub fn ttl(&self) -> Ttl {
161 self.ttl
162 }
163
164 pub fn set_ttl(&mut self, ttl: Ttl) {
166 self.ttl = ttl
167 }
168
169 pub fn data(&self) -> &Data {
171 &self.data
172 }
173
174 pub fn data_mut(&mut self) -> &mut Data {
176 &mut self.data
177 }
178
179 pub fn into_data(self) -> Data {
181 self.data
182 }
183
184 pub fn into_owner_and_data(self) -> (Name, Data) {
186 (self.owner, self.data)
187 }
188}
189
190impl<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
228impl<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
248impl<N, D> AsRef<Record<N, D>> for Record<N, D> {
251 fn as_ref(&self) -> &Record<N, D> {
252 self
253 }
254}
255
256impl<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
302impl<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
319impl<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 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
385impl<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
400impl<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
435impl<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
451pub 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#[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 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 pub fn owner(&self) -> &Name {
604 &self.owner
605 }
606
607 pub fn rtype(&self) -> Rtype {
609 self.rtype
610 }
611
612 pub fn class(&self) -> Class {
614 self.class
615 }
616
617 pub fn ttl(&self) -> Ttl {
619 self.ttl
620 }
621
622 pub fn rdlen(&self) -> u16 {
624 self.rdlen
625 }
626
627 pub fn into_record<Data>(self, data: Data) -> Record<Name, Data> {
629 Record::new(self.owner, self.class, self.ttl, data)
630 }
631}
632
633impl<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 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 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 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 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
770impl<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
788impl<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
840impl<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
852impl<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#[derive(Clone)]
882pub struct ParsedRecord<'a, Octs: Octets + ?Sized> {
883 header: RecordHeader<ParsedName<&'a Octs>>,
885
886 data: Parser<'a, Octs>,
888}
889
890impl<'a, Octs: Octets + ?Sized> ParsedRecord<'a, Octs> {
891 #[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 #[must_use]
905 pub fn owner(&self) -> ParsedName<&'a Octs> {
906 *self.header.owner()
907 }
908
909 #[must_use]
911 pub fn rtype(&self) -> Rtype {
912 self.header.rtype()
913 }
914
915 #[must_use]
917 pub fn class(&self) -> Class {
918 self.header.class()
919 }
920
921 #[must_use]
923 pub fn ttl(&self) -> Ttl {
924 self.header.ttl()
925 }
926
927 #[must_use]
929 pub fn rdlen(&self) -> u16 {
930 self.header.rdlen()
931 }
932}
933
934impl<'a, Octs: Octets + ?Sized> ParsedRecord<'a, Octs> {
935 #[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 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 #[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 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 parser.advance(rdlen as usize)?;
1020 Ok(())
1021 }
1022
1023 }
1026
1027impl<'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#[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
1085const SECS_PER_MINUTE: u32 = 60;
1088const SECS_PER_HOUR: u32 = 3600;
1089const SECS_PER_DAY: u32 = 86400;
1090
1091#[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 pub const SECOND: Ttl = Ttl::from_secs(1);
1112
1113 pub const MINUTE: Ttl = Ttl::from_mins(1);
1115
1116 pub const HOUR: Ttl = Ttl::from_hours(1);
1118
1119 pub const DAY: Ttl = Ttl::from_days(1);
1121
1122 pub const ZERO: Ttl = Ttl::from_secs(0);
1124
1125 pub const MAX: Ttl = Ttl::from_secs(u32::MAX);
1127
1128 pub const CAP: Ttl = Ttl::from_secs(604_800);
1130
1131 pub const MAX_MINUTES: u32 = 71582788;
1133
1134 pub const MAX_HOURS: u32 = 1193046;
1136
1137 pub const MAX_DAYS: u16 = 49710;
1139
1140 pub const COMPOSE_LEN: u16 = 4;
1141
1142 #[must_use]
1153 #[inline]
1154 pub const fn as_secs(&self) -> u32 {
1155 self.0
1156 }
1157
1158 #[must_use]
1169 #[inline]
1170 pub const fn as_minutes(&self) -> u32 {
1171 self.0 / SECS_PER_MINUTE
1172 }
1173
1174 #[must_use]
1185 #[inline]
1186 pub const fn as_hours(&self) -> u32 {
1187 self.0 / SECS_PER_HOUR
1188 }
1189
1190 #[must_use]
1201 #[inline]
1202 pub const fn as_days(&self) -> u16 {
1203 (self.0 / SECS_PER_DAY) as u16
1204 }
1205
1206 #[must_use]
1219 #[inline]
1220 pub const fn into_duration(&self) -> Duration {
1221 Duration::from_secs(self.0 as u64)
1222 }
1223
1224 #[must_use]
1226 #[inline]
1227 pub const fn from_secs(secs: u32) -> Self {
1228 Self(secs)
1229 }
1230
1231 #[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 #[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 #[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 #[must_use]
1285 #[inline]
1286 pub const fn from_duration_lossy(duration: Duration) -> Self {
1287 Self(duration.as_secs() as u32)
1288 }
1289
1290 #[must_use]
1306 #[inline]
1307 pub const fn is_zero(&self) -> bool {
1308 self.0 == 0
1309 }
1310
1311 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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#[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}