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