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