use crate::base::cmp::CanonicalOrd;
use crate::base::iana::{Rtype, TsigRcode};
use crate::base::name::{FlattenInto, ParsedDname, ToDname};
use crate::base::rdata::{
ComposeRecordData, LongRecordData, ParseRecordData, RecordData
};
use crate::base::wire::{Compose, Composer, Parse, ParseError};
use crate::utils::base64;
use core::cmp::Ordering;
use core::{fmt, hash};
use octseq::builder::OctetsBuilder;
use octseq::octets::{Octets, OctetsFrom, OctetsInto};
use octseq::parse::Parser;
#[cfg(feature = "std")]
use std::time::SystemTime;
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Tsig<Octs, Name> {
algorithm: Name,
time_signed: Time48,
fudge: u16,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "octseq::serde::SerializeOctets::serialize_octets",
deserialize_with = "octseq::serde::DeserializeOctets::deserialize_octets",
bound(
serialize = "Octs: octseq::serde::SerializeOctets",
deserialize = "Octs: octseq::serde::DeserializeOctets<'de>",
)
)
)]
mac: Octs,
original_id: u16,
error: TsigRcode,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "octseq::serde::SerializeOctets::serialize_octets",
deserialize_with = "octseq::serde::DeserializeOctets::deserialize_octets",
bound(
serialize = "Octs: octseq::serde::SerializeOctets",
deserialize = "Octs: octseq::serde::DeserializeOctets<'de>",
)
)
)]
other: Octs,
}
impl<O, N> Tsig<O, N> {
pub fn new(
algorithm: N,
time_signed: Time48,
fudge: u16,
mac: O,
original_id: u16,
error: TsigRcode,
other: O,
) -> Result<Self, LongRecordData>
where O: AsRef<[u8]>, N: ToDname {
LongRecordData::check_len(
6 + 2 + 2 + 2 + 2 + 2 + usize::from(algorithm.compose_len()).checked_add(
mac.as_ref().len()
).expect("long MAC").checked_add(
other.as_ref().len()
).expect("long TSIG")
)?;
Ok(unsafe {
Tsig::new_unchecked(
algorithm, time_signed, fudge, mac, original_id, error, other,
)
})
}
pub unsafe fn new_unchecked(
algorithm: N,
time_signed: Time48,
fudge: u16,
mac: O,
original_id: u16,
error: TsigRcode,
other: O,
) -> Self {
Tsig {
algorithm,
time_signed,
fudge,
mac,
original_id,
error,
other,
}
}
pub fn algorithm(&self) -> &N {
&self.algorithm
}
pub fn time_signed(&self) -> Time48 {
self.time_signed
}
pub fn fudge(&self) -> u16 {
self.fudge
}
pub fn mac(&self) -> &O {
&self.mac
}
pub fn mac_slice(&self) -> &[u8]
where
O: AsRef<[u8]>,
{
self.mac.as_ref()
}
pub fn into_mac(self) -> O {
self.mac
}
pub fn original_id(&self) -> u16 {
self.original_id
}
pub fn error(&self) -> TsigRcode {
self.error
}
pub fn other(&self) -> &O {
&self.other
}
pub fn other_time(&self) -> Option<Time48>
where
O: AsRef<[u8]>,
{
if self.other.as_ref().len() == 6 {
Some(Time48::from_slice(self.other.as_ref()))
} else {
None
}
}
pub fn is_valid_at(&self, now: Time48) -> bool {
now.eq_fudged(self.time_signed, self.fudge.into())
}
#[cfg(feature = "std")]
pub fn is_valid_now(&self) -> bool {
self.is_valid_at(Time48::now())
}
pub(super) fn convert_octets<TOcts, TName>(
self,
) -> Result<Tsig<TOcts, TName>, TOcts::Error>
where
TOcts: OctetsFrom<O>,
TName: OctetsFrom<N, Error = TOcts::Error>,
{
Ok(unsafe {
Tsig::new_unchecked(
self.algorithm.try_octets_into()?,
self.time_signed,
self.fudge,
self.mac.try_octets_into()?,
self.original_id,
self.error,
self.other.try_octets_into()?,
)
})
}
pub(super) fn flatten<TOcts, TName>(
self,
) -> Result<Tsig<TOcts, TName>, TOcts::Error>
where
TOcts: OctetsFrom<O>,
N: FlattenInto<TName, AppendError = TOcts::Error>,
{
Ok(unsafe {
Tsig::new_unchecked(
self.algorithm.try_flatten_into()?,
self.time_signed,
self.fudge,
self.mac.try_octets_into()?,
self.original_id,
self.error,
self.other.try_octets_into()?,
)
})
}
}
impl<Octs> Tsig<Octs, ParsedDname<Octs>> {
pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized + 'a>(
parser: &mut Parser<'a, Src>,
) -> Result<Self, ParseError> {
let algorithm = ParsedDname::parse(parser)?;
let time_signed = Time48::parse(parser)?;
let fudge = u16::parse(parser)?;
let mac_size = u16::parse(parser)?;
let mac = parser.parse_octets(mac_size as usize)?;
let original_id = u16::parse(parser)?;
let error = TsigRcode::parse(parser)?;
let other_len = u16::parse(parser)?;
let other = parser.parse_octets(other_len as usize)?;
Ok(unsafe {
Tsig::new_unchecked(
algorithm, time_signed, fudge, mac, original_id, error, other,
)
})
}
}
impl<Octs, SrcOctets, Name, SrcName> OctetsFrom<Tsig<SrcOctets, SrcName>>
for Tsig<Octs, Name>
where
Octs: OctetsFrom<SrcOctets>,
Name: OctetsFrom<SrcName>,
Octs::Error: From<Name::Error>,
{
type Error = Octs::Error;
fn try_octets_from(
source: Tsig<SrcOctets, SrcName>,
) -> Result<Self, Self::Error> {
Ok(unsafe {
Tsig::new_unchecked(
Name::try_octets_from(source.algorithm)?,
source.time_signed,
source.fudge,
Octs::try_octets_from(source.mac)?,
source.original_id,
source.error,
Octs::try_octets_from(source.other)?,
)
})
}
}
impl<Octs, TOcts, Name, TName> FlattenInto<Tsig<TOcts, TName>>
for Tsig<Octs, Name>
where
TOcts: OctetsFrom<Octs>,
Name: FlattenInto<TName, AppendError = TOcts::Error>
{
type AppendError = TOcts::Error;
fn try_flatten_into(
self
) -> Result<Tsig<TOcts, TName>, Self::AppendError > {
self.flatten()
}
}
impl<O, OO, N, NN> PartialEq<Tsig<OO, NN>> for Tsig<O, N>
where
O: AsRef<[u8]>,
OO: AsRef<[u8]>,
N: ToDname,
NN: ToDname,
{
fn eq(&self, other: &Tsig<OO, NN>) -> bool {
self.algorithm.name_eq(&other.algorithm)
&& self.time_signed == other.time_signed
&& self.fudge == other.fudge
&& self.mac.as_ref().eq(other.mac.as_ref())
&& self.original_id == other.original_id
&& self.error == other.error
&& self.other.as_ref().eq(other.other.as_ref())
}
}
impl<O: AsRef<[u8]>, N: ToDname> Eq for Tsig<O, N> {}
impl<O, OO, N, NN> PartialOrd<Tsig<OO, NN>> for Tsig<O, N>
where
O: AsRef<[u8]>,
OO: AsRef<[u8]>,
N: ToDname,
NN: ToDname,
{
fn partial_cmp(&self, other: &Tsig<OO, NN>) -> Option<Ordering> {
match self.algorithm.name_cmp(&other.algorithm) {
Ordering::Equal => {}
other => return Some(other),
}
match self.time_signed.partial_cmp(&other.time_signed) {
Some(Ordering::Equal) => {}
other => return other,
}
match self.fudge.partial_cmp(&other.fudge) {
Some(Ordering::Equal) => {}
other => return other,
}
match self.mac.as_ref().partial_cmp(other.mac.as_ref()) {
Some(Ordering::Equal) => {}
other => return other,
}
match self.original_id.partial_cmp(&other.original_id) {
Some(Ordering::Equal) => {}
other => return other,
}
match self.error.partial_cmp(&other.error) {
Some(Ordering::Equal) => {}
other => return other,
}
self.other.as_ref().partial_cmp(other.other.as_ref())
}
}
impl<O: AsRef<[u8]>, N: ToDname> Ord for Tsig<O, N> {
fn cmp(&self, other: &Self) -> Ordering {
match self.algorithm.name_cmp(&other.algorithm) {
Ordering::Equal => {}
other => return other,
}
match self.time_signed.cmp(&other.time_signed) {
Ordering::Equal => {}
other => return other,
}
match self.fudge.cmp(&other.fudge) {
Ordering::Equal => {}
other => return other,
}
match self.mac.as_ref().cmp(other.mac.as_ref()) {
Ordering::Equal => {}
other => return other,
}
match self.original_id.cmp(&other.original_id) {
Ordering::Equal => {}
other => return other,
}
match self.error.cmp(&other.error) {
Ordering::Equal => {}
other => return other,
}
self.other.as_ref().cmp(other.other.as_ref())
}
}
impl<O, OO, N, NN> CanonicalOrd<Tsig<OO, NN>> for Tsig<O, N>
where
O: AsRef<[u8]>,
OO: AsRef<[u8]>,
N: ToDname,
NN: ToDname,
{
fn canonical_cmp(&self, other: &Tsig<OO, NN>) -> Ordering {
match self.algorithm.composed_cmp(&other.algorithm) {
Ordering::Equal => {}
other => return other,
}
match self.time_signed.cmp(&other.time_signed) {
Ordering::Equal => {}
other => return other,
}
match self.fudge.cmp(&other.fudge) {
Ordering::Equal => {}
other => return other,
}
match self.mac.as_ref().len().cmp(&other.mac.as_ref().len()) {
Ordering::Equal => {}
other => return other,
}
match self.mac.as_ref().cmp(other.mac.as_ref()) {
Ordering::Equal => {}
other => return other,
}
match self.original_id.cmp(&other.original_id) {
Ordering::Equal => {}
other => return other,
}
match self.error.cmp(&other.error) {
Ordering::Equal => {}
other => return other,
}
match self.other.as_ref().len().cmp(&other.other.as_ref().len()) {
Ordering::Equal => {}
other => return other,
}
self.other.as_ref().cmp(other.other.as_ref())
}
}
impl<O: AsRef<[u8]>, N: hash::Hash> hash::Hash for Tsig<O, N> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.algorithm.hash(state);
self.time_signed.hash(state);
self.fudge.hash(state);
self.mac.as_ref().hash(state);
self.original_id.hash(state);
self.error.hash(state);
self.other.as_ref().hash(state);
}
}
impl<O, N> RecordData for Tsig<O, N> {
fn rtype(&self) -> Rtype {
Rtype::Tsig
}
}
impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
for Tsig<Octs::Range<'a>, ParsedDname<Octs::Range<'a>>>
{
fn parse_rdata(
rtype: Rtype,
parser: &mut Parser<'a, Octs>,
) -> Result<Option<Self>, ParseError> {
if rtype == Rtype::Tsig {
Self::parse(parser).map(Some)
} else {
Ok(None)
}
}
}
impl<Octs: AsRef<[u8]>, Name: ToDname> ComposeRecordData
for Tsig<Octs, Name>
{
fn rdlen(&self, _compress: bool) -> Option<u16> {
Some(
6 + 2 + 2 + 2 + 2 + 2 + self.algorithm.compose_len().checked_add(
u16::try_from(self.mac.as_ref().len()).expect("long MAC")
).expect("long MAC").checked_add(
u16::try_from(self.other.as_ref().len()).expect("long TSIG")
).expect("long TSIG"),
)
}
fn compose_rdata<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
self.algorithm.compose(target)?;
self.time_signed.compose(target)?;
self.fudge.compose(target)?;
u16::try_from(self.mac.as_ref().len())
.expect("long MAC")
.compose(target)?;
target.append_slice(self.mac.as_ref())?;
self.original_id.compose(target)?;
self.error.compose(target)?;
u16::try_from(self.other.as_ref().len())
.expect("long MAC")
.compose(target)?;
target.append_slice(self.other.as_ref())
}
fn compose_canonical_rdata<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
self.compose_rdata(target)
}
}
impl<O: AsRef<[u8]>, N: fmt::Display> fmt::Display for Tsig<O, N> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}. {} {} ",
self.algorithm, self.time_signed, self.fudge
)?;
base64::display(&self.mac, f)?;
write!(f, " {} {} \"", self.original_id, self.error)?;
base64::display(&self.other, f)?;
write!(f, "\"")
}
}
impl<O: AsRef<[u8]>, N: fmt::Debug> fmt::Debug for Tsig<O, N> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Tsig")
.field("algorithm", &self.algorithm)
.field("time_signed", &self.time_signed)
.field("fudge", &self.fudge)
.field("mac", &self.mac.as_ref())
.field("original_id", &self.original_id)
.field("error", &self.error)
.field("other", &self.other.as_ref())
.finish()
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Time48(u64);
impl Time48 {
#[cfg(feature = "std")]
#[must_use]
pub fn now() -> Time48 {
Self::from_u64(
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("system time before Unix epoch")
.as_secs(),
)
}
#[must_use]
pub fn from_u64(value: u64) -> Self {
assert!(value & 0xFFFF_0000_0000_0000 == 0);
Time48(value)
}
fn from_slice(slice: &[u8]) -> Self {
Time48(
(u64::from(slice[0]) << 40)
| (u64::from(slice[1]) << 32)
| (u64::from(slice[2]) << 24)
| (u64::from(slice[3]) << 16)
| (u64::from(slice[4]) << 8)
| (u64::from(slice[5])),
)
}
#[must_use]
pub fn into_octets(self) -> [u8; 6] {
let mut res = [0u8; 6];
res[0] = (self.0 >> 40) as u8;
res[1] = (self.0 >> 32) as u8;
res[2] = (self.0 >> 24) as u8;
res[3] = (self.0 >> 16) as u8;
res[4] = (self.0 >> 8) as u8;
res[5] = self.0 as u8;
res
}
#[must_use]
pub fn eq_fudged(self, other: Self, fudge: u64) -> bool {
self.0.saturating_sub(fudge) <= other.0
&& self.0.saturating_add(fudge) >= other.0
}
pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
parser: &mut Parser<Octs>,
) -> Result<Self, ParseError> {
let mut buf = [0u8; 6];
parser.parse_buf(&mut buf)?;
Ok(Time48::from_slice(&buf))
}
pub fn compose<Target: OctetsBuilder + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
target.append_slice(&self.into_octets())
}
}
impl From<Time48> for u64 {
fn from(value: Time48) -> u64 {
value.0
}
}
impl fmt::Display for Time48 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[cfg(test)]
#[cfg(all(feature = "std", feature = "bytes"))]
mod test {
use super::*;
use crate::base::name::Dname;
use crate::base::rdata::test::{test_compose_parse, test_rdlen};
use core::str::FromStr;
use std::vec::Vec;
#[test]
#[allow(clippy::redundant_closure)] fn tsig_compose_parse_scan() {
let rdata = Tsig::new(
Dname::<Vec<u8>>::from_str("key.example.com.").unwrap(),
Time48::now(),
12,
"foo",
13,
TsigRcode::BadCookie,
"",
).unwrap();
test_rdlen(&rdata);
test_compose_parse(&rdata, |parser| Tsig::parse(parser));
}
}