domain/rdata/
nsec3.rs

1//! Record data from [RFC 5155]: NSEC3 and NSEC3PARAM records.
2//!
3//! This RFC defines the NSEC3 and NSEC3PARAM resource records.
4//!
5//! [RFC 5155]: https://tools.ietf.org/html/rfc5155
6
7use super::dnssec::RtypeBitmap;
8use crate::base::cmp::CanonicalOrd;
9use crate::base::iana::{Nsec3HashAlg, Rtype};
10use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData};
11use crate::base::scan::{
12    ConvertSymbols, EntrySymbol, Scan, Scanner, ScannerError,
13};
14use crate::base::wire::{Compose, Composer, Parse, ParseError};
15use crate::utils::{base16, base32};
16#[cfg(feature = "bytes")]
17use bytes::Bytes;
18use core::cmp::Ordering;
19use core::{fmt, hash, str};
20use octseq::builder::{
21    EmptyBuilder, FreezeBuilder, FromBuilder, OctetsBuilder,
22};
23use octseq::octets::{Octets, OctetsFrom, OctetsInto};
24use octseq::parse::Parser;
25#[cfg(feature = "serde")]
26use octseq::serde::{DeserializeOctets, SerializeOctets};
27
28//------------ Nsec3 ---------------------------------------------------------
29
30#[derive(Clone)]
31#[cfg_attr(
32    feature = "serde",
33    derive(serde::Serialize, serde::Deserialize),
34    serde(bound(
35        serialize = "
36            Octs: octseq::serde::SerializeOctets + AsRef<[u8]>,
37        ",
38        deserialize = "
39            Octs: FromBuilder + octseq::serde::DeserializeOctets<'de>,
40            <Octs as FromBuilder>::Builder:
41                EmptyBuilder + octseq::builder::Truncate
42                + AsRef<[u8]> + AsMut<[u8]>,
43        ",
44    ))
45)]
46pub struct Nsec3<Octs> {
47    hash_algorithm: Nsec3HashAlg,
48    flags: u8,
49    iterations: u16,
50    salt: Nsec3Salt<Octs>,
51    next_owner: OwnerHash<Octs>,
52    types: RtypeBitmap<Octs>,
53}
54
55impl<Octs> Nsec3<Octs> {
56    pub fn new(
57        hash_algorithm: Nsec3HashAlg,
58        flags: u8,
59        iterations: u16,
60        salt: Nsec3Salt<Octs>,
61        next_owner: OwnerHash<Octs>,
62        types: RtypeBitmap<Octs>,
63    ) -> Self {
64        Nsec3 {
65            hash_algorithm,
66            flags,
67            iterations,
68            salt,
69            next_owner,
70            types,
71        }
72    }
73
74    pub fn hash_algorithm(&self) -> Nsec3HashAlg {
75        self.hash_algorithm
76    }
77
78    pub fn flags(&self) -> u8 {
79        self.flags
80    }
81
82    pub fn opt_out(&self) -> bool {
83        self.flags & 0x01 != 0
84    }
85
86    pub fn iterations(&self) -> u16 {
87        self.iterations
88    }
89
90    pub fn salt(&self) -> &Nsec3Salt<Octs> {
91        &self.salt
92    }
93
94    pub fn next_owner(&self) -> &OwnerHash<Octs> {
95        &self.next_owner
96    }
97
98    pub fn types(&self) -> &RtypeBitmap<Octs> {
99        &self.types
100    }
101
102    pub(super) fn convert_octets<Target>(
103        self,
104    ) -> Result<Nsec3<Target>, Target::Error>
105    where
106        Target: OctetsFrom<Octs>,
107    {
108        Ok(Nsec3::new(
109            self.hash_algorithm,
110            self.flags,
111            self.iterations,
112            self.salt.try_octets_into()?,
113            self.next_owner.try_octets_into()?,
114            self.types.try_octets_into()?,
115        ))
116    }
117
118    pub(super) fn flatten<Target: OctetsFrom<Octs>>(
119        self,
120    ) -> Result<Nsec3<Target>, Target::Error> {
121        self.convert_octets()
122    }
123
124    pub fn scan<S: Scanner<Octets = Octs>>(
125        scanner: &mut S,
126    ) -> Result<Self, S::Error> {
127        Ok(Self::new(
128            Nsec3HashAlg::scan(scanner)?,
129            u8::scan(scanner)?,
130            u16::scan(scanner)?,
131            Nsec3Salt::scan(scanner)?,
132            OwnerHash::scan(scanner)?,
133            RtypeBitmap::scan(scanner)?,
134        ))
135    }
136}
137
138impl<Octs: AsRef<[u8]>> Nsec3<Octs> {
139    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
140        parser: &mut Parser<'a, Src>,
141    ) -> Result<Self, ParseError> {
142        let hash_algorithm = Nsec3HashAlg::parse(parser)?;
143        let flags = u8::parse(parser)?;
144        let iterations = u16::parse(parser)?;
145        let salt = Nsec3Salt::parse(parser)?;
146        let next_owner = OwnerHash::parse(parser)?;
147        let types = RtypeBitmap::parse(parser)?;
148        Ok(Self::new(
149            hash_algorithm,
150            flags,
151            iterations,
152            salt,
153            next_owner,
154            types,
155        ))
156    }
157}
158
159//--- OctetsFrom
160
161impl<Octs, SrcOcts> OctetsFrom<Nsec3<SrcOcts>> for Nsec3<Octs>
162where
163    Octs: OctetsFrom<SrcOcts>,
164{
165    type Error = Octs::Error;
166
167    fn try_octets_from(source: Nsec3<SrcOcts>) -> Result<Self, Self::Error> {
168        Ok(Nsec3::new(
169            source.hash_algorithm,
170            source.flags,
171            source.iterations,
172            Nsec3Salt::try_octets_from(source.salt)?,
173            OwnerHash::try_octets_from(source.next_owner)?,
174            RtypeBitmap::try_octets_from(source.types)?,
175        ))
176    }
177}
178
179//--- PartialEq and Eq
180
181impl<Octs, Other> PartialEq<Nsec3<Other>> for Nsec3<Octs>
182where
183    Octs: AsRef<[u8]>,
184    Other: AsRef<[u8]>,
185{
186    fn eq(&self, other: &Nsec3<Other>) -> bool {
187        self.hash_algorithm == other.hash_algorithm
188            && self.flags == other.flags
189            && self.iterations == other.iterations
190            && self.salt == other.salt
191            && self.next_owner == other.next_owner
192            && self.types == other.types
193    }
194}
195
196impl<Octs: AsRef<[u8]>> Eq for Nsec3<Octs> {}
197
198//--- PartialOrd, CanonicalOrd, and Ord
199
200impl<Octs, Other> PartialOrd<Nsec3<Other>> for Nsec3<Octs>
201where
202    Octs: AsRef<[u8]>,
203    Other: AsRef<[u8]>,
204{
205    fn partial_cmp(&self, other: &Nsec3<Other>) -> Option<Ordering> {
206        match self.hash_algorithm.partial_cmp(&other.hash_algorithm) {
207            Some(Ordering::Equal) => {}
208            other => return other,
209        }
210        match self.flags.partial_cmp(&other.flags) {
211            Some(Ordering::Equal) => {}
212            other => return other,
213        }
214        match self.iterations.partial_cmp(&other.iterations) {
215            Some(Ordering::Equal) => {}
216            other => return other,
217        }
218        match self.salt.partial_cmp(&other.salt) {
219            Some(Ordering::Equal) => {}
220            other => return other,
221        }
222        match self.next_owner.partial_cmp(&other.next_owner) {
223            Some(Ordering::Equal) => {}
224            other => return other,
225        }
226        self.types.partial_cmp(&other.types)
227    }
228}
229
230impl<Octs, Other> CanonicalOrd<Nsec3<Other>> for Nsec3<Octs>
231where
232    Octs: AsRef<[u8]>,
233    Other: AsRef<[u8]>,
234{
235    fn canonical_cmp(&self, other: &Nsec3<Other>) -> Ordering {
236        match self.hash_algorithm.cmp(&other.hash_algorithm) {
237            Ordering::Equal => {}
238            other => return other,
239        }
240        match self.flags.cmp(&other.flags) {
241            Ordering::Equal => {}
242            other => return other,
243        }
244        match self.iterations.cmp(&other.iterations) {
245            Ordering::Equal => {}
246            other => return other,
247        }
248        match self.salt.canonical_cmp(&other.salt) {
249            Ordering::Equal => {}
250            other => return other,
251        }
252        match self.next_owner.canonical_cmp(&other.next_owner) {
253            Ordering::Equal => {}
254            other => return other,
255        }
256        self.types.canonical_cmp(&other.types)
257    }
258}
259
260impl<Octs: AsRef<[u8]>> Ord for Nsec3<Octs> {
261    fn cmp(&self, other: &Self) -> Ordering {
262        self.canonical_cmp(other)
263    }
264}
265
266//--- Hash
267
268impl<Octs: AsRef<[u8]>> hash::Hash for Nsec3<Octs> {
269    fn hash<H: hash::Hasher>(&self, state: &mut H) {
270        self.hash_algorithm.hash(state);
271        self.flags.hash(state);
272        self.iterations.hash(state);
273        self.salt.hash(state);
274        self.next_owner.hash(state);
275        self.types.hash(state);
276    }
277}
278
279//--- RecordData
280
281impl<Octs> RecordData for Nsec3<Octs> {
282    fn rtype(&self) -> Rtype {
283        Rtype::Nsec3
284    }
285}
286
287impl<'a, Octs> ParseRecordData<'a, Octs> for Nsec3<Octs::Range<'a>>
288where
289    Octs: Octets + ?Sized,
290{
291    fn parse_rdata(
292        rtype: Rtype,
293        parser: &mut Parser<'a, Octs>,
294    ) -> Result<Option<Self>, ParseError> {
295        if rtype == Rtype::Nsec3 {
296            Self::parse(parser).map(Some)
297        } else {
298            Ok(None)
299        }
300    }
301}
302
303impl<Octs: AsRef<[u8]>> ComposeRecordData for Nsec3<Octs> {
304    fn rdlen(&self, _compress: bool) -> Option<u16> {
305        Some(
306            u16::checked_add(
307                Nsec3HashAlg::COMPOSE_LEN
308                    + u8::COMPOSE_LEN
309                    + u16::COMPOSE_LEN,
310                self.salt.compose_len(),
311            )
312            .expect("long NSEC3")
313            .checked_add(self.next_owner.compose_len())
314            .expect("long NSEC3")
315            .checked_add(self.types.compose_len())
316            .expect("long NSEC3"),
317        )
318    }
319
320    fn compose_rdata<Target: Composer + ?Sized>(
321        &self,
322        target: &mut Target,
323    ) -> Result<(), Target::AppendError> {
324        self.hash_algorithm.compose(target)?;
325        self.flags.compose(target)?;
326        self.iterations.compose(target)?;
327        self.salt.compose(target)?;
328        self.next_owner.compose(target)?;
329        self.types.compose(target)
330    }
331
332    fn compose_canonical_rdata<Target: Composer + ?Sized>(
333        &self,
334        target: &mut Target,
335    ) -> Result<(), Target::AppendError> {
336        self.compose_rdata(target)
337    }
338}
339
340//--- Display, and Debug
341
342impl<Octs: AsRef<[u8]>> fmt::Display for Nsec3<Octs> {
343    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
344        write!(
345            f,
346            "{} {} {} {} ",
347            self.hash_algorithm, self.flags, self.iterations, self.salt
348        )?;
349        base32::display_hex(&self.next_owner, f)?;
350        write!(f, " {}", self.types)
351    }
352}
353
354impl<Octs: AsRef<[u8]>> fmt::Debug for Nsec3<Octs> {
355    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
356        f.debug_struct("Nsec3")
357            .field("hash_algorithm", &self.hash_algorithm)
358            .field("flags", &self.flags)
359            .field("iterations", &self.iterations)
360            .field("salt", &self.salt)
361            .field("next_owner", &self.next_owner)
362            .field("types", &self.types)
363            .finish()
364    }
365}
366
367//------------ Nsec3Param ----------------------------------------------------
368
369#[derive(Clone)]
370#[cfg_attr(
371    feature = "serde",
372    derive(serde::Serialize, serde::Deserialize),
373    serde(bound(
374        serialize = "
375            Octs: octseq::serde::SerializeOctets + AsRef<[u8]>,
376        ",
377        deserialize = "
378            Octs: FromBuilder + octseq::serde::DeserializeOctets<'de>,
379            <Octs as FromBuilder>::Builder: OctetsBuilder + EmptyBuilder,
380        ",
381    ))
382)]
383pub struct Nsec3param<Octs> {
384    hash_algorithm: Nsec3HashAlg,
385    flags: u8,
386    iterations: u16,
387    salt: Nsec3Salt<Octs>,
388}
389
390impl<Octs> Nsec3param<Octs> {
391    pub fn new(
392        hash_algorithm: Nsec3HashAlg,
393        flags: u8,
394        iterations: u16,
395        salt: Nsec3Salt<Octs>,
396    ) -> Self {
397        Nsec3param {
398            hash_algorithm,
399            flags,
400            iterations,
401            salt,
402        }
403    }
404
405    pub fn hash_algorithm(&self) -> Nsec3HashAlg {
406        self.hash_algorithm
407    }
408
409    pub fn flags(&self) -> u8 {
410        self.flags
411    }
412
413    pub fn iterations(&self) -> u16 {
414        self.iterations
415    }
416
417    pub fn salt(&self) -> &Nsec3Salt<Octs> {
418        &self.salt
419    }
420
421    pub(super) fn convert_octets<Target>(
422        self,
423    ) -> Result<Nsec3param<Target>, Target::Error>
424    where
425        Target: OctetsFrom<Octs>,
426    {
427        Ok(Nsec3param::new(
428            self.hash_algorithm,
429            self.flags,
430            self.iterations,
431            self.salt.try_octets_into()?,
432        ))
433    }
434
435    pub(super) fn flatten<Target: OctetsFrom<Octs>>(
436        self,
437    ) -> Result<Nsec3param<Target>, Target::Error> {
438        self.convert_octets()
439    }
440
441    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
442        parser: &mut Parser<'a, Src>,
443    ) -> Result<Self, ParseError> {
444        Ok(Self::new(
445            Nsec3HashAlg::parse(parser)?,
446            u8::parse(parser)?,
447            u16::parse(parser)?,
448            Nsec3Salt::parse(parser)?,
449        ))
450    }
451
452    pub fn scan<S: Scanner<Octets = Octs>>(
453        scanner: &mut S,
454    ) -> Result<Self, S::Error> {
455        Ok(Self::new(
456            Nsec3HashAlg::scan(scanner)?,
457            u8::scan(scanner)?,
458            u16::scan(scanner)?,
459            Nsec3Salt::scan(scanner)?,
460        ))
461    }
462}
463
464//--- OctetsFrom
465
466impl<Octs, SrcOcts> OctetsFrom<Nsec3param<SrcOcts>> for Nsec3param<Octs>
467where
468    Octs: OctetsFrom<SrcOcts>,
469{
470    type Error = Octs::Error;
471
472    fn try_octets_from(
473        source: Nsec3param<SrcOcts>,
474    ) -> Result<Self, Self::Error> {
475        Ok(Nsec3param::new(
476            source.hash_algorithm,
477            source.flags,
478            source.iterations,
479            Nsec3Salt::try_octets_from(source.salt)?,
480        ))
481    }
482}
483
484//--- PartialEq and Eq
485
486impl<Octs, Other> PartialEq<Nsec3param<Other>> for Nsec3param<Octs>
487where
488    Octs: AsRef<[u8]>,
489    Other: AsRef<[u8]>,
490{
491    fn eq(&self, other: &Nsec3param<Other>) -> bool {
492        self.hash_algorithm == other.hash_algorithm
493            && self.flags == other.flags
494            && self.iterations == other.iterations
495            && self.salt == other.salt
496    }
497}
498
499impl<Octs: AsRef<[u8]>> Eq for Nsec3param<Octs> {}
500
501//--- PartialOrd, CanonicalOrd, and Ord
502
503impl<Octs, Other> PartialOrd<Nsec3param<Other>> for Nsec3param<Octs>
504where
505    Octs: AsRef<[u8]>,
506    Other: AsRef<[u8]>,
507{
508    fn partial_cmp(&self, other: &Nsec3param<Other>) -> Option<Ordering> {
509        match self.hash_algorithm.partial_cmp(&other.hash_algorithm) {
510            Some(Ordering::Equal) => {}
511            other => return other,
512        }
513        match self.flags.partial_cmp(&other.flags) {
514            Some(Ordering::Equal) => {}
515            other => return other,
516        }
517        match self.iterations.partial_cmp(&other.iterations) {
518            Some(Ordering::Equal) => {}
519            other => return other,
520        }
521        self.salt.partial_cmp(&other.salt)
522    }
523}
524
525impl<Octs, Other> CanonicalOrd<Nsec3param<Other>> for Nsec3param<Octs>
526where
527    Octs: AsRef<[u8]>,
528    Other: AsRef<[u8]>,
529{
530    fn canonical_cmp(&self, other: &Nsec3param<Other>) -> Ordering {
531        match self.hash_algorithm.cmp(&other.hash_algorithm) {
532            Ordering::Equal => {}
533            other => return other,
534        }
535        match self.flags.cmp(&other.flags) {
536            Ordering::Equal => {}
537            other => return other,
538        }
539        match self.iterations.cmp(&other.iterations) {
540            Ordering::Equal => {}
541            other => return other,
542        }
543        self.salt.canonical_cmp(&other.salt)
544    }
545}
546
547impl<Octs: AsRef<[u8]>> Ord for Nsec3param<Octs> {
548    fn cmp(&self, other: &Self) -> Ordering {
549        match self.hash_algorithm.cmp(&other.hash_algorithm) {
550            Ordering::Equal => {}
551            other => return other,
552        }
553        match self.flags.cmp(&other.flags) {
554            Ordering::Equal => {}
555            other => return other,
556        }
557        match self.iterations.cmp(&other.iterations) {
558            Ordering::Equal => {}
559            other => return other,
560        }
561        self.salt.cmp(&other.salt)
562    }
563}
564
565//--- Hash
566
567impl<Octs: AsRef<[u8]>> hash::Hash for Nsec3param<Octs> {
568    fn hash<H: hash::Hasher>(&self, state: &mut H) {
569        self.hash_algorithm.hash(state);
570        self.flags.hash(state);
571        self.iterations.hash(state);
572        self.salt.hash(state);
573    }
574}
575
576//--- RecordData, ParseRecordData, ComposeRecordData
577
578impl<Octs> RecordData for Nsec3param<Octs> {
579    fn rtype(&self) -> Rtype {
580        Rtype::Nsec3param
581    }
582}
583
584impl<'a, Octs> ParseRecordData<'a, Octs> for Nsec3param<Octs::Range<'a>>
585where
586    Octs: Octets + ?Sized,
587{
588    fn parse_rdata(
589        rtype: Rtype,
590        parser: &mut Parser<'a, Octs>,
591    ) -> Result<Option<Self>, ParseError> {
592        if rtype == Rtype::Nsec3param {
593            Self::parse(parser).map(Some)
594        } else {
595            Ok(None)
596        }
597    }
598}
599
600impl<Octs: AsRef<[u8]>> ComposeRecordData for Nsec3param<Octs> {
601    fn rdlen(&self, _compress: bool) -> Option<u16> {
602        Some(
603            u16::checked_add(
604                Nsec3HashAlg::COMPOSE_LEN
605                    + u8::COMPOSE_LEN
606                    + u16::COMPOSE_LEN,
607                self.salt.compose_len(),
608            )
609            .expect("long NSEC3"),
610        )
611    }
612
613    fn compose_rdata<Target: Composer + ?Sized>(
614        &self,
615        target: &mut Target,
616    ) -> Result<(), Target::AppendError> {
617        self.hash_algorithm.compose(target)?;
618        self.flags.compose(target)?;
619        self.iterations.compose(target)?;
620        self.salt.compose(target)
621    }
622
623    fn compose_canonical_rdata<Target: Composer + ?Sized>(
624        &self,
625        target: &mut Target,
626    ) -> Result<(), Target::AppendError> {
627        self.compose_rdata(target)
628    }
629}
630
631//--- Display and Debug
632
633impl<Octs: AsRef<[u8]>> fmt::Display for Nsec3param<Octs> {
634    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
635        write!(
636            f,
637            "{} {} {} {}",
638            self.hash_algorithm, self.flags, self.iterations, self.salt
639        )
640    }
641}
642
643impl<Octs: AsRef<[u8]>> fmt::Debug for Nsec3param<Octs> {
644    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
645        f.debug_struct("Nsec3param")
646            .field("hash_algorithm", &self.hash_algorithm)
647            .field("flags", &self.flags)
648            .field("iterations", &self.iterations)
649            .field("salt", &self.salt)
650            .finish()
651    }
652}
653
654//------------ Nsec3Salt -----------------------------------------------------
655
656/// The salt value of an NSEC3 record.
657///
658/// The salt can never be longer than 255 octets since its length is encoded
659/// as a single octet.
660///
661/// The salt uses Base 16 (i.e., hex digits) as its representation format with
662/// no whitespace allowed.
663#[derive(Clone)]
664pub struct Nsec3Salt<Octs: ?Sized>(Octs);
665
666impl Nsec3Salt<()> {
667    /// The salt has a maximum length 255 octets since its length is encoded
668    /// as a single octet.
669    pub const MAX_LEN: usize = 255;
670}
671
672impl<Octs: ?Sized> Nsec3Salt<Octs> {
673    /// Creates an empty salt value.
674    #[must_use]
675    pub fn empty() -> Self
676    where
677        Octs: From<&'static [u8]>,
678    {
679        Self(b"".as_ref().into())
680    }
681
682    /// Crates a new salt value from the given octets.
683    ///
684    /// Returns succesfully if `octets` can indeed be used as a
685    /// character string, i.e., it is not longer than 255 bytes.
686    pub fn from_octets(octets: Octs) -> Result<Self, Nsec3SaltError>
687    where
688        Octs: AsRef<[u8]> + Sized,
689    {
690        if octets.as_ref().len() > Nsec3Salt::MAX_LEN {
691            Err(Nsec3SaltError)
692        } else {
693            Ok(unsafe { Self::from_octets_unchecked(octets) })
694        }
695    }
696
697    /// Creates a salt value from octets without length check.
698    ///
699    /// As this can break the guarantees made by the type, it is unsafe.
700    unsafe fn from_octets_unchecked(octets: Octs) -> Self
701    where
702        Octs: Sized,
703    {
704        Self(octets)
705    }
706
707    /// Converts the salt value into the underlying octets.
708    pub fn into_octets(self) -> Octs
709    where
710        Octs: Sized,
711    {
712        self.0
713    }
714
715    /// Returns a reference to a slice of the salt.
716    pub fn as_slice(&self) -> &[u8]
717    where
718        Octs: AsRef<[u8]>,
719    {
720        self.0.as_ref()
721    }
722
723    fn salt_len(&self) -> u8
724    where
725        Octs: AsRef<[u8]>,
726    {
727        self.0.as_ref().len().try_into().expect("long salt")
728    }
729
730    fn compose_len(&self) -> u16
731    where
732        Octs: AsRef<[u8]>,
733    {
734        u16::from(self.salt_len()) + 1
735    }
736
737    fn compose<Target: Composer /*OctetsBuilder*/ + ?Sized>(
738        &self,
739        target: &mut Target,
740    ) -> Result<(), Target::AppendError>
741    where
742        Octs: AsRef<[u8]>,
743    {
744        self.salt_len().compose(target)?;
745        target.append_slice(self.0.as_ref())
746    }
747}
748
749#[cfg(feature = "bytes")]
750#[cfg_attr(docsrs, doc(cfg(feature = "bytes")))]
751impl Nsec3Salt<Bytes> {
752    /// Creates a new salt from a bytes value.
753    pub fn from_bytes(bytes: Bytes) -> Result<Self, Nsec3SaltError> {
754        Self::from_octets(bytes)
755    }
756}
757
758impl Nsec3Salt<[u8]> {
759    /// Creates a new salt value from an octet slice.
760    pub fn from_slice(slice: &[u8]) -> Result<&Self, Nsec3SaltError> {
761        if slice.len() > Nsec3Salt::MAX_LEN {
762            Err(Nsec3SaltError)
763        } else {
764            Ok(unsafe { &*(slice as *const [u8] as *const Nsec3Salt<[u8]>) })
765        }
766    }
767}
768
769impl<Octs> Nsec3Salt<Octs> {
770    pub fn scan<S: Scanner<Octets = Octs>>(
771        scanner: &mut S,
772    ) -> Result<Self, S::Error> {
773        #[derive(Default)]
774        struct Converter(Option<Option<base16::SymbolConverter>>);
775
776        impl<Sym, Error> ConvertSymbols<Sym, Error> for Converter
777        where
778            Sym: Into<EntrySymbol>,
779            Error: ScannerError,
780        {
781            fn process_symbol(
782                &mut self,
783                symbol: Sym,
784            ) -> Result<Option<&[u8]>, Error> {
785                let symbol = symbol.into();
786                // If we are none, this is the first symbol. A '-' means
787                // empty. Anything else means Base 16.
788                if self.0.is_none() {
789                    match symbol {
790                        EntrySymbol::Symbol(symbol)
791                            if symbol.into_char() == Ok('-') =>
792                        {
793                            self.0 = Some(None);
794                            return Ok(None);
795                        }
796                        _ => {
797                            self.0 =
798                                Some(Some(base16::SymbolConverter::new()));
799                        }
800                    }
801                }
802
803                match self.0.as_mut() {
804                    None => unreachable!(),
805                    Some(None) => Err(Error::custom("illegal NSEC3 salt")),
806                    Some(Some(ref mut base16)) => {
807                        base16.process_symbol(symbol)
808                    }
809                }
810            }
811
812            fn process_tail(&mut self) -> Result<Option<&[u8]>, Error> {
813                if let Some(Some(ref mut base16)) = self.0 {
814                    <base16::SymbolConverter
815                        as ConvertSymbols<Sym, Error>
816                    >::process_tail(base16)
817                } else {
818                    Ok(None)
819                }
820            }
821        }
822
823        scanner
824            .convert_token(Converter::default())
825            .map(|res| unsafe { Self::from_octets_unchecked(res) })
826    }
827
828    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
829        parser: &mut Parser<'a, Src>,
830    ) -> Result<Self, ParseError> {
831        let len = parser.parse_u8()? as usize;
832        parser
833            .parse_octets(len)
834            .map(|octets| unsafe { Self::from_octets_unchecked(octets) })
835            .map_err(Into::into)
836    }
837}
838
839//--- OctetsFrom and FromStr
840
841impl<Octs, SrcOcts> OctetsFrom<Nsec3Salt<SrcOcts>> for Nsec3Salt<Octs>
842where
843    Octs: OctetsFrom<SrcOcts>,
844{
845    type Error = Octs::Error;
846
847    fn try_octets_from(
848        source: Nsec3Salt<SrcOcts>,
849    ) -> Result<Self, Self::Error> {
850        Octs::try_octets_from(source.0)
851            .map(|octets| unsafe { Self::from_octets_unchecked(octets) })
852    }
853}
854
855impl<Octs> str::FromStr for Nsec3Salt<Octs>
856where
857    Octs: FromBuilder,
858    <Octs as FromBuilder>::Builder: EmptyBuilder,
859{
860    type Err = base16::DecodeError;
861
862    fn from_str(s: &str) -> Result<Self, Self::Err> {
863        if s == "-" {
864            Ok(unsafe {
865                Self::from_octets_unchecked(Octs::Builder::empty().freeze())
866            })
867        } else {
868            base16::decode(s)
869                .map(|octets| unsafe { Self::from_octets_unchecked(octets) })
870        }
871    }
872}
873
874//--- AsRef
875
876impl<Octs: AsRef<U> + ?Sized, U: ?Sized> AsRef<U> for Nsec3Salt<Octs> {
877    fn as_ref(&self) -> &U {
878        self.0.as_ref()
879    }
880}
881
882//--- PartialEq and Eq
883
884impl<T, U> PartialEq<U> for Nsec3Salt<T>
885where
886    T: AsRef<[u8]> + ?Sized,
887    U: AsRef<[u8]> + ?Sized,
888{
889    fn eq(&self, other: &U) -> bool {
890        self.as_slice().eq(other.as_ref())
891    }
892}
893
894impl<T: AsRef<[u8]> + ?Sized> Eq for Nsec3Salt<T> {}
895
896//--- PartialOrd, Ord, and CanonicalOrd
897
898impl<T, U> PartialOrd<U> for Nsec3Salt<T>
899where
900    T: AsRef<[u8]> + ?Sized,
901    U: AsRef<[u8]> + ?Sized,
902{
903    fn partial_cmp(&self, other: &U) -> Option<Ordering> {
904        self.0.as_ref().partial_cmp(other.as_ref())
905    }
906}
907
908impl<T: AsRef<[u8]> + ?Sized> Ord for Nsec3Salt<T> {
909    fn cmp(&self, other: &Self) -> Ordering {
910        self.0.as_ref().cmp(other.as_ref())
911    }
912}
913
914impl<T, U> CanonicalOrd<Nsec3Salt<U>> for Nsec3Salt<T>
915where
916    T: AsRef<[u8]> + ?Sized,
917    U: AsRef<[u8]> + ?Sized,
918{
919    fn canonical_cmp(&self, other: &Nsec3Salt<U>) -> Ordering {
920        match self.0.as_ref().len().cmp(&other.0.as_ref().len()) {
921            Ordering::Equal => {}
922            other => return other,
923        }
924        self.as_slice().cmp(other.as_slice())
925    }
926}
927
928//--- Hash
929
930impl<T: AsRef<[u8]> + ?Sized> hash::Hash for Nsec3Salt<T> {
931    fn hash<H: hash::Hasher>(&self, state: &mut H) {
932        self.0.as_ref().hash(state)
933    }
934}
935
936//--- Display and Debug
937
938impl<Octs: AsRef<[u8]> + ?Sized> fmt::Display for Nsec3Salt<Octs> {
939    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
940        base16::display(self.as_slice(), f)
941    }
942}
943
944impl<Octs: AsRef<[u8]> + ?Sized> fmt::Debug for Nsec3Salt<Octs> {
945    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
946        f.debug_tuple("Nsec3Salt")
947            .field(&format_args!("{}", self))
948            .finish()
949    }
950}
951
952//--- Serialize and Deserialize
953
954#[cfg(feature = "serde")]
955impl<T: AsRef<[u8]> + SerializeOctets> serde::Serialize for Nsec3Salt<T> {
956    fn serialize<S: serde::Serializer>(
957        &self,
958        serializer: S,
959    ) -> Result<S::Ok, S::Error> {
960        if serializer.is_human_readable() {
961            serializer.serialize_newtype_struct(
962                "Nsec3Salt",
963                &format_args!("{}", self),
964            )
965        } else {
966            serializer.serialize_newtype_struct(
967                "Nsec3Salt",
968                &self.0.as_serialized_octets(),
969            )
970        }
971    }
972}
973
974#[cfg(feature = "serde")]
975impl<'de, Octs> serde::Deserialize<'de> for Nsec3Salt<Octs>
976where
977    Octs: FromBuilder + DeserializeOctets<'de>,
978    <Octs as FromBuilder>::Builder: OctetsBuilder + EmptyBuilder,
979{
980    fn deserialize<D: serde::Deserializer<'de>>(
981        deserializer: D,
982    ) -> Result<Self, D::Error> {
983        use core::marker::PhantomData;
984        use core::str::FromStr;
985
986        struct InnerVisitor<'de, T: DeserializeOctets<'de>>(T::Visitor);
987
988        impl<'de, Octs> serde::de::Visitor<'de> for InnerVisitor<'de, Octs>
989        where
990            Octs: FromBuilder + DeserializeOctets<'de>,
991            <Octs as FromBuilder>::Builder: OctetsBuilder + EmptyBuilder,
992        {
993            type Value = Nsec3Salt<Octs>;
994
995            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
996                f.write_str("an NSEC3 salt value")
997            }
998
999            fn visit_str<E: serde::de::Error>(
1000                self,
1001                v: &str,
1002            ) -> Result<Self::Value, E> {
1003                Nsec3Salt::from_str(v).map_err(E::custom)
1004            }
1005
1006            fn visit_borrowed_bytes<E: serde::de::Error>(
1007                self,
1008                value: &'de [u8],
1009            ) -> Result<Self::Value, E> {
1010                self.0.visit_borrowed_bytes(value).and_then(|octets| {
1011                    Nsec3Salt::from_octets(octets).map_err(E::custom)
1012                })
1013            }
1014
1015            #[cfg(feature = "std")]
1016            fn visit_byte_buf<E: serde::de::Error>(
1017                self,
1018                value: std::vec::Vec<u8>,
1019            ) -> Result<Self::Value, E> {
1020                self.0.visit_byte_buf(value).and_then(|octets| {
1021                    Nsec3Salt::from_octets(octets).map_err(E::custom)
1022                })
1023            }
1024        }
1025
1026        struct NewtypeVisitor<T>(PhantomData<T>);
1027
1028        impl<'de, Octs> serde::de::Visitor<'de> for NewtypeVisitor<Octs>
1029        where
1030            Octs: FromBuilder + DeserializeOctets<'de>,
1031            <Octs as FromBuilder>::Builder: OctetsBuilder + EmptyBuilder,
1032        {
1033            type Value = Nsec3Salt<Octs>;
1034
1035            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
1036                f.write_str("an NSEC3 salt value")
1037            }
1038
1039            fn visit_newtype_struct<D: serde::Deserializer<'de>>(
1040                self,
1041                deserializer: D,
1042            ) -> Result<Self::Value, D::Error> {
1043                if deserializer.is_human_readable() {
1044                    deserializer
1045                        .deserialize_str(InnerVisitor(Octs::visitor()))
1046                } else {
1047                    Octs::deserialize_with_visitor(
1048                        deserializer,
1049                        InnerVisitor(Octs::visitor()),
1050                    )
1051                }
1052            }
1053        }
1054
1055        deserializer.deserialize_newtype_struct(
1056            "Nsec3Salt",
1057            NewtypeVisitor(PhantomData),
1058        )
1059    }
1060}
1061
1062//------------ OwnerHash -----------------------------------------------------
1063
1064/// The hash over the next owner name.
1065///
1066/// This hash is used instead of the actual owner name in an NSEC3 record.
1067///
1068/// The hash can never be longer than 255 octets since its lenght is encoded
1069/// as a single octet.
1070///
1071/// For its presentation format, the hash uses an unpadded Base 32 encoding
1072/// with no whitespace allowed.
1073#[derive(Clone)]
1074pub struct OwnerHash<Octs: ?Sized>(Octs);
1075
1076impl OwnerHash<()> {
1077    /// The hash has a maximum length 255 octets since its length is encoded
1078    /// as a single octet.
1079    pub const MAX_LEN: usize = 255;
1080}
1081
1082impl<Octs> OwnerHash<Octs> {
1083    /// Creates a new owner hash from the given octets.
1084    ///
1085    /// Returns succesfully if `octets` can indeed be used as a
1086    /// character string, i.e., it is not longer than 255 bytes.
1087    pub fn from_octets(octets: Octs) -> Result<Self, OwnerHashError>
1088    where
1089        Octs: AsRef<[u8]>,
1090    {
1091        if octets.as_ref().len() > OwnerHash::MAX_LEN {
1092            Err(OwnerHashError)
1093        } else {
1094            Ok(unsafe { Self::from_octets_unchecked(octets) })
1095        }
1096    }
1097
1098    /// Creates an owner hash from octets without length check.
1099    ///
1100    /// As this can break the guarantees made by the type, it is unsafe.
1101    unsafe fn from_octets_unchecked(octets: Octs) -> Self {
1102        Self(octets)
1103    }
1104
1105    pub fn scan<S: Scanner<Octets = Octs>>(
1106        scanner: &mut S,
1107    ) -> Result<Self, S::Error> {
1108        scanner
1109            .convert_token(base32::SymbolConverter::new())
1110            .map(|octets| unsafe { Self::from_octets_unchecked(octets) })
1111    }
1112
1113    /// Converts the hash into the underlying octets.
1114    pub fn into_octets(self) -> Octs
1115    where
1116        Octs: Sized,
1117    {
1118        self.0
1119    }
1120}
1121
1122impl<Octs: ?Sized> OwnerHash<Octs> {
1123    /// Returns a reference to a slice of the hash.
1124    pub fn as_slice(&self) -> &[u8]
1125    where
1126        Octs: AsRef<[u8]>,
1127    {
1128        self.0.as_ref()
1129    }
1130
1131    fn hash_len(&self) -> u8
1132    where
1133        Octs: AsRef<[u8]>,
1134    {
1135        self.0.as_ref().len().try_into().expect("long hash")
1136    }
1137
1138    fn compose_len(&self) -> u16
1139    where
1140        Octs: AsRef<[u8]>,
1141    {
1142        u16::from(self.hash_len()) + 1
1143    }
1144
1145    fn compose<Target: Composer /*OctetsBuilder*/ + ?Sized>(
1146        &self,
1147        target: &mut Target,
1148    ) -> Result<(), Target::AppendError>
1149    where
1150        Octs: AsRef<[u8]>,
1151    {
1152        self.hash_len().compose(target)?;
1153        target.append_slice(self.0.as_ref())
1154    }
1155}
1156
1157#[cfg(feature = "bytes")]
1158#[cfg_attr(docsrs, doc(cfg(feature = "bytes")))]
1159impl OwnerHash<Bytes> {
1160    /// Creates a new owner hash from a bytes value.
1161    pub fn from_bytes(bytes: Bytes) -> Result<Self, OwnerHashError> {
1162        Self::from_octets(bytes)
1163    }
1164}
1165
1166impl OwnerHash<[u8]> {
1167    /// Creates a new owner hash from an octet slice.
1168    pub fn from_slice(slice: &[u8]) -> Result<&Self, OwnerHashError> {
1169        if slice.len() > OwnerHash::MAX_LEN {
1170            Err(OwnerHashError)
1171        } else {
1172            Ok(unsafe { &*(slice as *const [u8] as *const OwnerHash<[u8]>) })
1173        }
1174    }
1175}
1176
1177impl<Octs> OwnerHash<Octs> {
1178    fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
1179        parser: &mut Parser<'a, Src>,
1180    ) -> Result<Self, ParseError> {
1181        let len = parser.parse_u8()? as usize;
1182        parser
1183            .parse_octets(len)
1184            .map(|octets| unsafe { Self::from_octets_unchecked(octets) })
1185            .map_err(Into::into)
1186    }
1187}
1188
1189//--- OctetsFrom and FromStr
1190
1191impl<Octs, SrcOcts> OctetsFrom<OwnerHash<SrcOcts>> for OwnerHash<Octs>
1192where
1193    Octs: OctetsFrom<SrcOcts>,
1194{
1195    type Error = Octs::Error;
1196
1197    fn try_octets_from(
1198        source: OwnerHash<SrcOcts>,
1199    ) -> Result<Self, Self::Error> {
1200        Octs::try_octets_from(source.0)
1201            .map(|octets| unsafe { Self::from_octets_unchecked(octets) })
1202    }
1203}
1204
1205impl<Octs> str::FromStr for OwnerHash<Octs>
1206where
1207    Octs: FromBuilder,
1208    <Octs as FromBuilder>::Builder: OctetsBuilder + EmptyBuilder,
1209{
1210    type Err = base32::DecodeError;
1211
1212    fn from_str(s: &str) -> Result<Self, Self::Err> {
1213        base32::decode_hex(s)
1214            .map(|octets| unsafe { Self::from_octets_unchecked(octets) })
1215    }
1216}
1217
1218//--- AsRef
1219
1220impl<Octs: AsRef<U> + ?Sized, U: ?Sized> AsRef<U> for OwnerHash<Octs> {
1221    fn as_ref(&self) -> &U {
1222        self.0.as_ref()
1223    }
1224}
1225
1226//--- PartialEq and Eq
1227
1228impl<T, U> PartialEq<U> for OwnerHash<T>
1229where
1230    T: AsRef<[u8]> + ?Sized,
1231    U: AsRef<[u8]> + ?Sized,
1232{
1233    fn eq(&self, other: &U) -> bool {
1234        self.as_slice().eq(other.as_ref())
1235    }
1236}
1237
1238impl<T: AsRef<[u8]> + ?Sized> Eq for OwnerHash<T> {}
1239
1240//--- PartialOrd, Ord, and CanonicalOrd
1241
1242impl<T, U> PartialOrd<U> for OwnerHash<T>
1243where
1244    T: AsRef<[u8]> + ?Sized,
1245    U: AsRef<[u8]> + ?Sized,
1246{
1247    fn partial_cmp(&self, other: &U) -> Option<Ordering> {
1248        self.0.as_ref().partial_cmp(other.as_ref())
1249    }
1250}
1251
1252impl<T: AsRef<[u8]> + ?Sized> Ord for OwnerHash<T> {
1253    fn cmp(&self, other: &Self) -> Ordering {
1254        self.0.as_ref().cmp(other.as_ref())
1255    }
1256}
1257
1258impl<T, U> CanonicalOrd<OwnerHash<U>> for OwnerHash<T>
1259where
1260    T: AsRef<[u8]> + ?Sized,
1261    U: AsRef<[u8]> + ?Sized,
1262{
1263    fn canonical_cmp(&self, other: &OwnerHash<U>) -> Ordering {
1264        match self.0.as_ref().len().cmp(&other.0.as_ref().len()) {
1265            Ordering::Equal => {}
1266            other => return other,
1267        }
1268        self.as_slice().cmp(other.as_slice())
1269    }
1270}
1271
1272//--- Hash
1273
1274impl<T: AsRef<[u8]> + ?Sized> hash::Hash for OwnerHash<T> {
1275    fn hash<H: hash::Hasher>(&self, state: &mut H) {
1276        self.0.as_ref().hash(state)
1277    }
1278}
1279
1280//--- Display
1281
1282impl<Octs: AsRef<[u8]> + ?Sized> fmt::Display for OwnerHash<Octs> {
1283    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1284        base32::display_hex(self.as_slice(), f)
1285    }
1286}
1287
1288impl<Octs: AsRef<[u8]> + ?Sized> fmt::Debug for OwnerHash<Octs> {
1289    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1290        f.debug_tuple("OwnerHash")
1291            .field(&format_args!("{}", self))
1292            .finish()
1293    }
1294}
1295
1296//--- Serialize and Deserialize
1297
1298#[cfg(feature = "serde")]
1299impl<T: AsRef<[u8]> + SerializeOctets> serde::Serialize for OwnerHash<T> {
1300    fn serialize<S: serde::Serializer>(
1301        &self,
1302        serializer: S,
1303    ) -> Result<S::Ok, S::Error> {
1304        if serializer.is_human_readable() {
1305            serializer.serialize_newtype_struct(
1306                "OwnerHash",
1307                &format_args!("{}", self),
1308            )
1309        } else {
1310            serializer.serialize_newtype_struct(
1311                "OwnerHash",
1312                &self.0.as_serialized_octets(),
1313            )
1314        }
1315    }
1316}
1317
1318#[cfg(feature = "serde")]
1319impl<'de, Octs> serde::Deserialize<'de> for OwnerHash<Octs>
1320where
1321    Octs: FromBuilder + DeserializeOctets<'de>,
1322    <Octs as FromBuilder>::Builder: OctetsBuilder + EmptyBuilder,
1323{
1324    fn deserialize<D: serde::Deserializer<'de>>(
1325        deserializer: D,
1326    ) -> Result<Self, D::Error> {
1327        use core::marker::PhantomData;
1328        use core::str::FromStr;
1329
1330        struct InnerVisitor<'de, T: DeserializeOctets<'de>>(T::Visitor);
1331
1332        impl<'de, Octs> serde::de::Visitor<'de> for InnerVisitor<'de, Octs>
1333        where
1334            Octs: FromBuilder + DeserializeOctets<'de>,
1335            <Octs as FromBuilder>::Builder: OctetsBuilder + EmptyBuilder,
1336        {
1337            type Value = OwnerHash<Octs>;
1338
1339            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
1340                f.write_str("an owner name hash value")
1341            }
1342
1343            fn visit_str<E: serde::de::Error>(
1344                self,
1345                v: &str,
1346            ) -> Result<Self::Value, E> {
1347                OwnerHash::from_str(v).map_err(E::custom)
1348            }
1349
1350            fn visit_borrowed_bytes<E: serde::de::Error>(
1351                self,
1352                value: &'de [u8],
1353            ) -> Result<Self::Value, E> {
1354                self.0.visit_borrowed_bytes(value).and_then(|octets| {
1355                    OwnerHash::from_octets(octets).map_err(E::custom)
1356                })
1357            }
1358
1359            #[cfg(feature = "std")]
1360            fn visit_byte_buf<E: serde::de::Error>(
1361                self,
1362                value: std::vec::Vec<u8>,
1363            ) -> Result<Self::Value, E> {
1364                self.0.visit_byte_buf(value).and_then(|octets| {
1365                    OwnerHash::from_octets(octets).map_err(E::custom)
1366                })
1367            }
1368        }
1369
1370        struct NewtypeVisitor<T>(PhantomData<T>);
1371
1372        impl<'de, Octs> serde::de::Visitor<'de> for NewtypeVisitor<Octs>
1373        where
1374            Octs: FromBuilder + DeserializeOctets<'de>,
1375            <Octs as FromBuilder>::Builder: OctetsBuilder + EmptyBuilder,
1376        {
1377            type Value = OwnerHash<Octs>;
1378
1379            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
1380                f.write_str("an owner name hash value")
1381            }
1382
1383            fn visit_newtype_struct<D: serde::Deserializer<'de>>(
1384                self,
1385                deserializer: D,
1386            ) -> Result<Self::Value, D::Error> {
1387                if deserializer.is_human_readable() {
1388                    deserializer
1389                        .deserialize_str(InnerVisitor(Octs::visitor()))
1390                } else {
1391                    Octs::deserialize_with_visitor(
1392                        deserializer,
1393                        InnerVisitor(Octs::visitor()),
1394                    )
1395                }
1396            }
1397        }
1398
1399        deserializer.deserialize_newtype_struct(
1400            "OwnerHash",
1401            NewtypeVisitor(PhantomData),
1402        )
1403    }
1404}
1405
1406//============ Error Types ===================================================
1407
1408//------------ Nsec3SaltError ------------------------------------------------
1409
1410/// A byte sequence does not represent a valid NSEC3 salt.
1411///
1412/// This can only mean that the sequence is longer than 255 bytes.
1413#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1414pub struct Nsec3SaltError;
1415
1416impl fmt::Display for Nsec3SaltError {
1417    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1418        f.write_str("illegal NSEC3 salt")
1419    }
1420}
1421
1422#[cfg(feature = "std")]
1423impl std::error::Error for Nsec3SaltError {}
1424
1425//------------ OwnerHashError ------------------------------------------------
1426
1427/// A byte sequence does not represent a valid owner hash.
1428///
1429/// This can only mean that the sequence is longer than 255 bytes.
1430#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1431pub struct OwnerHashError;
1432
1433impl fmt::Display for OwnerHashError {
1434    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1435        f.write_str("illegal owner name hash")
1436    }
1437}
1438
1439#[cfg(feature = "std")]
1440impl std::error::Error for OwnerHashError {}
1441
1442//============ Testing ======================================================
1443
1444#[cfg(test)]
1445#[cfg(all(feature = "std", feature = "bytes"))]
1446mod test {
1447    use super::super::dnssec::RtypeBitmapBuilder;
1448    use super::*;
1449    use crate::base::rdata::test::{
1450        test_compose_parse, test_rdlen, test_scan,
1451    };
1452    use std::vec::Vec;
1453
1454    #[test]
1455    #[allow(clippy::redundant_closure)] // lifetimes ...
1456    fn nsec3_compose_parse_scan() {
1457        let mut rtype = RtypeBitmapBuilder::new_vec();
1458        rtype.add(Rtype::A).unwrap();
1459        rtype.add(Rtype::Srv).unwrap();
1460        let rdata = Nsec3::new(
1461            Nsec3HashAlg::Sha1,
1462            10,
1463            11,
1464            Nsec3Salt::from_octets(Vec::from("bar")).unwrap(),
1465            OwnerHash::from_octets(Vec::from("foo")).unwrap(),
1466            rtype.finalize(),
1467        );
1468        test_rdlen(&rdata);
1469        test_compose_parse(&rdata, |parser| Nsec3::parse(parser));
1470        test_scan(
1471            &["1", "10", "11", "626172", "CPNMU", "A", "SRV"],
1472            Nsec3::scan,
1473            &rdata,
1474        );
1475    }
1476
1477    #[test]
1478    #[allow(clippy::redundant_closure)] // lifetimes ...
1479    fn nsec3param_compose_parse_scan() {
1480        let rdata = Nsec3param::new(
1481            Nsec3HashAlg::Sha1,
1482            10,
1483            11,
1484            Nsec3Salt::from_octets(Vec::from("bar")).unwrap(),
1485        );
1486        test_rdlen(&rdata);
1487        test_compose_parse(&rdata, |parser| Nsec3param::parse(parser));
1488        test_scan(&["1", "10", "11", "626172"], Nsec3param::scan, &rdata);
1489    }
1490}