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::zonefile_fmt::{self, Formatter, ZonefileFmt};
26use crate::base::wire::{Compose, Composer, Parse, ParseError};
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 O: AsRef<[u8]>, N: ToName {
108 LongRecordData::check_len(
109 6 + 2 + 2 + 2 + 2 + 2 + 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 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 pub fn algorithm(&self) -> &N {
160 &self.algorithm
161 }
162
163 pub fn time_signed(&self) -> Time48 {
168 self.time_signed
169 }
170
171 pub fn fudge(&self) -> u16 {
176 self.fudge
177 }
178
179 pub fn mac(&self) -> &O {
181 &self.mac
182 }
183
184 pub fn mac_slice(&self) -> &[u8]
186 where
187 O: AsRef<[u8]>,
188 {
189 self.mac.as_ref()
190 }
191
192 pub fn into_mac(self) -> O {
194 self.mac
195 }
196
197 pub fn original_id(&self) -> u16 {
202 self.original_id
203 }
204
205 pub fn error(&self) -> TsigRcode {
207 self.error
208 }
209
210 pub fn other(&self) -> &O {
215 &self.other
216 }
217
218 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 pub fn is_valid_at(&self, now: Time48) -> bool {
241 now.eq_fudged(self.time_signed, self.fudge.into())
242 }
243
244 #[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
318impl<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
362impl<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
384impl<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
496impl<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
510impl<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 + 2 + 2 + 2 + 2 + 2 + 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
579impl<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
609impl<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#[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 #[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 #[must_use]
663 pub fn from_u64(value: u64) -> Self {
664 assert!(value & 0xFFFF_0000_0000_0000 == 0);
665 Time48(value)
666 }
667
668 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 #[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 #[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
728impl From<Time48> for u64 {
731 fn from(value: Time48) -> u64 {
732 value.0
733 }
734}
735
736impl fmt::Display for Time48 {
739 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
740 self.0.fmt(f)
741 }
742}
743
744#[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)] 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}