1use 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#[derive(Clone)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub struct Tsig<Octs, Name> {
34 algorithm: Name,
36
37 time_signed: Time48,
41
42 fudge: u16,
44
45 #[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_id: u16,
64
65 error: TsigRcode,
67
68 #[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 pub(crate) const RTYPE: Rtype = Rtype::TSIG;
90}
91
92impl<O, N> Tsig<O, N> {
93 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 + 2 + 2 + 2 + 2 + 2 + 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 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 pub fn algorithm(&self) -> &N {
169 &self.algorithm
170 }
171
172 pub fn time_signed(&self) -> Time48 {
177 self.time_signed
178 }
179
180 pub fn fudge(&self) -> u16 {
185 self.fudge
186 }
187
188 pub fn mac(&self) -> &O {
190 &self.mac
191 }
192
193 pub fn mac_slice(&self) -> &[u8]
195 where
196 O: AsRef<[u8]>,
197 {
198 self.mac.as_ref()
199 }
200
201 pub fn into_mac(self) -> O {
203 self.mac
204 }
205
206 pub fn original_id(&self) -> u16 {
211 self.original_id
212 }
213
214 pub fn error(&self) -> TsigRcode {
216 self.error
217 }
218
219 pub fn other(&self) -> &O {
224 &self.other
225 }
226
227 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 pub fn is_valid_at(&self, now: Time48) -> bool {
250 now.eq_fudged(self.time_signed, self.fudge.into())
251 }
252
253 #[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
333impl<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
376impl<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
398impl<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
510impl<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
524impl<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 + 2 + 2 + 2 + 2 + 2 + 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
591impl<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
621impl<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#[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 #[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 #[must_use]
675 pub fn from_u64(value: u64) -> Self {
676 assert!(value & 0xFFFF_0000_0000_0000 == 0);
677 Time48(value)
678 }
679
680 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 #[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 #[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
740impl From<Time48> for u64 {
743 fn from(value: Time48) -> u64 {
744 value.0
745 }
746}
747
748impl fmt::Display for Time48 {
751 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752 self.0.fmt(f)
753 }
754}
755
756#[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)] 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}