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::utils::base64;
27
28#[derive(Clone)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Tsig<Octs, Name> {
33 algorithm: Name,
35
36 time_signed: Time48,
40
41 fudge: u16,
43
44 #[cfg_attr(
49 feature = "serde",
50 serde(
51 serialize_with = "octseq::serde::SerializeOctets::serialize_octets",
52 deserialize_with = "octseq::serde::DeserializeOctets::deserialize_octets",
53 bound(
54 serialize = "Octs: octseq::serde::SerializeOctets",
55 deserialize = "Octs: octseq::serde::DeserializeOctets<'de>",
56 )
57 )
58 )]
59 mac: Octs,
60
61 original_id: u16,
63
64 error: TsigRcode,
66
67 #[cfg_attr(
73 feature = "serde",
74 serde(
75 serialize_with = "octseq::serde::SerializeOctets::serialize_octets",
76 deserialize_with = "octseq::serde::DeserializeOctets::deserialize_octets",
77 bound(
78 serialize = "Octs: octseq::serde::SerializeOctets",
79 deserialize = "Octs: octseq::serde::DeserializeOctets<'de>",
80 )
81 )
82 )]
83 other: Octs,
84}
85
86impl Tsig<(), ()> {
87 pub(crate) const RTYPE: Rtype = Rtype::TSIG;
89}
90
91impl<O, N> Tsig<O, N> {
92 pub fn new(
98 algorithm: N,
99 time_signed: Time48,
100 fudge: u16,
101 mac: O,
102 original_id: u16,
103 error: TsigRcode,
104 other: O,
105 ) -> Result<Self, LongRecordData>
106 where O: AsRef<[u8]>, N: ToName {
107 LongRecordData::check_len(
108 6 + 2 + 2 + 2 + 2 + 2 + usize::from(algorithm.compose_len()).checked_add(
115 mac.as_ref().len()
116 ).expect("long MAC").checked_add(
117 other.as_ref().len()
118 ).expect("long TSIG")
119 )?;
120 Ok(unsafe {
121 Tsig::new_unchecked(
122 algorithm, time_signed, fudge, mac, original_id, error, other,
123 )
124 })
125 }
126
127 pub unsafe fn new_unchecked(
134 algorithm: N,
135 time_signed: Time48,
136 fudge: u16,
137 mac: O,
138 original_id: u16,
139 error: TsigRcode,
140 other: O,
141 ) -> Self {
142 Tsig {
143 algorithm,
144 time_signed,
145 fudge,
146 mac,
147 original_id,
148 error,
149 other,
150 }
151 }
152
153 pub fn algorithm(&self) -> &N {
159 &self.algorithm
160 }
161
162 pub fn time_signed(&self) -> Time48 {
167 self.time_signed
168 }
169
170 pub fn fudge(&self) -> u16 {
175 self.fudge
176 }
177
178 pub fn mac(&self) -> &O {
180 &self.mac
181 }
182
183 pub fn mac_slice(&self) -> &[u8]
185 where
186 O: AsRef<[u8]>,
187 {
188 self.mac.as_ref()
189 }
190
191 pub fn into_mac(self) -> O {
193 self.mac
194 }
195
196 pub fn original_id(&self) -> u16 {
201 self.original_id
202 }
203
204 pub fn error(&self) -> TsigRcode {
206 self.error
207 }
208
209 pub fn other(&self) -> &O {
214 &self.other
215 }
216
217 pub fn other_time(&self) -> Option<Time48>
222 where
223 O: AsRef<[u8]>,
224 {
225 if self.other.as_ref().len() == 6 {
226 Some(Time48::from_slice(self.other.as_ref()))
227 } else {
228 None
229 }
230 }
231
232 pub fn is_valid_at(&self, now: Time48) -> bool {
240 now.eq_fudged(self.time_signed, self.fudge.into())
241 }
242
243 #[cfg(feature = "std")]
251 pub fn is_valid_now(&self) -> bool {
252 self.is_valid_at(Time48::now())
253 }
254
255 pub(super) fn convert_octets<TOcts, TName>(
256 self,
257 ) -> Result<Tsig<TOcts, TName>, TOcts::Error>
258 where
259 TOcts: OctetsFrom<O>,
260 TName: OctetsFrom<N, Error = TOcts::Error>,
261 {
262 Ok(unsafe {
263 Tsig::new_unchecked(
264 self.algorithm.try_octets_into()?,
265 self.time_signed,
266 self.fudge,
267 self.mac.try_octets_into()?,
268 self.original_id,
269 self.error,
270 self.other.try_octets_into()?,
271 )
272 })
273 }
274
275 pub(super) fn flatten<TOcts, TName>(
276 self,
277 ) -> Result<Tsig<TOcts, TName>, TOcts::Error>
278 where
279 TOcts: OctetsFrom<O>,
280 N: FlattenInto<TName, AppendError = TOcts::Error>,
281 {
282 Ok(unsafe {
283 Tsig::new_unchecked(
284 self.algorithm.try_flatten_into()?,
285 self.time_signed,
286 self.fudge,
287 self.mac.try_octets_into()?,
288 self.original_id,
289 self.error,
290 self.other.try_octets_into()?,
291 )
292 })
293 }
294}
295
296impl<Octs> Tsig<Octs, ParsedName<Octs>> {
297 pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized + 'a>(
298 parser: &mut Parser<'a, Src>,
299 ) -> Result<Self, ParseError> {
300 let algorithm = ParsedName::parse(parser)?;
301 let time_signed = Time48::parse(parser)?;
302 let fudge = u16::parse(parser)?;
303 let mac_size = u16::parse(parser)?;
304 let mac = parser.parse_octets(mac_size as usize)?;
305 let original_id = u16::parse(parser)?;
306 let error = TsigRcode::parse(parser)?;
307 let other_len = u16::parse(parser)?;
308 let other = parser.parse_octets(other_len as usize)?;
309 Ok(unsafe {
310 Tsig::new_unchecked(
311 algorithm, time_signed, fudge, mac, original_id, error, other,
312 )
313 })
314 }
315}
316
317impl<Octs, SrcOctets, Name, SrcName> OctetsFrom<Tsig<SrcOctets, SrcName>>
320 for Tsig<Octs, Name>
321where
322 Octs: OctetsFrom<SrcOctets>,
323 Name: OctetsFrom<SrcName>,
324 Octs::Error: From<Name::Error>,
325{
326 type Error = Octs::Error;
327
328 fn try_octets_from(
329 source: Tsig<SrcOctets, SrcName>,
330 ) -> Result<Self, Self::Error> {
331 Ok(unsafe {
332 Tsig::new_unchecked(
333 Name::try_octets_from(source.algorithm)?,
334 source.time_signed,
335 source.fudge,
336 Octs::try_octets_from(source.mac)?,
337 source.original_id,
338 source.error,
339 Octs::try_octets_from(source.other)?,
340 )
341 })
342 }
343}
344
345impl<Octs, TOcts, Name, TName> FlattenInto<Tsig<TOcts, TName>>
346 for Tsig<Octs, Name>
347where
348 TOcts: OctetsFrom<Octs>,
349 Name: FlattenInto<TName, AppendError = TOcts::Error>
350{
351 type AppendError = TOcts::Error;
352
353 fn try_flatten_into(
354 self
355 ) -> Result<Tsig<TOcts, TName>, Self::AppendError > {
356 self.flatten()
357 }
358}
359
360
361impl<O, OO, N, NN> PartialEq<Tsig<OO, NN>> for Tsig<O, N>
364where
365 O: AsRef<[u8]>,
366 OO: AsRef<[u8]>,
367 N: ToName,
368 NN: ToName,
369{
370 fn eq(&self, other: &Tsig<OO, NN>) -> bool {
371 self.algorithm.name_eq(&other.algorithm)
372 && self.time_signed == other.time_signed
373 && self.fudge == other.fudge
374 && self.mac.as_ref().eq(other.mac.as_ref())
375 && self.original_id == other.original_id
376 && self.error == other.error
377 && self.other.as_ref().eq(other.other.as_ref())
378 }
379}
380
381impl<O: AsRef<[u8]>, N: ToName> Eq for Tsig<O, N> {}
382
383impl<O, OO, N, NN> PartialOrd<Tsig<OO, NN>> for Tsig<O, N>
386where
387 O: AsRef<[u8]>,
388 OO: AsRef<[u8]>,
389 N: ToName,
390 NN: ToName,
391{
392 fn partial_cmp(&self, other: &Tsig<OO, NN>) -> Option<Ordering> {
393 match self.algorithm.name_cmp(&other.algorithm) {
394 Ordering::Equal => {}
395 other => return Some(other),
396 }
397 match self.time_signed.partial_cmp(&other.time_signed) {
398 Some(Ordering::Equal) => {}
399 other => return other,
400 }
401 match self.fudge.partial_cmp(&other.fudge) {
402 Some(Ordering::Equal) => {}
403 other => return other,
404 }
405 match self.mac.as_ref().partial_cmp(other.mac.as_ref()) {
406 Some(Ordering::Equal) => {}
407 other => return other,
408 }
409 match self.original_id.partial_cmp(&other.original_id) {
410 Some(Ordering::Equal) => {}
411 other => return other,
412 }
413 match self.error.partial_cmp(&other.error) {
414 Some(Ordering::Equal) => {}
415 other => return other,
416 }
417 self.other.as_ref().partial_cmp(other.other.as_ref())
418 }
419}
420
421impl<O: AsRef<[u8]>, N: ToName> Ord for Tsig<O, N> {
422 fn cmp(&self, other: &Self) -> Ordering {
423 match self.algorithm.name_cmp(&other.algorithm) {
424 Ordering::Equal => {}
425 other => return other,
426 }
427 match self.time_signed.cmp(&other.time_signed) {
428 Ordering::Equal => {}
429 other => return other,
430 }
431 match self.fudge.cmp(&other.fudge) {
432 Ordering::Equal => {}
433 other => return other,
434 }
435 match self.mac.as_ref().cmp(other.mac.as_ref()) {
436 Ordering::Equal => {}
437 other => return other,
438 }
439 match self.original_id.cmp(&other.original_id) {
440 Ordering::Equal => {}
441 other => return other,
442 }
443 match self.error.cmp(&other.error) {
444 Ordering::Equal => {}
445 other => return other,
446 }
447 self.other.as_ref().cmp(other.other.as_ref())
448 }
449}
450
451impl<O, OO, N, NN> CanonicalOrd<Tsig<OO, NN>> for Tsig<O, N>
452where
453 O: AsRef<[u8]>,
454 OO: AsRef<[u8]>,
455 N: ToName,
456 NN: ToName,
457{
458 fn canonical_cmp(&self, other: &Tsig<OO, NN>) -> Ordering {
459 match self.algorithm.composed_cmp(&other.algorithm) {
460 Ordering::Equal => {}
461 other => return other,
462 }
463 match self.time_signed.cmp(&other.time_signed) {
464 Ordering::Equal => {}
465 other => return other,
466 }
467 match self.fudge.cmp(&other.fudge) {
468 Ordering::Equal => {}
469 other => return other,
470 }
471 match self.mac.as_ref().len().cmp(&other.mac.as_ref().len()) {
472 Ordering::Equal => {}
473 other => return other,
474 }
475 match self.mac.as_ref().cmp(other.mac.as_ref()) {
476 Ordering::Equal => {}
477 other => return other,
478 }
479 match self.original_id.cmp(&other.original_id) {
480 Ordering::Equal => {}
481 other => return other,
482 }
483 match self.error.cmp(&other.error) {
484 Ordering::Equal => {}
485 other => return other,
486 }
487 match self.other.as_ref().len().cmp(&other.other.as_ref().len()) {
488 Ordering::Equal => {}
489 other => return other,
490 }
491 self.other.as_ref().cmp(other.other.as_ref())
492 }
493}
494
495impl<O: AsRef<[u8]>, N: hash::Hash> hash::Hash for Tsig<O, N> {
498 fn hash<H: hash::Hasher>(&self, state: &mut H) {
499 self.algorithm.hash(state);
500 self.time_signed.hash(state);
501 self.fudge.hash(state);
502 self.mac.as_ref().hash(state);
503 self.original_id.hash(state);
504 self.error.hash(state);
505 self.other.as_ref().hash(state);
506 }
507}
508
509impl<O, N> RecordData for Tsig<O, N> {
512 fn rtype(&self) -> Rtype {
513 Tsig::RTYPE
514 }
515}
516
517impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
518 for Tsig<Octs::Range<'a>, ParsedName<Octs::Range<'a>>>
519{
520 fn parse_rdata(
521 rtype: Rtype,
522 parser: &mut Parser<'a, Octs>,
523 ) -> Result<Option<Self>, ParseError> {
524 if rtype == Tsig::RTYPE {
525 Self::parse(parser).map(Some)
526 } else {
527 Ok(None)
528 }
529 }
530}
531
532impl<Octs: AsRef<[u8]>, Name: ToName> ComposeRecordData
533 for Tsig<Octs, Name>
534{
535 fn rdlen(&self, _compress: bool) -> Option<u16> {
536 Some(
537 6 + 2 + 2 + 2 + 2 + 2 + self.algorithm.compose_len().checked_add(
544 u16::try_from(self.mac.as_ref().len()).expect("long MAC")
545 ).expect("long MAC").checked_add(
546 u16::try_from(self.other.as_ref().len()).expect("long TSIG")
547 ).expect("long TSIG"),
548 )
549 }
550
551 fn compose_rdata<Target: Composer + ?Sized>(
552 &self,
553 target: &mut Target,
554 ) -> Result<(), Target::AppendError> {
555 self.algorithm.compose(target)?;
556 self.time_signed.compose(target)?;
557 self.fudge.compose(target)?;
558 u16::try_from(self.mac.as_ref().len())
559 .expect("long MAC")
560 .compose(target)?;
561 target.append_slice(self.mac.as_ref())?;
562 self.original_id.compose(target)?;
563 self.error.compose(target)?;
564 u16::try_from(self.other.as_ref().len())
565 .expect("long MAC")
566 .compose(target)?;
567 target.append_slice(self.other.as_ref())
568 }
569
570 fn compose_canonical_rdata<Target: Composer + ?Sized>(
571 &self,
572 target: &mut Target,
573 ) -> Result<(), Target::AppendError> {
574 self.compose_rdata(target)
575 }
576}
577
578impl<O: AsRef<[u8]>, N: fmt::Display> fmt::Display for Tsig<O, N> {
581 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
582 write!(
583 f,
584 "{}. {} {} ",
585 self.algorithm, self.time_signed, self.fudge
586 )?;
587 base64::display(&self.mac, f)?;
588 write!(f, " {} {} \"", self.original_id, self.error)?;
589 base64::display(&self.other, f)?;
590 write!(f, "\"")
591 }
592}
593
594impl<O: AsRef<[u8]>, N: fmt::Debug> fmt::Debug for Tsig<O, N> {
595 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
596 f.debug_struct("Tsig")
597 .field("algorithm", &self.algorithm)
598 .field("time_signed", &self.time_signed)
599 .field("fudge", &self.fudge)
600 .field("mac", &self.mac.as_ref())
601 .field("original_id", &self.original_id)
602 .field("error", &self.error)
603 .field("other", &self.other.as_ref())
604 .finish()
605 }
606}
607
608#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
612#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
613pub struct Time48(u64);
614
615impl Time48 {
616 #[cfg(feature = "std")]
622 #[must_use]
623 pub fn now() -> Time48 {
624 Self::from_u64(
625 SystemTime::now()
626 .duration_since(SystemTime::UNIX_EPOCH)
627 .expect("system time before Unix epoch")
628 .as_secs(),
629 )
630 }
631
632 #[must_use]
637 pub fn from_u64(value: u64) -> Self {
638 assert!(value & 0xFFFF_0000_0000_0000 == 0);
639 Time48(value)
640 }
641
642 fn from_slice(slice: &[u8]) -> Self {
651 Time48(
652 (u64::from(slice[0]) << 40)
653 | (u64::from(slice[1]) << 32)
654 | (u64::from(slice[2]) << 24)
655 | (u64::from(slice[3]) << 16)
656 | (u64::from(slice[4]) << 8)
657 | (u64::from(slice[5])),
658 )
659 }
660
661 #[must_use]
665 pub fn into_octets(self) -> [u8; 6] {
666 let mut res = [0u8; 6];
667 res[0] = (self.0 >> 40) as u8;
668 res[1] = (self.0 >> 32) as u8;
669 res[2] = (self.0 >> 24) as u8;
670 res[3] = (self.0 >> 16) as u8;
671 res[4] = (self.0 >> 8) as u8;
672 res[5] = self.0 as u8;
673 res
674 }
675
676 #[must_use]
681 pub fn eq_fudged(self, other: Self, fudge: u64) -> bool {
682 self.0.saturating_sub(fudge) <= other.0
683 && self.0.saturating_add(fudge) >= other.0
684 }
685
686 pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
687 parser: &mut Parser<Octs>,
688 ) -> Result<Self, ParseError> {
689 let mut buf = [0u8; 6];
690 parser.parse_buf(&mut buf)?;
691 Ok(Time48::from_slice(&buf))
692 }
693
694 pub fn compose<Target: OctetsBuilder + ?Sized>(
695 &self,
696 target: &mut Target,
697 ) -> Result<(), Target::AppendError> {
698 target.append_slice(&self.into_octets())
699 }
700}
701
702impl From<Time48> for u64 {
705 fn from(value: Time48) -> u64 {
706 value.0
707 }
708}
709
710impl fmt::Display for Time48 {
713 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
714 self.0.fmt(f)
715 }
716}
717
718#[cfg(test)]
721#[cfg(all(feature = "std", feature = "bytes"))]
722mod test {
723 use super::*;
724 use crate::base::name::Name;
725 use crate::base::rdata::test::{test_compose_parse, test_rdlen};
726 use core::str::FromStr;
727 use std::vec::Vec;
728
729 #[test]
730 #[allow(clippy::redundant_closure)] fn tsig_compose_parse_scan() {
732 let rdata = Tsig::new(
733 Name::<Vec<u8>>::from_str("key.example.com.").unwrap(),
734 Time48::now(),
735 12,
736 "foo",
737 13,
738 TsigRcode::BADCOOKIE,
739 "",
740 ).unwrap();
741 test_rdlen(&rdata);
742 test_compose_parse(&rdata, |parser| Tsig::parse(parser));
743 }
744}