domain/rdata/
tsig.rs

1//! Record data from [RFC 2845]: TSIG records.
2//!
3//! This RFC defines the TSIG record type used for signing DNS messages.
4//!
5//! [RFC 2845]: https://tools.ietf.org/html/rfc2845
6
7use core::cmp::Ordering;
8use core::{fmt, hash};
9
10#[cfg(all(feature = "std", not(test)))]
11use std::time::SystemTime;
12
13#[cfg(all(feature = "std", test))]
14use mock_instant::thread_local::SystemTime;
15use octseq::builder::OctetsBuilder;
16use octseq::octets::{Octets, OctetsFrom, OctetsInto};
17use octseq::parse::Parser;
18
19use crate::base::cmp::CanonicalOrd;
20use crate::base::iana::{Rtype, TsigRcode};
21use crate::base::name::{FlattenInto, ParsedName, ToName};
22use crate::base::rdata::{
23    ComposeRecordData, LongRecordData, ParseRecordData, RecordData,
24};
25use crate::base::wire::{Compose, Composer, Parse, ParseError};
26use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt};
27use crate::utils::base64;
28
29//------------ Tsig ----------------------------------------------------------
30
31#[derive(Clone)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub struct Tsig<Octs, Name> {
34    /// The signature algorithm as a domain name.
35    algorithm: Name,
36
37    /// The Unix epoch time at which the signature was created.
38    ///
39    /// Note that this is an unsigned 48 bit value in wire format.
40    time_signed: Time48,
41
42    /// Seconds of error permitted in time signed.
43    fudge: u16,
44
45    /// MAC.
46    ///
47    /// In wire format, consists of a unsigned 16 bit integer containing the
48    /// length followed by that many octets of actual MAC.
49    #[cfg_attr(
50        feature = "serde",
51        serde(
52            serialize_with = "octseq::serde::SerializeOctets::serialize_octets",
53            deserialize_with = "octseq::serde::DeserializeOctets::deserialize_octets",
54            bound(
55                serialize = "Octs: octseq::serde::SerializeOctets",
56                deserialize = "Octs: octseq::serde::DeserializeOctets<'de>",
57            )
58        )
59    )]
60    mac: Octs,
61
62    /// Original message ID.
63    original_id: u16,
64
65    /// TSIG response code.
66    error: TsigRcode,
67
68    /// Other.
69    ///
70    /// This is normally empty unless a BADTIME error happened. In wire
71    /// format, it is encoded as a unsigned 16 bit integer followed by that
72    /// many octets.
73    #[cfg_attr(
74        feature = "serde",
75        serde(
76            serialize_with = "octseq::serde::SerializeOctets::serialize_octets",
77            deserialize_with = "octseq::serde::DeserializeOctets::deserialize_octets",
78            bound(
79                serialize = "Octs: octseq::serde::SerializeOctets",
80                deserialize = "Octs: octseq::serde::DeserializeOctets<'de>",
81            )
82        )
83    )]
84    other: Octs,
85}
86
87impl Tsig<(), ()> {
88    /// The rtype of this record data type.
89    pub(crate) const RTYPE: Rtype = Rtype::TSIG;
90}
91
92impl<O, N> Tsig<O, N> {
93    /// Creates new TSIG record data from its components.
94    ///
95    /// See the access methods for an explanation of these components. The
96    /// function will return an error if the wire format length of the record
97    /// would exceed 65,535 octets.
98    pub fn new(
99        algorithm: N,
100        time_signed: Time48,
101        fudge: u16,
102        mac: O,
103        original_id: u16,
104        error: TsigRcode,
105        other: O,
106    ) -> Result<Self, LongRecordData>
107    where
108        O: AsRef<[u8]>,
109        N: ToName,
110    {
111        LongRecordData::check_len(
112            6 // time_signed
113            + 2 // fudge
114            + 2 // MAC length
115            + 2 // original ID
116            + 2 // error
117            + 2 // other length
118            + usize::from(algorithm.compose_len()).checked_add(
119                mac.as_ref().len()
120            ).expect("long MAC").checked_add(
121                other.as_ref().len()
122            ).expect("long TSIG"),
123        )?;
124        Ok(unsafe {
125            Tsig::new_unchecked(
126                algorithm,
127                time_signed,
128                fudge,
129                mac,
130                original_id,
131                error,
132                other,
133            )
134        })
135    }
136
137    /// Creates new TSIG record data without checking.
138    ///
139    /// # Safety
140    ///
141    /// The caller needs to ensure that the wire format length of the
142    /// created record will not exceed 65,535 octets.
143    pub unsafe fn new_unchecked(
144        algorithm: N,
145        time_signed: Time48,
146        fudge: u16,
147        mac: O,
148        original_id: u16,
149        error: TsigRcode,
150        other: O,
151    ) -> Self {
152        Tsig {
153            algorithm,
154            time_signed,
155            fudge,
156            mac,
157            original_id,
158            error,
159            other,
160        }
161    }
162
163    /// Returns a reference to the algorithm name.
164    ///
165    /// TSIG encodes the algorithm used for keys and signatures as a domain
166    /// name. It does, however, only use the format. No structure is used at
167    /// all.
168    pub fn algorithm(&self) -> &N {
169        &self.algorithm
170    }
171
172    /// Returns the Unix time when the signature is created.
173    ///
174    /// Despite its type, this is actually a 48 bit number. The upper 16 bits
175    /// will never be set.
176    pub fn time_signed(&self) -> Time48 {
177        self.time_signed
178    }
179
180    /// Return the number of seconds of offset from signing time permitted.
181    ///
182    /// When a signature is checked, the local system time needs to be within
183    /// this many seconds from `time_signed` to be accepted.
184    pub fn fudge(&self) -> u16 {
185        self.fudge
186    }
187
188    /// Returns a reference to the bytes value containing the MAC.
189    pub fn mac(&self) -> &O {
190        &self.mac
191    }
192
193    /// Returns an octet slice containing the MAC.
194    pub fn mac_slice(&self) -> &[u8]
195    where
196        O: AsRef<[u8]>,
197    {
198        self.mac.as_ref()
199    }
200
201    /// Converts the record data into the MAC.
202    pub fn into_mac(self) -> O {
203        self.mac
204    }
205
206    /// Returns the original message ID.
207    ///
208    /// Since the message ID is part of the signature generation but may be
209    /// changed for a forwarded message, it is included in the TSIG record.
210    pub fn original_id(&self) -> u16 {
211        self.original_id
212    }
213
214    /// Returns the TSIG error.
215    pub fn error(&self) -> TsigRcode {
216        self.error
217    }
218
219    /// Returns a reference to the other bytes.
220    ///
221    /// This field is only used for BADTIME errors to return the server time.
222    /// Otherwise it is empty.
223    pub fn other(&self) -> &O {
224        &self.other
225    }
226
227    /// Returns the other bytes as the server time.
228    ///
229    /// If the other bytes field is exactly 6 bytes long, this methods
230    /// returns it as a `u64` representation of the Unix time contained.
231    pub fn other_time(&self) -> Option<Time48>
232    where
233        O: AsRef<[u8]>,
234    {
235        if self.other.as_ref().len() == 6 {
236            Some(Time48::from_slice(self.other.as_ref()))
237        } else {
238            None
239        }
240    }
241
242    /// Returns whether the record is valid at the given time.
243    ///
244    /// The method checks whether the given time is within [`fudge`]
245    /// seconds of the [`time_signed`].
246    ///
247    /// [`fudge`]: #method.fudge
248    /// [`time_signed`]: #method.time_signed
249    pub fn is_valid_at(&self, now: Time48) -> bool {
250        now.eq_fudged(self.time_signed, self.fudge.into())
251    }
252
253    /// Returns whether the record is valid right now.
254    ///
255    /// The method checks whether the current system time is within [`fudge`]
256    /// seconds of the [`time_signed`].
257    ///
258    /// [`fudge`]: #method.fudge
259    /// [`time_signed`]: #method.time_signed
260    #[cfg(feature = "std")]
261    pub fn is_valid_now(&self) -> bool {
262        self.is_valid_at(Time48::now())
263    }
264
265    pub(super) fn convert_octets<TOcts, TName>(
266        self,
267    ) -> Result<Tsig<TOcts, TName>, TOcts::Error>
268    where
269        TOcts: OctetsFrom<O>,
270        TName: OctetsFrom<N, Error = TOcts::Error>,
271    {
272        Ok(unsafe {
273            Tsig::new_unchecked(
274                self.algorithm.try_octets_into()?,
275                self.time_signed,
276                self.fudge,
277                self.mac.try_octets_into()?,
278                self.original_id,
279                self.error,
280                self.other.try_octets_into()?,
281            )
282        })
283    }
284
285    pub(super) fn flatten<TOcts, TName>(
286        self,
287    ) -> Result<Tsig<TOcts, TName>, TOcts::Error>
288    where
289        TOcts: OctetsFrom<O>,
290        N: FlattenInto<TName, AppendError = TOcts::Error>,
291    {
292        Ok(unsafe {
293            Tsig::new_unchecked(
294                self.algorithm.try_flatten_into()?,
295                self.time_signed,
296                self.fudge,
297                self.mac.try_octets_into()?,
298                self.original_id,
299                self.error,
300                self.other.try_octets_into()?,
301            )
302        })
303    }
304}
305
306impl<Octs> Tsig<Octs, ParsedName<Octs>> {
307    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized + 'a>(
308        parser: &mut Parser<'a, Src>,
309    ) -> Result<Self, ParseError> {
310        let algorithm = ParsedName::parse(parser)?;
311        let time_signed = Time48::parse(parser)?;
312        let fudge = u16::parse(parser)?;
313        let mac_size = u16::parse(parser)?;
314        let mac = parser.parse_octets(mac_size as usize)?;
315        let original_id = u16::parse(parser)?;
316        let error = TsigRcode::parse(parser)?;
317        let other_len = u16::parse(parser)?;
318        let other = parser.parse_octets(other_len as usize)?;
319        Ok(unsafe {
320            Tsig::new_unchecked(
321                algorithm,
322                time_signed,
323                fudge,
324                mac,
325                original_id,
326                error,
327                other,
328            )
329        })
330    }
331}
332
333//--- OctetsFrom and FlattenInto
334
335impl<Octs, SrcOctets, Name, SrcName> OctetsFrom<Tsig<SrcOctets, SrcName>>
336    for Tsig<Octs, Name>
337where
338    Octs: OctetsFrom<SrcOctets>,
339    Name: OctetsFrom<SrcName>,
340    Octs::Error: From<Name::Error>,
341{
342    type Error = Octs::Error;
343
344    fn try_octets_from(
345        source: Tsig<SrcOctets, SrcName>,
346    ) -> Result<Self, Self::Error> {
347        Ok(unsafe {
348            Tsig::new_unchecked(
349                Name::try_octets_from(source.algorithm)?,
350                source.time_signed,
351                source.fudge,
352                Octs::try_octets_from(source.mac)?,
353                source.original_id,
354                source.error,
355                Octs::try_octets_from(source.other)?,
356            )
357        })
358    }
359}
360
361impl<Octs, TOcts, Name, TName> FlattenInto<Tsig<TOcts, TName>>
362    for Tsig<Octs, Name>
363where
364    TOcts: OctetsFrom<Octs>,
365    Name: FlattenInto<TName, AppendError = TOcts::Error>,
366{
367    type AppendError = TOcts::Error;
368
369    fn try_flatten_into(
370        self,
371    ) -> Result<Tsig<TOcts, TName>, Self::AppendError> {
372        self.flatten()
373    }
374}
375
376//--- PartialEq and Eq
377
378impl<O, OO, N, NN> PartialEq<Tsig<OO, NN>> for Tsig<O, N>
379where
380    O: AsRef<[u8]>,
381    OO: AsRef<[u8]>,
382    N: ToName,
383    NN: ToName,
384{
385    fn eq(&self, other: &Tsig<OO, NN>) -> bool {
386        self.algorithm.name_eq(&other.algorithm)
387            && self.time_signed == other.time_signed
388            && self.fudge == other.fudge
389            && self.mac.as_ref().eq(other.mac.as_ref())
390            && self.original_id == other.original_id
391            && self.error == other.error
392            && self.other.as_ref().eq(other.other.as_ref())
393    }
394}
395
396impl<O: AsRef<[u8]>, N: ToName> Eq for Tsig<O, N> {}
397
398//--- PartialOrd, Ord, and CanonicalOrd
399
400impl<O, OO, N, NN> PartialOrd<Tsig<OO, NN>> for Tsig<O, N>
401where
402    O: AsRef<[u8]>,
403    OO: AsRef<[u8]>,
404    N: ToName,
405    NN: ToName,
406{
407    fn partial_cmp(&self, other: &Tsig<OO, NN>) -> Option<Ordering> {
408        match self.algorithm.name_cmp(&other.algorithm) {
409            Ordering::Equal => {}
410            other => return Some(other),
411        }
412        match self.time_signed.partial_cmp(&other.time_signed) {
413            Some(Ordering::Equal) => {}
414            other => return other,
415        }
416        match self.fudge.partial_cmp(&other.fudge) {
417            Some(Ordering::Equal) => {}
418            other => return other,
419        }
420        match self.mac.as_ref().partial_cmp(other.mac.as_ref()) {
421            Some(Ordering::Equal) => {}
422            other => return other,
423        }
424        match self.original_id.partial_cmp(&other.original_id) {
425            Some(Ordering::Equal) => {}
426            other => return other,
427        }
428        match self.error.partial_cmp(&other.error) {
429            Some(Ordering::Equal) => {}
430            other => return other,
431        }
432        self.other.as_ref().partial_cmp(other.other.as_ref())
433    }
434}
435
436impl<O: AsRef<[u8]>, N: ToName> Ord for Tsig<O, N> {
437    fn cmp(&self, other: &Self) -> Ordering {
438        match self.algorithm.name_cmp(&other.algorithm) {
439            Ordering::Equal => {}
440            other => return other,
441        }
442        match self.time_signed.cmp(&other.time_signed) {
443            Ordering::Equal => {}
444            other => return other,
445        }
446        match self.fudge.cmp(&other.fudge) {
447            Ordering::Equal => {}
448            other => return other,
449        }
450        match self.mac.as_ref().cmp(other.mac.as_ref()) {
451            Ordering::Equal => {}
452            other => return other,
453        }
454        match self.original_id.cmp(&other.original_id) {
455            Ordering::Equal => {}
456            other => return other,
457        }
458        match self.error.cmp(&other.error) {
459            Ordering::Equal => {}
460            other => return other,
461        }
462        self.other.as_ref().cmp(other.other.as_ref())
463    }
464}
465
466impl<O, OO, N, NN> CanonicalOrd<Tsig<OO, NN>> for Tsig<O, N>
467where
468    O: AsRef<[u8]>,
469    OO: AsRef<[u8]>,
470    N: ToName,
471    NN: ToName,
472{
473    fn canonical_cmp(&self, other: &Tsig<OO, NN>) -> Ordering {
474        match self.algorithm.composed_cmp(&other.algorithm) {
475            Ordering::Equal => {}
476            other => return other,
477        }
478        match self.time_signed.cmp(&other.time_signed) {
479            Ordering::Equal => {}
480            other => return other,
481        }
482        match self.fudge.cmp(&other.fudge) {
483            Ordering::Equal => {}
484            other => return other,
485        }
486        match self.mac.as_ref().len().cmp(&other.mac.as_ref().len()) {
487            Ordering::Equal => {}
488            other => return other,
489        }
490        match self.mac.as_ref().cmp(other.mac.as_ref()) {
491            Ordering::Equal => {}
492            other => return other,
493        }
494        match self.original_id.cmp(&other.original_id) {
495            Ordering::Equal => {}
496            other => return other,
497        }
498        match self.error.cmp(&other.error) {
499            Ordering::Equal => {}
500            other => return other,
501        }
502        match self.other.as_ref().len().cmp(&other.other.as_ref().len()) {
503            Ordering::Equal => {}
504            other => return other,
505        }
506        self.other.as_ref().cmp(other.other.as_ref())
507    }
508}
509
510//--- Hash
511
512impl<O: AsRef<[u8]>, N: hash::Hash> hash::Hash for Tsig<O, N> {
513    fn hash<H: hash::Hasher>(&self, state: &mut H) {
514        self.algorithm.hash(state);
515        self.time_signed.hash(state);
516        self.fudge.hash(state);
517        self.mac.as_ref().hash(state);
518        self.original_id.hash(state);
519        self.error.hash(state);
520        self.other.as_ref().hash(state);
521    }
522}
523
524//--- RecordData, ParseRecordData, ComposeRecordData
525
526impl<O, N> RecordData for Tsig<O, N> {
527    fn rtype(&self) -> Rtype {
528        Tsig::RTYPE
529    }
530}
531
532impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
533    for Tsig<Octs::Range<'a>, ParsedName<Octs::Range<'a>>>
534{
535    fn parse_rdata(
536        rtype: Rtype,
537        parser: &mut Parser<'a, Octs>,
538    ) -> Result<Option<Self>, ParseError> {
539        if rtype == Tsig::RTYPE {
540            Self::parse(parser).map(Some)
541        } else {
542            Ok(None)
543        }
544    }
545}
546
547impl<Octs: AsRef<[u8]>, Name: ToName> ComposeRecordData for Tsig<Octs, Name> {
548    fn rdlen(&self, _compress: bool) -> Option<u16> {
549        Some(
550            6 // time_signed
551            + 2 // fudge
552            + 2 // MAC length
553            + 2 // original ID
554            + 2 // error
555            + 2 // other length
556            + self.algorithm.compose_len().checked_add(
557                u16::try_from(self.mac.as_ref().len()).expect("long MAC")
558            ).expect("long MAC").checked_add(
559                u16::try_from(self.other.as_ref().len()).expect("long TSIG")
560            ).expect("long TSIG"),
561        )
562    }
563
564    fn compose_rdata<Target: Composer + ?Sized>(
565        &self,
566        target: &mut Target,
567    ) -> Result<(), Target::AppendError> {
568        self.algorithm.compose(target)?;
569        self.time_signed.compose(target)?;
570        self.fudge.compose(target)?;
571        u16::try_from(self.mac.as_ref().len())
572            .expect("long MAC")
573            .compose(target)?;
574        target.append_slice(self.mac.as_ref())?;
575        self.original_id.compose(target)?;
576        self.error.compose(target)?;
577        u16::try_from(self.other.as_ref().len())
578            .expect("long MAC")
579            .compose(target)?;
580        target.append_slice(self.other.as_ref())
581    }
582
583    fn compose_canonical_rdata<Target: Composer + ?Sized>(
584        &self,
585        target: &mut Target,
586    ) -> Result<(), Target::AppendError> {
587        self.compose_rdata(target)
588    }
589}
590
591//--- Display and Debug
592
593impl<O: AsRef<[u8]>, N: fmt::Display> fmt::Display for Tsig<O, N> {
594    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
595        write!(
596            f,
597            "{}. {} {} ",
598            self.algorithm, self.time_signed, self.fudge
599        )?;
600        base64::display(&self.mac, f)?;
601        write!(f, " {} {} \"", self.original_id, self.error)?;
602        base64::display(&self.other, f)?;
603        write!(f, "\"")
604    }
605}
606
607impl<O: AsRef<[u8]>, N: fmt::Debug> fmt::Debug for Tsig<O, N> {
608    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609        f.debug_struct("Tsig")
610            .field("algorithm", &self.algorithm)
611            .field("time_signed", &self.time_signed)
612            .field("fudge", &self.fudge)
613            .field("mac", &self.mac.as_ref())
614            .field("original_id", &self.original_id)
615            .field("error", &self.error)
616            .field("other", &self.other.as_ref())
617            .finish()
618    }
619}
620
621//--- ZonefileFmt
622
623impl<O: AsRef<[u8]>, N: ToName> ZonefileFmt for Tsig<O, N> {
624    fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
625        p.block(|p| {
626            p.write_token(self.algorithm.fmt_with_dot())?;
627            p.write_comment("algorithm")?;
628            p.write_token(self.time_signed)?;
629            p.write_comment("time signed")?;
630            p.write_token(self.fudge)?;
631            p.write_comment("fudge")?;
632            p.write_token(base64::encode_display(&self.mac))?;
633            p.write_comment("mac")?;
634            p.write_token(self.original_id)?;
635            p.write_comment("original id")?;
636            p.write_token(self.error)?;
637            p.write_comment("error")?;
638            p.write_token(format_args!(
639                "\"{}\"",
640                base64::encode_display(&self.other)
641            ))
642        })
643    }
644}
645
646//------------ Time48 --------------------------------------------------------
647
648/// A 48-bit Unix timestamp.
649#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
650#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
651pub struct Time48(u64);
652
653impl Time48 {
654    /// Returns the timestamp of the current moment.
655    ///
656    /// The function will panic if for whatever reason the current moment is
657    /// too far in the future to fit into this type. For a correctly set
658    /// clock, this will happen in December 8,921,556, so should be fine.
659    #[cfg(feature = "std")]
660    #[must_use]
661    pub fn now() -> Time48 {
662        Self::from_u64(
663            SystemTime::now()
664                .duration_since(SystemTime::UNIX_EPOCH)
665                .expect("system time before Unix epoch")
666                .as_secs(),
667        )
668    }
669
670    /// Creates a value from a 64 bit integer.
671    ///
672    /// The upper 16 bits of the arument must be zero or else this function
673    /// panics. This is also why we don’t implement `From`.
674    #[must_use]
675    pub fn from_u64(value: u64) -> Self {
676        assert!(value & 0xFFFF_0000_0000_0000 == 0);
677        Time48(value)
678    }
679
680    /// Creates a value from an octet slice.
681    ///
682    /// This slice should contain the octets of the value in network byte
683    /// order.
684    ///
685    /// # Panics
686    ///
687    /// The function panics if the slice is shorter than 6 octets.
688    fn from_slice(slice: &[u8]) -> Self {
689        Time48(
690            (u64::from(slice[0]) << 40)
691                | (u64::from(slice[1]) << 32)
692                | (u64::from(slice[2]) << 24)
693                | (u64::from(slice[3]) << 16)
694                | (u64::from(slice[4]) << 8)
695                | (u64::from(slice[5])),
696        )
697    }
698
699    /// Converts a value into its wire format.
700    ///
701    /// Returns the octets of the encoded value in network byte order.
702    #[must_use]
703    pub fn into_octets(self) -> [u8; 6] {
704        let mut res = [0u8; 6];
705        res[0] = (self.0 >> 40) as u8;
706        res[1] = (self.0 >> 32) as u8;
707        res[2] = (self.0 >> 24) as u8;
708        res[3] = (self.0 >> 16) as u8;
709        res[4] = (self.0 >> 8) as u8;
710        res[5] = self.0 as u8;
711        res
712    }
713
714    /// Returns whether the time is within a given period.
715    ///
716    /// Returns `true` iff `other` is at most `fudge` seconds before or after
717    /// this value’s time.
718    #[must_use]
719    pub fn eq_fudged(self, other: Self, fudge: u64) -> bool {
720        self.0.saturating_sub(fudge) <= other.0
721            && self.0.saturating_add(fudge) >= other.0
722    }
723
724    pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
725        parser: &mut Parser<'_, Octs>,
726    ) -> Result<Self, ParseError> {
727        let mut buf = [0u8; 6];
728        parser.parse_buf(&mut buf)?;
729        Ok(Time48::from_slice(&buf))
730    }
731
732    pub fn compose<Target: OctetsBuilder + ?Sized>(
733        &self,
734        target: &mut Target,
735    ) -> Result<(), Target::AppendError> {
736        target.append_slice(&self.into_octets())
737    }
738}
739
740//--- From
741
742impl From<Time48> for u64 {
743    fn from(value: Time48) -> u64 {
744        value.0
745    }
746}
747
748//--- Display
749
750impl fmt::Display for Time48 {
751    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752        self.0.fmt(f)
753    }
754}
755
756//============ Testing =======================================================
757
758#[cfg(test)]
759#[cfg(all(feature = "std", feature = "bytes"))]
760mod test {
761    use super::*;
762    use crate::base::name::Name;
763    use crate::base::rdata::test::{test_compose_parse, test_rdlen};
764    use core::str::FromStr;
765    use std::vec::Vec;
766
767    #[test]
768    #[allow(clippy::redundant_closure)] // lifetimes ...
769    fn tsig_compose_parse_scan() {
770        let rdata = Tsig::new(
771            Name::<Vec<u8>>::from_str("key.example.com.").unwrap(),
772            Time48::now(),
773            12,
774            "foo",
775            13,
776            TsigRcode::BADCOOKIE,
777            "",
778        )
779        .unwrap();
780        test_rdlen(&rdata);
781        test_compose_parse(&rdata, |parser| Tsig::parse(parser));
782    }
783}