domain/rdata/
cds.rs

1//! Record data from [RFC 7344]: CDS and CDNSKEY records.
2//!
3//! [RFC 7344]: https://tools.ietf.org/html/rfc7344
4use crate::base::cmp::CanonicalOrd;
5use crate::base::iana::{DigestAlgorithm, Rtype, SecurityAlgorithm};
6use crate::base::rdata::{
7    ComposeRecordData, LongRecordData, ParseRecordData, RecordData,
8};
9use crate::base::scan::{Scan, Scanner, ScannerError};
10use crate::base::wire::{Compose, Composer, Parse, ParseError};
11use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt};
12use crate::utils::{base16, base64};
13use core::cmp::Ordering;
14use core::{fmt, hash};
15use octseq::octets::{Octets, OctetsFrom, OctetsInto};
16use octseq::parse::Parser;
17
18//------------ Cdnskey --------------------------------------------------------
19
20#[derive(Clone)]
21#[cfg_attr(
22    feature = "serde",
23    derive(serde::Serialize, serde::Deserialize),
24    serde(bound(
25        serialize = "
26            Octs: octseq::serde::SerializeOctets + AsRef<[u8]>
27        ",
28        deserialize = "
29            Octs:
30                octseq::builder::FromBuilder
31                + octseq::serde::DeserializeOctets<'de>,
32            <Octs as octseq::builder::FromBuilder>::Builder:
33                octseq::builder::OctetsBuilder 
34                + octseq::builder::EmptyBuilder,
35        ",
36    ))
37)]
38pub struct Cdnskey<Octs> {
39    flags: u16,
40    protocol: u8,
41    algorithm: SecurityAlgorithm,
42    #[cfg_attr(
43        feature = "serde",
44        serde(with = "crate::utils::base64::serde")
45    )]
46    public_key: Octs,
47}
48
49impl Cdnskey<()> {
50    /// The rtype of this record data type.
51    pub(crate) const RTYPE: Rtype = Rtype::CDNSKEY;
52}
53
54impl<Octs> Cdnskey<Octs> {
55    pub fn new(
56        flags: u16,
57        protocol: u8,
58        algorithm: SecurityAlgorithm,
59        public_key: Octs,
60    ) -> Result<Self, LongRecordData>
61    where
62        Octs: AsRef<[u8]>,
63    {
64        LongRecordData::check_len(
65            usize::from(
66                u16::COMPOSE_LEN
67                    + u8::COMPOSE_LEN
68                    + SecurityAlgorithm::COMPOSE_LEN,
69            )
70            .checked_add(public_key.as_ref().len())
71            .expect("long key"),
72        )?;
73        Ok(unsafe {
74            Cdnskey::new_unchecked(flags, protocol, algorithm, public_key)
75        })
76    }
77
78    /// Creates new CDNSKEY record data without checking.
79    ///
80    /// # Safety
81    ///
82    /// The caller needs to ensure that wire format representation of the
83    /// record data is at most 65,535 octets long.
84    pub unsafe fn new_unchecked(
85        flags: u16,
86        protocol: u8,
87        algorithm: SecurityAlgorithm,
88        public_key: Octs,
89    ) -> Self {
90        Cdnskey {
91            flags,
92            protocol,
93            algorithm,
94            public_key,
95        }
96    }
97
98    pub fn flags(&self) -> u16 {
99        self.flags
100    }
101
102    pub fn protocol(&self) -> u8 {
103        self.protocol
104    }
105
106    pub fn algorithm(&self) -> SecurityAlgorithm {
107        self.algorithm
108    }
109
110    pub fn public_key(&self) -> &Octs {
111        &self.public_key
112    }
113
114    pub(super) fn convert_octets<Target: OctetsFrom<Octs>>(
115        self,
116    ) -> Result<Cdnskey<Target>, Target::Error> {
117        Ok(unsafe {
118            Cdnskey::new_unchecked(
119                self.flags,
120                self.protocol,
121                self.algorithm,
122                self.public_key.try_octets_into()?,
123            )
124        })
125    }
126
127    pub(super) fn flatten<Target: OctetsFrom<Octs>>(
128        self,
129    ) -> Result<Cdnskey<Target>, Target::Error> {
130        self.convert_octets()
131    }
132
133    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
134        parser: &mut Parser<'a, Src>,
135    ) -> Result<Self, ParseError> {
136        let len = match parser.remaining().checked_sub(4) {
137            Some(len) => len,
138            None => return Err(ParseError::ShortInput),
139        };
140        Ok(unsafe {
141            Self::new_unchecked(
142                u16::parse(parser)?,
143                u8::parse(parser)?,
144                SecurityAlgorithm::parse(parser)?,
145                parser.parse_octets(len)?,
146            )
147        })
148    }
149
150    pub fn scan<S: Scanner<Octets = Octs>>(
151        scanner: &mut S,
152    ) -> Result<Self, S::Error>
153    where
154        Octs: AsRef<[u8]>,
155    {
156        Self::new(
157            u16::scan(scanner)?,
158            u8::scan(scanner)?,
159            SecurityAlgorithm::scan(scanner)?,
160            scanner.convert_entry(base64::SymbolConverter::new())?,
161        )
162        .map_err(|err| S::Error::custom(err.as_str()))
163    }
164}
165
166//--- OctetsFrom
167
168impl<Octs, SrcOcts> OctetsFrom<Cdnskey<SrcOcts>> for Cdnskey<Octs>
169where
170    Octs: OctetsFrom<SrcOcts>,
171{
172    type Error = Octs::Error;
173
174    fn try_octets_from(
175        source: Cdnskey<SrcOcts>,
176    ) -> Result<Self, Self::Error> {
177        Ok(unsafe {
178            Cdnskey::new_unchecked(
179                source.flags,
180                source.protocol,
181                source.algorithm,
182                Octs::try_octets_from(source.public_key)?,
183            )
184        })
185    }
186}
187
188//--- PartialEq and Eq
189
190impl<Octs, Other> PartialEq<Cdnskey<Other>> for Cdnskey<Octs>
191where
192    Octs: AsRef<[u8]>,
193    Other: AsRef<[u8]>,
194{
195    fn eq(&self, other: &Cdnskey<Other>) -> bool {
196        self.flags == other.flags
197            && self.protocol == other.protocol
198            && self.algorithm == other.algorithm
199            && self.public_key.as_ref() == other.public_key.as_ref()
200    }
201}
202
203impl<Octs: AsRef<[u8]>> Eq for Cdnskey<Octs> {}
204
205//--- PartialOrd, CanonicalOrd, and Ord
206
207impl<Octs, Other> PartialOrd<Cdnskey<Other>> for Cdnskey<Octs>
208where
209    Octs: AsRef<[u8]>,
210    Other: AsRef<[u8]>,
211{
212    fn partial_cmp(&self, other: &Cdnskey<Other>) -> Option<Ordering> {
213        Some(self.canonical_cmp(other))
214    }
215}
216
217impl<Octs, Other> CanonicalOrd<Cdnskey<Other>> for Cdnskey<Octs>
218where
219    Octs: AsRef<[u8]>,
220    Other: AsRef<[u8]>,
221{
222    fn canonical_cmp(&self, other: &Cdnskey<Other>) -> Ordering {
223        match self.flags.cmp(&other.flags) {
224            Ordering::Equal => {}
225            other => return other,
226        }
227        match self.protocol.cmp(&other.protocol) {
228            Ordering::Equal => {}
229            other => return other,
230        }
231        match self.algorithm.cmp(&other.algorithm) {
232            Ordering::Equal => {}
233            other => return other,
234        }
235        self.public_key.as_ref().cmp(other.public_key.as_ref())
236    }
237}
238
239impl<Octs: AsRef<[u8]>> Ord for Cdnskey<Octs> {
240    fn cmp(&self, other: &Self) -> Ordering {
241        self.canonical_cmp(other)
242    }
243}
244
245//--- Hash
246
247impl<Octs: AsRef<[u8]>> hash::Hash for Cdnskey<Octs> {
248    fn hash<H: hash::Hasher>(&self, state: &mut H) {
249        self.flags.hash(state);
250        self.protocol.hash(state);
251        self.algorithm.hash(state);
252        self.public_key.as_ref().hash(state);
253    }
254}
255
256//--- RecordData, ParseRecordData, ComposeRecordData
257
258impl<Octs> RecordData for Cdnskey<Octs> {
259    fn rtype(&self) -> Rtype {
260        Cdnskey::RTYPE
261    }
262}
263
264impl<'a, Octs> ParseRecordData<'a, Octs> for Cdnskey<Octs::Range<'a>>
265where
266    Octs: Octets + ?Sized,
267{
268    fn parse_rdata(
269        rtype: Rtype,
270        parser: &mut Parser<'a, Octs>,
271    ) -> Result<Option<Self>, ParseError> {
272        if rtype == Cdnskey::RTYPE {
273            Self::parse(parser).map(Some)
274        } else {
275            Ok(None)
276        }
277    }
278}
279
280impl<Octs: AsRef<[u8]>> ComposeRecordData for Cdnskey<Octs> {
281    fn rdlen(&self, _compress: bool) -> Option<u16> {
282        Some(
283            u16::try_from(self.public_key.as_ref().len())
284                .expect("long key")
285                .checked_add(
286                    u16::COMPOSE_LEN
287                        + u8::COMPOSE_LEN
288                        + SecurityAlgorithm::COMPOSE_LEN,
289                )
290                .expect("long key"),
291        )
292    }
293
294    fn compose_rdata<Target: Composer + ?Sized>(
295        &self,
296        target: &mut Target,
297    ) -> Result<(), Target::AppendError> {
298        self.flags.compose(target)?;
299        self.protocol.compose(target)?;
300        self.algorithm.compose(target)?;
301        target.append_slice(self.public_key.as_ref())
302    }
303
304    fn compose_canonical_rdata<Target: Composer + ?Sized>(
305        &self,
306        target: &mut Target,
307    ) -> Result<(), Target::AppendError> {
308        self.compose_rdata(target)
309    }
310}
311
312//--- Display
313
314impl<Octs: AsRef<[u8]>> fmt::Display for Cdnskey<Octs> {
315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316        write!(f, "{} {} {} ", self.flags, self.protocol, self.algorithm)?;
317        base64::display(&self.public_key, f)
318    }
319}
320
321//--- Debug
322
323impl<Octs: AsRef<[u8]>> fmt::Debug for Cdnskey<Octs> {
324    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325        f.debug_struct("Cdnskey")
326            .field("flags", &self.flags)
327            .field("protocol", &self.protocol)
328            .field("algorithm", &self.algorithm)
329            .field("public_key", &self.public_key.as_ref())
330            .finish()
331    }
332}
333
334//--- ZonefileFmt
335
336impl<Octs: AsRef<[u8]>> ZonefileFmt for Cdnskey<Octs> {
337    fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
338        p.block(|p| {
339            p.write_token(self.flags)?;
340            p.write_token(self.protocol)?;
341            p.write_show(self.algorithm)?;
342            p.write_token(base64::encode_display(&self.public_key))
343        })
344    }
345}
346
347//------------ Cds -----------------------------------------------------------
348
349#[derive(Clone)]
350#[cfg_attr(
351    feature = "serde",
352    derive(serde::Serialize, serde::Deserialize),
353    serde(bound(
354        serialize = "
355            Octs: octseq::serde::SerializeOctets + AsRef<[u8]>
356        ",
357        deserialize = "
358            Octs:
359                octseq::builder::FromBuilder
360                + octseq::serde::DeserializeOctets<'de>,
361            <Octs as octseq::builder::FromBuilder>::Builder:
362                octseq::builder::OctetsBuilder
363                + octseq::builder::EmptyBuilder,
364        ",
365    ))
366)]
367pub struct Cds<Octs> {
368    key_tag: u16,
369    algorithm: SecurityAlgorithm,
370    digest_type: DigestAlgorithm,
371    #[cfg_attr(
372        feature = "serde",
373        serde(with = "crate::utils::base64::serde")
374    )]
375    digest: Octs,
376}
377
378impl Cds<()> {
379    /// The rtype of this record data type.
380    pub(crate) const RTYPE: Rtype = Rtype::CDS;
381}
382
383impl<Octs> Cds<Octs> {
384    pub fn new(
385        key_tag: u16,
386        algorithm: SecurityAlgorithm,
387        digest_type: DigestAlgorithm,
388        digest: Octs,
389    ) -> Result<Self, LongRecordData>
390    where
391        Octs: AsRef<[u8]>,
392    {
393        LongRecordData::check_len(
394            usize::from(
395                u16::COMPOSE_LEN
396                    + SecurityAlgorithm::COMPOSE_LEN
397                    + DigestAlgorithm::COMPOSE_LEN,
398            )
399            .checked_add(digest.as_ref().len())
400            .expect("long digest"),
401        )?;
402        Ok(unsafe {
403            Cds::new_unchecked(key_tag, algorithm, digest_type, digest)
404        })
405    }
406
407    /// Creates new CDS record data without checking.
408    ///
409    /// # Safety
410    ///
411    /// The caller needs to ensure that wire format representation of the
412    /// record data is at most 65,535 octets long.
413    pub unsafe fn new_unchecked(
414        key_tag: u16,
415        algorithm: SecurityAlgorithm,
416        digest_type: DigestAlgorithm,
417        digest: Octs,
418    ) -> Self {
419        Cds {
420            key_tag,
421            algorithm,
422            digest_type,
423            digest,
424        }
425    }
426
427    pub fn key_tag(&self) -> u16 {
428        self.key_tag
429    }
430
431    pub fn algorithm(&self) -> SecurityAlgorithm {
432        self.algorithm
433    }
434
435    pub fn digest_type(&self) -> DigestAlgorithm {
436        self.digest_type
437    }
438
439    pub fn digest(&self) -> &Octs {
440        &self.digest
441    }
442
443    pub fn into_digest(self) -> Octs {
444        self.digest
445    }
446
447    pub(super) fn convert_octets<Target: OctetsFrom<Octs>>(
448        self,
449    ) -> Result<Cds<Target>, Target::Error> {
450        Ok(unsafe {
451            Cds::new_unchecked(
452                self.key_tag,
453                self.algorithm,
454                self.digest_type,
455                self.digest.try_octets_into()?,
456            )
457        })
458    }
459
460    pub(super) fn flatten<Target: OctetsFrom<Octs>>(
461        self,
462    ) -> Result<Cds<Target>, Target::Error> {
463        self.convert_octets()
464    }
465
466    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
467        parser: &mut Parser<'a, Src>,
468    ) -> Result<Self, ParseError> {
469        let len = match parser.remaining().checked_sub(4) {
470            Some(len) => len,
471            None => return Err(ParseError::ShortInput),
472        };
473        Ok(unsafe {
474            Self::new_unchecked(
475                u16::parse(parser)?,
476                SecurityAlgorithm::parse(parser)?,
477                DigestAlgorithm::parse(parser)?,
478                parser.parse_octets(len)?,
479            )
480        })
481    }
482
483    pub fn scan<S: Scanner<Octets = Octs>>(
484        scanner: &mut S,
485    ) -> Result<Self, S::Error>
486    where
487        Octs: AsRef<[u8]>,
488    {
489        Self::new(
490            u16::scan(scanner)?,
491            SecurityAlgorithm::scan(scanner)?,
492            DigestAlgorithm::scan(scanner)?,
493            scanner.convert_entry(base16::SymbolConverter::new())?,
494        )
495        .map_err(|err| S::Error::custom(err.as_str()))
496    }
497}
498
499//--- OctetsFrom
500
501impl<Octs, SrcOcts> OctetsFrom<Cds<SrcOcts>> for Cds<Octs>
502where
503    Octs: OctetsFrom<SrcOcts>,
504{
505    type Error = Octs::Error;
506
507    fn try_octets_from(source: Cds<SrcOcts>) -> Result<Self, Self::Error> {
508        Ok(unsafe {
509            Cds::new_unchecked(
510                source.key_tag,
511                source.algorithm,
512                source.digest_type,
513                Octs::try_octets_from(source.digest)?,
514            )
515        })
516    }
517}
518
519//--- PartialEq and Eq
520
521impl<Octs, Other> PartialEq<Cds<Other>> for Cds<Octs>
522where
523    Octs: AsRef<[u8]>,
524    Other: AsRef<[u8]>,
525{
526    fn eq(&self, other: &Cds<Other>) -> bool {
527        self.key_tag == other.key_tag
528            && self.algorithm == other.algorithm
529            && self.digest_type == other.digest_type
530            && self.digest.as_ref().eq(other.digest.as_ref())
531    }
532}
533
534impl<Octs: AsRef<[u8]>> Eq for Cds<Octs> {}
535
536//--- PartialOrd, CanonicalOrd, and Ord
537
538impl<Octs, Other> PartialOrd<Cds<Other>> for Cds<Octs>
539where
540    Octs: AsRef<[u8]>,
541    Other: AsRef<[u8]>,
542{
543    fn partial_cmp(&self, other: &Cds<Other>) -> Option<Ordering> {
544        match self.key_tag.partial_cmp(&other.key_tag) {
545            Some(Ordering::Equal) => {}
546            other => return other,
547        }
548        match self.algorithm.partial_cmp(&other.algorithm) {
549            Some(Ordering::Equal) => {}
550            other => return other,
551        }
552        match self.digest_type.partial_cmp(&other.digest_type) {
553            Some(Ordering::Equal) => {}
554            other => return other,
555        }
556        self.digest.as_ref().partial_cmp(other.digest.as_ref())
557    }
558}
559
560impl<Octs, Other> CanonicalOrd<Cds<Other>> for Cds<Octs>
561where
562    Octs: AsRef<[u8]>,
563    Other: AsRef<[u8]>,
564{
565    fn canonical_cmp(&self, other: &Cds<Other>) -> Ordering {
566        match self.key_tag.cmp(&other.key_tag) {
567            Ordering::Equal => {}
568            other => return other,
569        }
570        match self.algorithm.cmp(&other.algorithm) {
571            Ordering::Equal => {}
572            other => return other,
573        }
574        match self.digest_type.cmp(&other.digest_type) {
575            Ordering::Equal => {}
576            other => return other,
577        }
578        self.digest.as_ref().cmp(other.digest.as_ref())
579    }
580}
581
582impl<Octs: AsRef<[u8]>> Ord for Cds<Octs> {
583    fn cmp(&self, other: &Self) -> Ordering {
584        self.canonical_cmp(other)
585    }
586}
587
588//--- Hash
589
590impl<Octs: AsRef<[u8]>> hash::Hash for Cds<Octs> {
591    fn hash<H: hash::Hasher>(&self, state: &mut H) {
592        self.key_tag.hash(state);
593        self.algorithm.hash(state);
594        self.digest_type.hash(state);
595        self.digest.as_ref().hash(state);
596    }
597}
598
599//--- RecordData, ParseRecordData, ComposeRecordData
600
601impl<Octs> RecordData for Cds<Octs> {
602    fn rtype(&self) -> Rtype {
603        Cds::RTYPE
604    }
605}
606
607impl<'a, Octs> ParseRecordData<'a, Octs> for Cds<Octs::Range<'a>>
608where
609    Octs: Octets + ?Sized,
610{
611    fn parse_rdata(
612        rtype: Rtype,
613        parser: &mut Parser<'a, Octs>,
614    ) -> Result<Option<Self>, ParseError> {
615        if rtype == Cds::RTYPE {
616            Self::parse(parser).map(Some)
617        } else {
618            Ok(None)
619        }
620    }
621}
622
623impl<Octs: AsRef<[u8]>> ComposeRecordData for Cds<Octs> {
624    fn rdlen(&self, _compress: bool) -> Option<u16> {
625        Some(
626            u16::checked_add(
627                u16::COMPOSE_LEN
628                    + SecurityAlgorithm::COMPOSE_LEN
629                    + DigestAlgorithm::COMPOSE_LEN,
630                self.digest.as_ref().len().try_into().expect("long digest"),
631            )
632            .expect("long digest"),
633        )
634    }
635
636    fn compose_rdata<Target: Composer + ?Sized>(
637        &self,
638        target: &mut Target,
639    ) -> Result<(), Target::AppendError> {
640        self.key_tag.compose(target)?;
641        self.algorithm.compose(target)?;
642        self.digest_type.compose(target)?;
643        target.append_slice(self.digest.as_ref())
644    }
645
646    fn compose_canonical_rdata<Target: Composer + ?Sized>(
647        &self,
648        target: &mut Target,
649    ) -> Result<(), Target::AppendError> {
650        self.compose_rdata(target)
651    }
652}
653
654//--- Display
655
656impl<Octs: AsRef<[u8]>> fmt::Display for Cds<Octs> {
657    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
658        write!(
659            f,
660            "{} {} {} ",
661            self.key_tag, self.algorithm, self.digest_type
662        )?;
663        for ch in self.digest.as_ref() {
664            write!(f, "{:02x}", ch)?
665        }
666        Ok(())
667    }
668}
669
670//--- Debug
671
672impl<Octs: AsRef<[u8]>> fmt::Debug for Cds<Octs> {
673    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
674        f.debug_struct("Cds")
675            .field("key_tag", &self.key_tag)
676            .field("algorithm", &self.algorithm)
677            .field("digest_type", &self.digest_type)
678            .field("digest", &self.digest.as_ref())
679            .finish()
680    }
681}
682
683//--- ZonefileFmt
684
685impl<Octs: AsRef<[u8]>> ZonefileFmt for Cds<Octs> {
686    fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
687        p.block(|p| {
688            p.write_token(self.key_tag)?;
689            p.write_comment("key tag")?;
690            p.write_show(self.algorithm)?;
691            p.write_show(self.digest_type)?;
692            p.write_token(base16::encode_display(&self.digest))
693        })
694    }
695}
696
697//------------ parsed --------------------------------------------------------
698
699pub mod parsed {
700    pub use super::{Cdnskey, Cds};
701}
702
703//============ Test ==========================================================
704
705#[cfg(test)]
706#[cfg(all(feature = "std", feature = "bytes"))]
707mod test {
708    use super::*;
709    use crate::base::rdata::test::{
710        test_compose_parse, test_rdlen, test_scan,
711    };
712
713    //--- Cdnskey
714
715    #[test]
716    #[allow(clippy::redundant_closure)] // lifetimes ...
717    fn cdnskey_compose_parse_scan() {
718        let rdata =
719            Cdnskey::new(10, 11, SecurityAlgorithm::RSASHA1, b"key").unwrap();
720        test_rdlen(&rdata);
721        test_compose_parse(&rdata, |parser| Cdnskey::parse(parser));
722        test_scan(&["10", "11", "5", "a2V5"], Cdnskey::scan, &rdata);
723    }
724
725    //--- Cds
726
727    #[test]
728    #[allow(clippy::redundant_closure)] // lifetimes ...
729    fn cds_compose_parse_scan() {
730        let rdata = Cds::new(
731            10,
732            SecurityAlgorithm::RSASHA1,
733            DigestAlgorithm::SHA256,
734            b"key",
735        )
736        .unwrap();
737        test_rdlen(&rdata);
738        test_compose_parse(&rdata, |parser| Cds::parse(parser));
739        test_scan(&["10", "5", "2", "6b6579"], Cds::scan, &rdata);
740    }
741}