1use crate::base::cmp::CanonicalOrd;
8use crate::base::iana::{Rtype, TsigRcode};
9use crate::base::name::{FlattenInto, ParsedDname, ToDname};
10use crate::base::rdata::{
11 ComposeRecordData, LongRecordData, ParseRecordData, RecordData
12};
13use crate::base::wire::{Compose, Composer, Parse, ParseError};
14use crate::utils::base64;
15use core::cmp::Ordering;
16use core::{fmt, hash};
17use octseq::builder::OctetsBuilder;
18use octseq::octets::{Octets, OctetsFrom, OctetsInto};
19use octseq::parse::Parser;
20#[cfg(feature = "std")]
21use std::time::SystemTime;
22
23#[derive(Clone)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct Tsig<Octs, Name> {
28 algorithm: Name,
30
31 time_signed: Time48,
35
36 fudge: u16,
38
39 #[cfg_attr(
44 feature = "serde",
45 serde(
46 serialize_with = "octseq::serde::SerializeOctets::serialize_octets",
47 deserialize_with = "octseq::serde::DeserializeOctets::deserialize_octets",
48 bound(
49 serialize = "Octs: octseq::serde::SerializeOctets",
50 deserialize = "Octs: octseq::serde::DeserializeOctets<'de>",
51 )
52 )
53 )]
54 mac: Octs,
55
56 original_id: u16,
58
59 error: TsigRcode,
61
62 #[cfg_attr(
68 feature = "serde",
69 serde(
70 serialize_with = "octseq::serde::SerializeOctets::serialize_octets",
71 deserialize_with = "octseq::serde::DeserializeOctets::deserialize_octets",
72 bound(
73 serialize = "Octs: octseq::serde::SerializeOctets",
74 deserialize = "Octs: octseq::serde::DeserializeOctets<'de>",
75 )
76 )
77 )]
78 other: Octs,
79}
80
81impl<O, N> Tsig<O, N> {
82 pub fn new(
88 algorithm: N,
89 time_signed: Time48,
90 fudge: u16,
91 mac: O,
92 original_id: u16,
93 error: TsigRcode,
94 other: O,
95 ) -> Result<Self, LongRecordData>
96 where O: AsRef<[u8]>, N: ToDname {
97 LongRecordData::check_len(
98 6 + 2 + 2 + 2 + 2 + 2 + usize::from(algorithm.compose_len()).checked_add(
105 mac.as_ref().len()
106 ).expect("long MAC").checked_add(
107 other.as_ref().len()
108 ).expect("long TSIG")
109 )?;
110 Ok(unsafe {
111 Tsig::new_unchecked(
112 algorithm, time_signed, fudge, mac, original_id, error, other,
113 )
114 })
115 }
116
117 pub unsafe fn new_unchecked(
124 algorithm: N,
125 time_signed: Time48,
126 fudge: u16,
127 mac: O,
128 original_id: u16,
129 error: TsigRcode,
130 other: O,
131 ) -> Self {
132 Tsig {
133 algorithm,
134 time_signed,
135 fudge,
136 mac,
137 original_id,
138 error,
139 other,
140 }
141 }
142
143 pub fn algorithm(&self) -> &N {
149 &self.algorithm
150 }
151
152 pub fn time_signed(&self) -> Time48 {
157 self.time_signed
158 }
159
160 pub fn fudge(&self) -> u16 {
165 self.fudge
166 }
167
168 pub fn mac(&self) -> &O {
170 &self.mac
171 }
172
173 pub fn mac_slice(&self) -> &[u8]
175 where
176 O: AsRef<[u8]>,
177 {
178 self.mac.as_ref()
179 }
180
181 pub fn into_mac(self) -> O {
183 self.mac
184 }
185
186 pub fn original_id(&self) -> u16 {
191 self.original_id
192 }
193
194 pub fn error(&self) -> TsigRcode {
196 self.error
197 }
198
199 pub fn other(&self) -> &O {
204 &self.other
205 }
206
207 pub fn other_time(&self) -> Option<Time48>
212 where
213 O: AsRef<[u8]>,
214 {
215 if self.other.as_ref().len() == 6 {
216 Some(Time48::from_slice(self.other.as_ref()))
217 } else {
218 None
219 }
220 }
221
222 pub fn is_valid_at(&self, now: Time48) -> bool {
230 now.eq_fudged(self.time_signed, self.fudge.into())
231 }
232
233 #[cfg(feature = "std")]
241 pub fn is_valid_now(&self) -> bool {
242 self.is_valid_at(Time48::now())
243 }
244
245 pub(super) fn convert_octets<TOcts, TName>(
246 self,
247 ) -> Result<Tsig<TOcts, TName>, TOcts::Error>
248 where
249 TOcts: OctetsFrom<O>,
250 TName: OctetsFrom<N, Error = TOcts::Error>,
251 {
252 Ok(unsafe {
253 Tsig::new_unchecked(
254 self.algorithm.try_octets_into()?,
255 self.time_signed,
256 self.fudge,
257 self.mac.try_octets_into()?,
258 self.original_id,
259 self.error,
260 self.other.try_octets_into()?,
261 )
262 })
263 }
264
265 pub(super) fn flatten<TOcts, TName>(
266 self,
267 ) -> Result<Tsig<TOcts, TName>, TOcts::Error>
268 where
269 TOcts: OctetsFrom<O>,
270 N: FlattenInto<TName, AppendError = TOcts::Error>,
271 {
272 Ok(unsafe {
273 Tsig::new_unchecked(
274 self.algorithm.try_flatten_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
286impl<Octs> Tsig<Octs, ParsedDname<Octs>> {
287 pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized + 'a>(
288 parser: &mut Parser<'a, Src>,
289 ) -> Result<Self, ParseError> {
290 let algorithm = ParsedDname::parse(parser)?;
291 let time_signed = Time48::parse(parser)?;
292 let fudge = u16::parse(parser)?;
293 let mac_size = u16::parse(parser)?;
294 let mac = parser.parse_octets(mac_size as usize)?;
295 let original_id = u16::parse(parser)?;
296 let error = TsigRcode::parse(parser)?;
297 let other_len = u16::parse(parser)?;
298 let other = parser.parse_octets(other_len as usize)?;
299 Ok(unsafe {
300 Tsig::new_unchecked(
301 algorithm, time_signed, fudge, mac, original_id, error, other,
302 )
303 })
304 }
305}
306
307impl<Octs, SrcOctets, Name, SrcName> OctetsFrom<Tsig<SrcOctets, SrcName>>
310 for Tsig<Octs, Name>
311where
312 Octs: OctetsFrom<SrcOctets>,
313 Name: OctetsFrom<SrcName>,
314 Octs::Error: From<Name::Error>,
315{
316 type Error = Octs::Error;
317
318 fn try_octets_from(
319 source: Tsig<SrcOctets, SrcName>,
320 ) -> Result<Self, Self::Error> {
321 Ok(unsafe {
322 Tsig::new_unchecked(
323 Name::try_octets_from(source.algorithm)?,
324 source.time_signed,
325 source.fudge,
326 Octs::try_octets_from(source.mac)?,
327 source.original_id,
328 source.error,
329 Octs::try_octets_from(source.other)?,
330 )
331 })
332 }
333}
334
335impl<Octs, TOcts, Name, TName> FlattenInto<Tsig<TOcts, TName>>
336 for Tsig<Octs, Name>
337where
338 TOcts: OctetsFrom<Octs>,
339 Name: FlattenInto<TName, AppendError = TOcts::Error>
340{
341 type AppendError = TOcts::Error;
342
343 fn try_flatten_into(
344 self
345 ) -> Result<Tsig<TOcts, TName>, Self::AppendError > {
346 self.flatten()
347 }
348}
349
350
351impl<O, OO, N, NN> PartialEq<Tsig<OO, NN>> for Tsig<O, N>
354where
355 O: AsRef<[u8]>,
356 OO: AsRef<[u8]>,
357 N: ToDname,
358 NN: ToDname,
359{
360 fn eq(&self, other: &Tsig<OO, NN>) -> bool {
361 self.algorithm.name_eq(&other.algorithm)
362 && self.time_signed == other.time_signed
363 && self.fudge == other.fudge
364 && self.mac.as_ref().eq(other.mac.as_ref())
365 && self.original_id == other.original_id
366 && self.error == other.error
367 && self.other.as_ref().eq(other.other.as_ref())
368 }
369}
370
371impl<O: AsRef<[u8]>, N: ToDname> Eq for Tsig<O, N> {}
372
373impl<O, OO, N, NN> PartialOrd<Tsig<OO, NN>> for Tsig<O, N>
376where
377 O: AsRef<[u8]>,
378 OO: AsRef<[u8]>,
379 N: ToDname,
380 NN: ToDname,
381{
382 fn partial_cmp(&self, other: &Tsig<OO, NN>) -> Option<Ordering> {
383 match self.algorithm.name_cmp(&other.algorithm) {
384 Ordering::Equal => {}
385 other => return Some(other),
386 }
387 match self.time_signed.partial_cmp(&other.time_signed) {
388 Some(Ordering::Equal) => {}
389 other => return other,
390 }
391 match self.fudge.partial_cmp(&other.fudge) {
392 Some(Ordering::Equal) => {}
393 other => return other,
394 }
395 match self.mac.as_ref().partial_cmp(other.mac.as_ref()) {
396 Some(Ordering::Equal) => {}
397 other => return other,
398 }
399 match self.original_id.partial_cmp(&other.original_id) {
400 Some(Ordering::Equal) => {}
401 other => return other,
402 }
403 match self.error.partial_cmp(&other.error) {
404 Some(Ordering::Equal) => {}
405 other => return other,
406 }
407 self.other.as_ref().partial_cmp(other.other.as_ref())
408 }
409}
410
411impl<O: AsRef<[u8]>, N: ToDname> Ord for Tsig<O, N> {
412 fn cmp(&self, other: &Self) -> Ordering {
413 match self.algorithm.name_cmp(&other.algorithm) {
414 Ordering::Equal => {}
415 other => return other,
416 }
417 match self.time_signed.cmp(&other.time_signed) {
418 Ordering::Equal => {}
419 other => return other,
420 }
421 match self.fudge.cmp(&other.fudge) {
422 Ordering::Equal => {}
423 other => return other,
424 }
425 match self.mac.as_ref().cmp(other.mac.as_ref()) {
426 Ordering::Equal => {}
427 other => return other,
428 }
429 match self.original_id.cmp(&other.original_id) {
430 Ordering::Equal => {}
431 other => return other,
432 }
433 match self.error.cmp(&other.error) {
434 Ordering::Equal => {}
435 other => return other,
436 }
437 self.other.as_ref().cmp(other.other.as_ref())
438 }
439}
440
441impl<O, OO, N, NN> CanonicalOrd<Tsig<OO, NN>> for Tsig<O, N>
442where
443 O: AsRef<[u8]>,
444 OO: AsRef<[u8]>,
445 N: ToDname,
446 NN: ToDname,
447{
448 fn canonical_cmp(&self, other: &Tsig<OO, NN>) -> Ordering {
449 match self.algorithm.composed_cmp(&other.algorithm) {
450 Ordering::Equal => {}
451 other => return other,
452 }
453 match self.time_signed.cmp(&other.time_signed) {
454 Ordering::Equal => {}
455 other => return other,
456 }
457 match self.fudge.cmp(&other.fudge) {
458 Ordering::Equal => {}
459 other => return other,
460 }
461 match self.mac.as_ref().len().cmp(&other.mac.as_ref().len()) {
462 Ordering::Equal => {}
463 other => return other,
464 }
465 match self.mac.as_ref().cmp(other.mac.as_ref()) {
466 Ordering::Equal => {}
467 other => return other,
468 }
469 match self.original_id.cmp(&other.original_id) {
470 Ordering::Equal => {}
471 other => return other,
472 }
473 match self.error.cmp(&other.error) {
474 Ordering::Equal => {}
475 other => return other,
476 }
477 match self.other.as_ref().len().cmp(&other.other.as_ref().len()) {
478 Ordering::Equal => {}
479 other => return other,
480 }
481 self.other.as_ref().cmp(other.other.as_ref())
482 }
483}
484
485impl<O: AsRef<[u8]>, N: hash::Hash> hash::Hash for Tsig<O, N> {
488 fn hash<H: hash::Hasher>(&self, state: &mut H) {
489 self.algorithm.hash(state);
490 self.time_signed.hash(state);
491 self.fudge.hash(state);
492 self.mac.as_ref().hash(state);
493 self.original_id.hash(state);
494 self.error.hash(state);
495 self.other.as_ref().hash(state);
496 }
497}
498
499impl<O, N> RecordData for Tsig<O, N> {
502 fn rtype(&self) -> Rtype {
503 Rtype::Tsig
504 }
505}
506
507impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
508 for Tsig<Octs::Range<'a>, ParsedDname<Octs::Range<'a>>>
509{
510 fn parse_rdata(
511 rtype: Rtype,
512 parser: &mut Parser<'a, Octs>,
513 ) -> Result<Option<Self>, ParseError> {
514 if rtype == Rtype::Tsig {
515 Self::parse(parser).map(Some)
516 } else {
517 Ok(None)
518 }
519 }
520}
521
522impl<Octs: AsRef<[u8]>, Name: ToDname> ComposeRecordData
523 for Tsig<Octs, Name>
524{
525 fn rdlen(&self, _compress: bool) -> Option<u16> {
526 Some(
527 6 + 2 + 2 + 2 + 2 + 2 + self.algorithm.compose_len().checked_add(
534 u16::try_from(self.mac.as_ref().len()).expect("long MAC")
535 ).expect("long MAC").checked_add(
536 u16::try_from(self.other.as_ref().len()).expect("long TSIG")
537 ).expect("long TSIG"),
538 )
539 }
540
541 fn compose_rdata<Target: Composer + ?Sized>(
542 &self,
543 target: &mut Target,
544 ) -> Result<(), Target::AppendError> {
545 self.algorithm.compose(target)?;
546 self.time_signed.compose(target)?;
547 self.fudge.compose(target)?;
548 u16::try_from(self.mac.as_ref().len())
549 .expect("long MAC")
550 .compose(target)?;
551 target.append_slice(self.mac.as_ref())?;
552 self.original_id.compose(target)?;
553 self.error.compose(target)?;
554 u16::try_from(self.other.as_ref().len())
555 .expect("long MAC")
556 .compose(target)?;
557 target.append_slice(self.other.as_ref())
558 }
559
560 fn compose_canonical_rdata<Target: Composer + ?Sized>(
561 &self,
562 target: &mut Target,
563 ) -> Result<(), Target::AppendError> {
564 self.compose_rdata(target)
565 }
566}
567
568impl<O: AsRef<[u8]>, N: fmt::Display> fmt::Display for Tsig<O, N> {
571 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
572 write!(
573 f,
574 "{}. {} {} ",
575 self.algorithm, self.time_signed, self.fudge
576 )?;
577 base64::display(&self.mac, f)?;
578 write!(f, " {} {} \"", self.original_id, self.error)?;
579 base64::display(&self.other, f)?;
580 write!(f, "\"")
581 }
582}
583
584impl<O: AsRef<[u8]>, N: fmt::Debug> fmt::Debug for Tsig<O, N> {
585 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
586 f.debug_struct("Tsig")
587 .field("algorithm", &self.algorithm)
588 .field("time_signed", &self.time_signed)
589 .field("fudge", &self.fudge)
590 .field("mac", &self.mac.as_ref())
591 .field("original_id", &self.original_id)
592 .field("error", &self.error)
593 .field("other", &self.other.as_ref())
594 .finish()
595 }
596}
597
598#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
602#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
603pub struct Time48(u64);
604
605impl Time48 {
606 #[cfg(feature = "std")]
612 #[must_use]
613 pub fn now() -> Time48 {
614 Self::from_u64(
615 SystemTime::now()
616 .duration_since(SystemTime::UNIX_EPOCH)
617 .expect("system time before Unix epoch")
618 .as_secs(),
619 )
620 }
621
622 #[must_use]
627 pub fn from_u64(value: u64) -> Self {
628 assert!(value & 0xFFFF_0000_0000_0000 == 0);
629 Time48(value)
630 }
631
632 fn from_slice(slice: &[u8]) -> Self {
641 Time48(
642 (u64::from(slice[0]) << 40)
643 | (u64::from(slice[1]) << 32)
644 | (u64::from(slice[2]) << 24)
645 | (u64::from(slice[3]) << 16)
646 | (u64::from(slice[4]) << 8)
647 | (u64::from(slice[5])),
648 )
649 }
650
651 #[must_use]
655 pub fn into_octets(self) -> [u8; 6] {
656 let mut res = [0u8; 6];
657 res[0] = (self.0 >> 40) as u8;
658 res[1] = (self.0 >> 32) as u8;
659 res[2] = (self.0 >> 24) as u8;
660 res[3] = (self.0 >> 16) as u8;
661 res[4] = (self.0 >> 8) as u8;
662 res[5] = self.0 as u8;
663 res
664 }
665
666 #[must_use]
671 pub fn eq_fudged(self, other: Self, fudge: u64) -> bool {
672 self.0.saturating_sub(fudge) <= other.0
673 && self.0.saturating_add(fudge) >= other.0
674 }
675
676 pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
677 parser: &mut Parser<Octs>,
678 ) -> Result<Self, ParseError> {
679 let mut buf = [0u8; 6];
680 parser.parse_buf(&mut buf)?;
681 Ok(Time48::from_slice(&buf))
682 }
683
684 pub fn compose<Target: OctetsBuilder + ?Sized>(
685 &self,
686 target: &mut Target,
687 ) -> Result<(), Target::AppendError> {
688 target.append_slice(&self.into_octets())
689 }
690}
691
692impl From<Time48> for u64 {
695 fn from(value: Time48) -> u64 {
696 value.0
697 }
698}
699
700impl fmt::Display for Time48 {
703 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
704 self.0.fmt(f)
705 }
706}
707
708#[cfg(test)]
711#[cfg(all(feature = "std", feature = "bytes"))]
712mod test {
713 use super::*;
714 use crate::base::name::Dname;
715 use crate::base::rdata::test::{test_compose_parse, test_rdlen};
716 use core::str::FromStr;
717 use std::vec::Vec;
718
719 #[test]
720 #[allow(clippy::redundant_closure)] fn tsig_compose_parse_scan() {
722 let rdata = Tsig::new(
723 Dname::<Vec<u8>>::from_str("key.example.com.").unwrap(),
724 Time48::now(),
725 12,
726 "foo",
727 13,
728 TsigRcode::BadCookie,
729 "",
730 ).unwrap();
731 test_rdlen(&rdata);
732 test_compose_parse(&rdata, |parser| Tsig::parse(parser));
733 }
734}