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
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 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
166impl<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
188impl<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
205impl<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
245impl<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
256impl<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
312impl<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
321impl<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
334impl<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#[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 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 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
499impl<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
519impl<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
536impl<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
588impl<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
599impl<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
654impl<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
670impl<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
683impl<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
697pub mod parsed {
700 pub use super::{Cdnskey, Cds};
701}
702
703#[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 #[test]
716 #[allow(clippy::redundant_closure)] 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 #[test]
728 #[allow(clippy::redundant_closure)] 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}