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