use crate::base::cmp::CanonicalOrd;
use crate::base::iana::Rtype;
use crate::base::name::{FlattenInto, ParsedName, ToName};
use crate::base::rdata::{
ComposeRecordData, ParseRecordData, RecordData,
};
use crate::base::record::Ttl;
use crate::base::scan::{Scan, Scanner};
use crate::base::serial::Serial;
use crate::base::wire::{Compose, Composer, ParseError};
use core::fmt;
use core::cmp::Ordering;
use octseq::octets::{Octets, OctetsFrom, OctetsInto};
use octseq::parse::Parser;
#[derive(Clone, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Soa<N> {
mname: N,
rname: N,
serial: Serial,
refresh: Ttl,
retry: Ttl,
expire: Ttl,
minimum: Ttl,
}
impl Soa<()> {
pub(crate) const RTYPE: Rtype = Rtype::SOA;
}
impl<N> Soa<N> {
pub fn new(
mname: N,
rname: N,
serial: Serial,
refresh: Ttl,
retry: Ttl,
expire: Ttl,
minimum: Ttl,
) -> Self {
Soa {
mname,
rname,
serial,
refresh,
retry,
expire,
minimum,
}
}
pub fn mname(&self) -> &N {
&self.mname
}
pub fn rname(&self) -> &N {
&self.rname
}
pub fn serial(&self) -> Serial {
self.serial
}
pub fn refresh(&self) -> Ttl {
self.refresh
}
pub fn retry(&self) -> Ttl {
self.retry
}
pub fn expire(&self) -> Ttl {
self.expire
}
pub fn minimum(&self) -> Ttl {
self.minimum
}
pub(in crate::rdata) fn convert_octets<Target: OctetsFrom<N>>(
self,
) -> Result<Soa<Target>, Target::Error> {
Ok(Soa::new(
self.mname.try_octets_into()?,
self.rname.try_octets_into()?,
self.serial,
self.refresh,
self.retry,
self.expire,
self.minimum,
))
}
pub(in crate::rdata) fn flatten<TargetName>(
self,
) -> Result<Soa<TargetName>, N::AppendError>
where N: FlattenInto<TargetName> {
Ok(Soa::new(
self.mname.try_flatten_into()?,
self.rname.try_flatten_into()?,
self.serial,
self.refresh,
self.retry,
self.expire,
self.minimum,
))
}
pub fn scan<S: Scanner<Name = N>>(
scanner: &mut S,
) -> Result<Self, S::Error> {
Ok(Self::new(
scanner.scan_name()?,
scanner.scan_name()?,
Serial::scan(scanner)?,
Ttl::scan(scanner)?,
Ttl::scan(scanner)?,
Ttl::scan(scanner)?,
Ttl::scan(scanner)?,
))
}
}
impl<Octs> Soa<ParsedName<Octs>> {
pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized + 'a>(
parser: &mut Parser<'a, Src>,
) -> Result<Self, ParseError> {
Ok(Self::new(
ParsedName::parse(parser)?,
ParsedName::parse(parser)?,
Serial::parse(parser)?,
Ttl::parse(parser)?,
Ttl::parse(parser)?,
Ttl::parse(parser)?,
Ttl::parse(parser)?,
))
}
}
impl<Name, SrcName> OctetsFrom<Soa<SrcName>> for Soa<Name>
where
Name: OctetsFrom<SrcName>,
{
type Error = Name::Error;
fn try_octets_from(source: Soa<SrcName>) -> Result<Self, Self::Error> {
Ok(Soa::new(
Name::try_octets_from(source.mname)?,
Name::try_octets_from(source.rname)?,
source.serial,
source.refresh,
source.retry,
source.expire,
source.minimum,
))
}
}
impl<Name, TName> FlattenInto<Soa<TName>> for Soa<Name>
where
Name: FlattenInto<TName>,
{
type AppendError = Name::AppendError;
fn try_flatten_into(self) -> Result<Soa<TName>, Name::AppendError> {
self.flatten()
}
}
impl<N, NN> PartialEq<Soa<NN>> for Soa<N>
where
N: ToName,
NN: ToName,
{
fn eq(&self, other: &Soa<NN>) -> bool {
self.mname.name_eq(&other.mname)
&& self.rname.name_eq(&other.rname)
&& self.serial == other.serial
&& self.refresh == other.refresh
&& self.retry == other.retry
&& self.expire == other.expire
&& self.minimum == other.minimum
}
}
impl<N: ToName> Eq for Soa<N> {}
impl<N, NN> PartialOrd<Soa<NN>> for Soa<N>
where
N: ToName,
NN: ToName,
{
fn partial_cmp(&self, other: &Soa<NN>) -> Option<Ordering> {
match self.mname.name_cmp(&other.mname) {
Ordering::Equal => {}
other => return Some(other),
}
match self.rname.name_cmp(&other.rname) {
Ordering::Equal => {}
other => return Some(other),
}
match u32::from(self.serial).partial_cmp(&u32::from(other.serial)) {
Some(Ordering::Equal) => {}
other => return other,
}
match self.refresh.partial_cmp(&other.refresh) {
Some(Ordering::Equal) => {}
other => return other,
}
match self.retry.partial_cmp(&other.retry) {
Some(Ordering::Equal) => {}
other => return other,
}
match self.expire.partial_cmp(&other.expire) {
Some(Ordering::Equal) => {}
other => return other,
}
self.minimum.partial_cmp(&other.minimum)
}
}
impl<N: ToName> Ord for Soa<N> {
fn cmp(&self, other: &Self) -> Ordering {
match self.mname.name_cmp(&other.mname) {
Ordering::Equal => {}
other => return other,
}
match self.rname.name_cmp(&other.rname) {
Ordering::Equal => {}
other => return other,
}
match u32::from(self.serial).cmp(&u32::from(other.serial)) {
Ordering::Equal => {}
other => return other,
}
match self.refresh.cmp(&other.refresh) {
Ordering::Equal => {}
other => return other,
}
match self.retry.cmp(&other.retry) {
Ordering::Equal => {}
other => return other,
}
match self.expire.cmp(&other.expire) {
Ordering::Equal => {}
other => return other,
}
self.minimum.cmp(&other.minimum)
}
}
impl<N: ToName, NN: ToName> CanonicalOrd<Soa<NN>> for Soa<N> {
fn canonical_cmp(&self, other: &Soa<NN>) -> Ordering {
match self.mname.lowercase_composed_cmp(&other.mname) {
Ordering::Equal => {}
other => return other,
}
match self.rname.lowercase_composed_cmp(&other.rname) {
Ordering::Equal => {}
other => return other,
}
match self.serial.canonical_cmp(&other.serial) {
Ordering::Equal => {}
other => return other,
}
match self.refresh.cmp(&other.refresh) {
Ordering::Equal => {}
other => return other,
}
match self.retry.cmp(&other.retry) {
Ordering::Equal => {}
other => return other,
}
match self.expire.cmp(&other.expire) {
Ordering::Equal => {}
other => return other,
}
self.minimum.cmp(&other.minimum)
}
}
impl<N> RecordData for Soa<N> {
fn rtype(&self) -> Rtype {
Soa::RTYPE
}
}
impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
for Soa<ParsedName<Octs::Range<'a>>>
{
fn parse_rdata(
rtype: Rtype,
parser: &mut Parser<'a, Octs>,
) -> Result<Option<Self>, ParseError> {
if rtype == Soa::RTYPE {
Self::parse(parser).map(Some)
} else {
Ok(None)
}
}
}
impl<Name: ToName> ComposeRecordData for Soa<Name> {
fn rdlen(&self, compress: bool) -> Option<u16> {
if compress {
None
} else {
Some(
self.mname.compose_len()
+ self.rname.compose_len()
+ Serial::COMPOSE_LEN
+ 4 * u32::COMPOSE_LEN,
)
}
}
fn compose_rdata<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
if target.can_compress() {
target.append_compressed_name(&self.mname)?;
target.append_compressed_name(&self.rname)?;
} else {
self.mname.compose(target)?;
self.rname.compose(target)?;
}
self.compose_fixed(target)
}
fn compose_canonical_rdata<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
self.mname.compose_canonical(target)?;
self.rname.compose_canonical(target)?;
self.compose_fixed(target)
}
}
impl<Name: ToName> Soa<Name> {
fn compose_fixed<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
self.serial.compose(target)?;
self.refresh.compose(target)?;
self.retry.compose(target)?;
self.expire.compose(target)?;
self.minimum.compose(target)
}
}
impl<N: fmt::Display> fmt::Display for Soa<N> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}. {}. {} {} {} {} {}",
self.mname,
self.rname,
self.serial,
self.refresh.as_secs(),
self.retry.as_secs(),
self.expire.as_secs(),
self.minimum.as_secs()
)
}
}
#[cfg(test)]
#[cfg(all(feature = "std", feature = "bytes"))]
mod test {
use super::*;
use crate::base::name::Name;
use crate::base::rdata::test::{
test_compose_parse, test_rdlen, test_scan,
};
use core::str::FromStr;
use std::vec::Vec;
#[test]
#[allow(clippy::redundant_closure)] fn soa_compose_parse_scan() {
let rdata = Soa::<Name<Vec<u8>>>::new(
Name::from_str("m.example.com").unwrap(),
Name::from_str("r.example.com").unwrap(),
Serial(11),
Ttl::from_secs(12),
Ttl::from_secs(13),
Ttl::from_secs(14),
Ttl::from_secs(15),
);
test_rdlen(&rdata);
test_compose_parse(&rdata, |parser| Soa::parse(parser));
test_scan(
&[
"m.example.com",
"r.example.com",
"11",
"12",
"13",
"14",
"15",
],
Soa::scan,
&rdata,
);
}
}