1use 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#[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 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 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
164impl<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
186impl<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
203impl<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
243impl<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
254impl<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
308impl<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
317impl<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
330impl<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#[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 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 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
495impl<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
515impl<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
532impl<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
584impl<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
595impl<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
650impl<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
666impl<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
679impl<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
693pub mod parsed {
696 pub use super::{Cdnskey, Cds};
697}
698
699#[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 #[test]
712 #[allow(clippy::redundant_closure)] 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 #[test]
723 #[allow(clippy::redundant_closure)] 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}