use super::cmp::CanonicalOrd;
use super::iana::{Class, Rtype};
use super::name::{FlattenInto, ParsedName, ToName};
use super::rdata::{
ComposeRecordData, ParseAnyRecordData, ParseRecordData, RecordData,
};
use super::wire::{Compose, Composer, FormError, Parse, ParseError};
use super::zonefile_fmt::{self, Formatter, ZonefileFmt};
use core::cmp::Ordering;
use core::time::Duration;
use core::{fmt, hash};
use octseq::builder::ShortBuf;
use octseq::octets::{Octets, OctetsFrom};
use octseq::parse::Parser;
use octseq::OctetsBuilder;
#[cfg_attr(feature = "zonefile", doc = "[zonefile][crate::zonefile]")]
#[cfg_attr(not(feature = "zonefile"), doc = "zonefile")]
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Record<Name, Data> {
owner: Name,
class: Class,
ttl: Ttl,
data: Data,
}
impl<Name, Data> Record<Name, Data> {
pub fn new(owner: Name, class: Class, ttl: Ttl, data: Data) -> Self {
Record {
owner,
class,
ttl,
data,
}
}
pub fn from_record<NN, DD>(record: Record<NN, DD>) -> Self
where
Name: From<NN>,
Data: From<DD>,
{
Self::new(
record.owner.into(),
record.class,
record.ttl,
record.data.into(),
)
}
pub fn owner(&self) -> &Name {
&self.owner
}
pub fn rtype(&self) -> Rtype
where
Data: RecordData,
{
self.data.rtype()
}
pub fn class(&self) -> Class {
self.class
}
pub fn set_class(&mut self, class: Class) {
self.class = class
}
pub fn ttl(&self) -> Ttl {
self.ttl
}
pub fn set_ttl(&mut self, ttl: Ttl) {
self.ttl = ttl
}
pub fn data(&self) -> &Data {
&self.data
}
pub fn data_mut(&mut self) -> &mut Data {
&mut self.data
}
pub fn into_data(self) -> Data {
self.data
}
pub fn into_owner_and_data(self) -> (Name, Data) {
(self.owner, self.data)
}
}
impl<Octs, Data> Record<ParsedName<Octs>, Data> {
pub fn parse<'a, Src: Octets<Range<'a> = Octs> + 'a>(
parser: &mut Parser<'a, Src>,
) -> Result<Option<Self>, ParseError>
where
Data: ParseRecordData<'a, Src>,
{
let header = RecordHeader::parse(parser)?;
header.parse_into_record(parser)
}
}
impl<N: ToName, D: RecordData + ComposeRecordData> Record<N, D> {
pub fn compose<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
target.append_compressed_name(&self.owner)?;
self.data.rtype().compose(target)?;
self.class.compose(target)?;
self.ttl.compose(target)?;
self.data.compose_len_rdata(target)
}
pub fn compose_canonical<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
self.owner.compose_canonical(target)?;
self.data.rtype().compose(target)?;
self.class.compose(target)?;
self.ttl.compose(target)?;
self.data.compose_canonical_len_rdata(target)
}
}
impl<N, D> From<(N, Class, u32, D)> for Record<N, D> {
fn from((owner, class, ttl, data): (N, Class, u32, D)) -> Self {
Self::new(owner, class, Ttl::from_secs(ttl), data)
}
}
impl<N, D> From<(N, Class, Ttl, D)> for Record<N, D> {
fn from((owner, class, ttl, data): (N, Class, Ttl, D)) -> Self {
Self::new(owner, class, ttl, data)
}
}
impl<N, D> From<(N, u32, D)> for Record<N, D> {
fn from((owner, ttl, data): (N, u32, D)) -> Self {
Self::new(owner, Class::IN, Ttl::from_secs(ttl), data)
}
}
impl<N, D> AsRef<Record<N, D>> for Record<N, D> {
fn as_ref(&self) -> &Record<N, D> {
self
}
}
impl<Name, Data, SrcName, SrcData> OctetsFrom<Record<SrcName, SrcData>>
for Record<Name, Data>
where
Name: OctetsFrom<SrcName>,
Data: OctetsFrom<SrcData>,
Data::Error: From<Name::Error>,
{
type Error = Data::Error;
fn try_octets_from(
source: Record<SrcName, SrcData>,
) -> Result<Self, Self::Error> {
Ok(Record {
owner: Name::try_octets_from(source.owner)?,
class: source.class,
ttl: source.ttl,
data: Data::try_octets_from(source.data)?,
})
}
}
impl<Name, TName, Data, TData> FlattenInto<Record<TName, TData>>
for Record<Name, Data>
where
Name: FlattenInto<TName>,
Data: FlattenInto<TData, AppendError = Name::AppendError>,
{
type AppendError = Name::AppendError;
fn try_flatten_into(
self,
) -> Result<Record<TName, TData>, Name::AppendError> {
Ok(Record::new(
self.owner.try_flatten_into()?,
self.class,
self.ttl,
self.data.try_flatten_into()?,
))
}
}
impl<N, NN, D, DD> PartialEq<Record<NN, DD>> for Record<N, D>
where
N: PartialEq<NN>,
D: RecordData + PartialEq<DD>,
DD: RecordData,
{
fn eq(&self, other: &Record<NN, DD>) -> bool {
self.owner == other.owner
&& self.class == other.class
&& self.data == other.data
}
}
impl<N: Eq, D: RecordData + Eq> Eq for Record<N, D> {}
impl<N, NN, D, DD> PartialOrd<Record<NN, DD>> for Record<N, D>
where
N: PartialOrd<NN>,
D: RecordData + PartialOrd<DD>,
DD: RecordData,
{
fn partial_cmp(&self, other: &Record<NN, DD>) -> Option<Ordering> {
match self.owner.partial_cmp(&other.owner) {
Some(Ordering::Equal) => {}
res => return res,
}
match self.class.partial_cmp(&other.class) {
Some(Ordering::Equal) => {}
res => return res,
}
self.data.partial_cmp(&other.data)
}
}
impl<N, D> Ord for Record<N, D>
where
N: Ord,
D: RecordData + Ord,
{
fn cmp(&self, other: &Self) -> Ordering {
match self.owner.cmp(&other.owner) {
Ordering::Equal => {}
res => return res,
}
match self.class.cmp(&other.class) {
Ordering::Equal => {}
res => return res,
}
self.data.cmp(&other.data)
}
}
impl<N, NN, D, DD> CanonicalOrd<Record<NN, DD>> for Record<N, D>
where
N: ToName,
NN: ToName,
D: RecordData + CanonicalOrd<DD>,
DD: RecordData,
{
fn canonical_cmp(&self, other: &Record<NN, DD>) -> Ordering {
match self.class.cmp(&other.class) {
Ordering::Equal => {}
res => return res,
}
match self.owner.name_cmp(&other.owner) {
Ordering::Equal => {}
res => return res,
}
match self.rtype().cmp(&other.rtype()) {
Ordering::Equal => {}
res => return res,
}
self.data.canonical_cmp(&other.data)
}
}
impl<Name, Data> hash::Hash for Record<Name, Data>
where
Name: hash::Hash,
Data: hash::Hash,
{
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.owner.hash(state);
self.class.hash(state);
self.ttl.hash(state);
self.data.hash(state);
}
}
impl<Name, Data> fmt::Display for Record<Name, Data>
where
Name: fmt::Display,
Data: RecordData + fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}. {} {} {} {}",
self.owner,
self.ttl.as_secs(),
self.class,
self.data.rtype(),
self.data
)
}
}
impl<Name, Data> fmt::Debug for Record<Name, Data>
where
Name: fmt::Debug,
Data: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Record")
.field("owner", &self.owner)
.field("class", &self.class)
.field("ttl", &self.ttl)
.field("data", &self.data)
.finish()
}
}
impl<Name, Data> ZonefileFmt for Record<Name, Data>
where
Name: ToName,
Data: RecordData + ZonefileFmt,
{
fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
p.write_token(self.owner.fmt_with_dot())?;
p.write_show(self.ttl)?;
p.write_show(self.class)?;
p.write_show(self.data.rtype())?;
p.write_show(&self.data)
}
}
pub trait ComposeRecord {
fn compose_record<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError>;
}
impl<T: ComposeRecord> ComposeRecord for &T {
fn compose_record<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
(*self).compose_record(target)
}
}
impl<Name, Data> ComposeRecord for Record<Name, Data>
where
Name: ToName,
Data: ComposeRecordData,
{
fn compose_record<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
self.compose(target)
}
}
impl<Name, Data> ComposeRecord for (Name, Class, u32, Data)
where
Name: ToName,
Data: ComposeRecordData,
{
fn compose_record<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
Record::new(&self.0, self.1, Ttl::from_secs(self.2), &self.3)
.compose(target)
}
}
impl<Name, Data> ComposeRecord for (Name, Class, Ttl, Data)
where
Name: ToName,
Data: ComposeRecordData,
{
fn compose_record<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
Record::new(&self.0, self.1, self.2, &self.3).compose(target)
}
}
impl<Name, Data> ComposeRecord for (Name, u32, Data)
where
Name: ToName,
Data: ComposeRecordData,
{
fn compose_record<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
Record::new(&self.0, Class::IN, Ttl::from_secs(self.1), &self.2)
.compose(target)
}
}
impl<Name, Data> ComposeRecord for (Name, Ttl, Data)
where
Name: ToName,
Data: ComposeRecordData,
{
fn compose_record<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
Record::new(&self.0, Class::IN, self.1, &self.2).compose(target)
}
}
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RecordHeader<Name> {
owner: Name,
rtype: Rtype,
class: Class,
ttl: Ttl,
rdlen: u16,
}
impl<Name> RecordHeader<Name> {
pub fn new(
owner: Name,
rtype: Rtype,
class: Class,
ttl: Ttl,
rdlen: u16,
) -> Self {
RecordHeader {
owner,
rtype,
class,
ttl,
rdlen,
}
}
}
impl<'a, Octs: Octets + ?Sized> RecordHeader<ParsedName<&'a Octs>> {
fn deref_owner(&self) -> RecordHeader<ParsedName<Octs::Range<'a>>> {
RecordHeader {
owner: self.owner.deref_octets(),
rtype: self.rtype,
class: self.class,
ttl: self.ttl,
rdlen: self.rdlen,
}
}
}
impl<Name> RecordHeader<Name> {
pub fn owner(&self) -> &Name {
&self.owner
}
pub fn rtype(&self) -> Rtype {
self.rtype
}
pub fn class(&self) -> Class {
self.class
}
pub fn ttl(&self) -> Ttl {
self.ttl
}
pub fn rdlen(&self) -> u16 {
self.rdlen
}
pub fn into_record<Data>(self, data: Data) -> Record<Name, Data> {
Record::new(self.owner, self.class, self.ttl, data)
}
}
impl<Octs> RecordHeader<ParsedName<Octs>> {
pub fn parse<'a, Src: Octets<Range<'a> = Octs>>(
parser: &mut Parser<'a, Src>,
) -> Result<Self, ParseError> {
RecordHeader::parse_ref(parser).map(|res| res.deref_owner())
}
}
impl<'a, Octs: AsRef<[u8]> + ?Sized> RecordHeader<ParsedName<&'a Octs>> {
pub fn parse_ref(
parser: &mut Parser<'a, Octs>,
) -> Result<Self, ParseError> {
Ok(RecordHeader::new(
ParsedName::parse_ref(parser)?,
Rtype::parse(parser)?,
Class::parse(parser)?,
Ttl::parse(parser)?,
parser.parse_u16_be()?,
))
}
}
impl<Name> RecordHeader<Name> {
pub fn parse_and_skip<'a, Octs>(
parser: &mut Parser<'a, Octs>,
) -> Result<Self, ParseError>
where
Self: Parse<'a, Octs>,
Octs: Octets,
{
let header = Self::parse(parser)?;
match parser.advance(header.rdlen() as usize) {
Ok(()) => Ok(header),
Err(_) => Err(ParseError::ShortInput),
}
}
}
impl RecordHeader<()> {
fn parse_rdlen<Octs: Octets + ?Sized>(
parser: &mut Parser<'_, Octs>,
) -> Result<u16, ParseError> {
ParsedName::skip(parser)?;
parser.advance(
(Rtype::COMPOSE_LEN + Class::COMPOSE_LEN + u32::COMPOSE_LEN)
.into(),
)?;
u16::parse(parser)
}
}
impl<Octs> RecordHeader<ParsedName<Octs>> {
pub fn parse_into_record<'a, Src, Data>(
self,
parser: &mut Parser<'a, Src>,
) -> Result<Option<Record<ParsedName<Octs>, Data>>, ParseError>
where
Src: AsRef<[u8]> + ?Sized,
Data: ParseRecordData<'a, Src>,
{
let mut parser = parser.parse_parser(self.rdlen as usize)?;
let res = Data::parse_rdata(self.rtype, &mut parser)?
.map(|data| Record::new(self.owner, self.class, self.ttl, data));
if res.is_some() && parser.remaining() > 0 {
return Err(ParseError::Form(FormError::new(
"trailing data in option",
)));
}
Ok(res)
}
pub fn parse_into_any_record<'a, Src, Data>(
self,
parser: &mut Parser<'a, Src>,
) -> Result<Record<ParsedName<Octs>, Data>, ParseError>
where
Src: AsRef<[u8]> + ?Sized,
Data: ParseAnyRecordData<'a, Src>,
{
let mut parser = parser.parse_parser(self.rdlen as usize)?;
let res = Record::new(
self.owner,
self.class,
self.ttl,
Data::parse_any_rdata(self.rtype, &mut parser)?,
);
if parser.remaining() > 0 {
return Err(ParseError::Form(FormError::new(
"trailing data in option",
)));
}
Ok(res)
}
}
impl<Name: ToName> RecordHeader<Name> {
pub fn compose<Target: Composer + ?Sized>(
&self,
buf: &mut Target,
) -> Result<(), Target::AppendError> {
buf.append_compressed_name(&self.owner)?;
self.rtype.compose(buf)?;
self.class.compose(buf)?;
self.ttl.compose(buf)?;
self.rdlen.compose(buf)
}
pub fn compose_canonical<Target: Composer + ?Sized>(
&self,
buf: &mut Target,
) -> Result<(), Target::AppendError> {
self.owner.compose_canonical(buf)?;
self.rtype.compose(buf)?;
self.class.compose(buf)?;
self.ttl.compose(buf)?;
self.rdlen.compose(buf)
}
}
impl<Name, NName> PartialEq<RecordHeader<NName>> for RecordHeader<Name>
where
Name: ToName,
NName: ToName,
{
fn eq(&self, other: &RecordHeader<NName>) -> bool {
self.owner.name_eq(&other.owner)
&& self.rtype == other.rtype
&& self.class == other.class
&& self.ttl == other.ttl
&& self.rdlen == other.rdlen
}
}
impl<Name: ToName> Eq for RecordHeader<Name> {}
impl<Name, NName> PartialOrd<RecordHeader<NName>> for RecordHeader<Name>
where
Name: ToName,
NName: ToName,
{
fn partial_cmp(&self, other: &RecordHeader<NName>) -> Option<Ordering> {
match self.owner.name_cmp(&other.owner) {
Ordering::Equal => {}
other => return Some(other),
}
match self.rtype.partial_cmp(&other.rtype) {
Some(Ordering::Equal) => {}
other => return other,
}
match self.class.partial_cmp(&other.class) {
Some(Ordering::Equal) => {}
other => return other,
}
match self.ttl.partial_cmp(&other.ttl) {
Some(Ordering::Equal) => {}
other => return other,
}
self.rdlen.partial_cmp(&other.rdlen)
}
}
impl<Name: ToName> Ord for RecordHeader<Name> {
fn cmp(&self, other: &Self) -> Ordering {
match self.owner.name_cmp(&other.owner) {
Ordering::Equal => {}
other => return other,
}
match self.rtype.cmp(&other.rtype) {
Ordering::Equal => {}
other => return other,
}
match self.class.cmp(&other.class) {
Ordering::Equal => {}
other => return other,
}
match self.ttl.cmp(&other.ttl) {
Ordering::Equal => {}
other => return other,
}
self.rdlen.cmp(&other.rdlen)
}
}
impl<Name: hash::Hash> hash::Hash for RecordHeader<Name> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.owner.hash(state);
self.rtype.hash(state);
self.class.hash(state);
self.ttl.hash(state);
self.rdlen.hash(state);
}
}
impl<Name: fmt::Debug> fmt::Debug for RecordHeader<Name> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RecordHeader")
.field("owner", &self.owner)
.field("rtype", &self.rtype)
.field("class", &self.class)
.field("ttl", &self.ttl)
.field("rdlen", &self.rdlen)
.finish()
}
}
#[derive(Clone)]
pub struct ParsedRecord<'a, Octs: Octets + ?Sized> {
header: RecordHeader<ParsedName<&'a Octs>>,
data: Parser<'a, Octs>,
}
impl<'a, Octs: Octets + ?Sized> ParsedRecord<'a, Octs> {
#[must_use]
pub fn new(
header: RecordHeader<ParsedName<&'a Octs>>,
data: Parser<'a, Octs>,
) -> Self {
ParsedRecord { header, data }
}
#[must_use]
pub fn owner(&self) -> ParsedName<&'a Octs> {
*self.header.owner()
}
#[must_use]
pub fn rtype(&self) -> Rtype {
self.header.rtype()
}
#[must_use]
pub fn class(&self) -> Class {
self.header.class()
}
#[must_use]
pub fn ttl(&self) -> Ttl {
self.header.ttl()
}
#[must_use]
pub fn rdlen(&self) -> u16 {
self.header.rdlen()
}
}
impl<'a, Octs: Octets + ?Sized> ParsedRecord<'a, Octs> {
#[allow(clippy::type_complexity)]
pub fn to_record<Data>(
&self,
) -> Result<Option<Record<ParsedName<Octs::Range<'_>>, Data>>, ParseError>
where
Data: ParseRecordData<'a, Octs>,
{
self.header
.deref_owner()
.parse_into_record(&mut self.data.clone())
}
pub fn to_any_record<Data>(
&self,
) -> Result<Record<ParsedName<Octs::Range<'_>>, Data>, ParseError>
where
Data: ParseAnyRecordData<'a, Octs>,
{
self.header
.deref_owner()
.parse_into_any_record(&mut self.data.clone())
}
#[allow(clippy::type_complexity)]
pub fn into_record<Data>(
mut self,
) -> Result<Option<Record<ParsedName<Octs::Range<'a>>, Data>>, ParseError>
where
Data: ParseRecordData<'a, Octs>,
{
self.header.deref_owner().parse_into_record(&mut self.data)
}
pub fn into_any_record<Data>(
mut self,
) -> Result<Record<ParsedName<Octs::Range<'a>>, Data>, ParseError>
where
Data: ParseAnyRecordData<'a, Octs>,
{
self.header
.deref_owner()
.parse_into_any_record(&mut self.data)
}
}
impl<'a, Octs: Octets + ?Sized> ParsedRecord<'a, Octs> {
pub fn parse(parser: &mut Parser<'a, Octs>) -> Result<Self, ParseError> {
let header = RecordHeader::parse_ref(parser)?;
let data = *parser;
parser.advance(header.rdlen() as usize)?;
Ok(Self::new(header, data))
}
pub fn skip(parser: &mut Parser<'a, Octs>) -> Result<(), ParseError> {
let rdlen = RecordHeader::parse_rdlen(parser)?;
parser.advance(rdlen as usize)?;
Ok(())
}
}
impl<'o, Octs, Other> PartialEq<ParsedRecord<'o, Other>>
for ParsedRecord<'_, Octs>
where
Octs: Octets + ?Sized,
Other: Octets + ?Sized,
{
fn eq(&self, other: &ParsedRecord<'o, Other>) -> bool {
self.header == other.header
&& self
.data
.peek(self.header.rdlen() as usize)
.eq(&other.data.peek(other.header.rdlen() as usize))
}
}
impl<Octs: Octets + ?Sized> Eq for ParsedRecord<'_, Octs> {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RecordParseError<N, D> {
Name(N),
Data(D),
ShortBuf,
}
impl<N, D> fmt::Display for RecordParseError<N, D>
where
N: fmt::Display,
D: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
RecordParseError::Name(ref name) => name.fmt(f),
RecordParseError::Data(ref data) => data.fmt(f),
RecordParseError::ShortBuf => {
f.write_str("unexpected end of buffer")
}
}
}
}
#[cfg(feature = "std")]
impl<N, D> std::error::Error for RecordParseError<N, D>
where
N: std::error::Error,
D: std::error::Error,
{
}
impl<N, D> From<ShortBuf> for RecordParseError<N, D> {
fn from(_: ShortBuf) -> Self {
RecordParseError::ShortBuf
}
}
const SECS_PER_MINUTE: u32 = 60;
const SECS_PER_HOUR: u32 = 3600;
const SECS_PER_DAY: u32 = 86400;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default,
)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Ttl(u32);
impl Ttl {
pub const SECOND: Ttl = Ttl::from_secs(1);
pub const MINUTE: Ttl = Ttl::from_mins(1);
pub const HOUR: Ttl = Ttl::from_hours(1);
pub const DAY: Ttl = Ttl::from_days(1);
pub const ZERO: Ttl = Ttl::from_secs(0);
pub const MAX: Ttl = Ttl::from_secs(u32::MAX);
pub const CAP: Ttl = Ttl::from_secs(604_800);
pub const MAX_MINUTES: u32 = 71582788;
pub const MAX_HOURS: u32 = 1193046;
pub const MAX_DAYS: u16 = 49710;
pub const COMPOSE_LEN: u16 = 4;
#[must_use]
#[inline]
pub const fn as_secs(&self) -> u32 {
self.0
}
#[must_use]
#[inline]
pub const fn as_minutes(&self) -> u32 {
self.0 / SECS_PER_MINUTE
}
#[must_use]
#[inline]
pub const fn as_hours(&self) -> u32 {
self.0 / SECS_PER_HOUR
}
#[must_use]
#[inline]
pub const fn as_days(&self) -> u16 {
(self.0 / SECS_PER_DAY) as u16
}
#[must_use]
#[inline]
pub const fn into_duration(&self) -> Duration {
Duration::from_secs(self.0 as u64)
}
#[must_use]
#[inline]
pub const fn from_secs(secs: u32) -> Self {
Self(secs)
}
#[must_use]
#[inline]
pub const fn from_mins(minutes: u32) -> Self {
assert!(minutes <= 71582788);
Self(minutes * SECS_PER_MINUTE)
}
#[must_use]
#[inline]
pub const fn from_hours(hours: u32) -> Self {
assert!(hours <= 1193046);
Self(hours * SECS_PER_HOUR)
}
#[must_use]
#[inline]
pub const fn from_days(days: u16) -> Self {
assert!(days <= 49710);
Self(days as u32 * SECS_PER_DAY)
}
#[must_use]
#[inline]
pub const fn from_duration_lossy(duration: Duration) -> Self {
Self(duration.as_secs() as u32)
}
#[must_use]
#[inline]
pub const fn is_zero(&self) -> bool {
self.0 == 0
}
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const fn checked_add(self, rhs: Ttl) -> Option<Ttl> {
if let Some(secs) = self.0.checked_add(rhs.0) {
Some(Ttl(secs))
} else {
None
}
}
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const fn saturating_add(self, rhs: Ttl) -> Ttl {
match self.0.checked_add(rhs.0) {
Some(secs) => Ttl(secs),
None => Ttl::MAX,
}
}
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const fn checked_sub(self, rhs: Ttl) -> Option<Ttl> {
if let Some(secs) = self.0.checked_sub(rhs.0) {
Some(Ttl(secs))
} else {
None
}
}
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const fn saturating_sub(self, rhs: Ttl) -> Ttl {
match self.0.checked_sub(rhs.0) {
Some(secs) => Ttl(secs),
None => Ttl::ZERO,
}
}
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const fn checked_mul(self, rhs: u32) -> Option<Ttl> {
if let Some(secs) = self.0.checked_mul(rhs) {
Some(Ttl(secs))
} else {
None
}
}
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const fn saturating_mul(self, rhs: u32) -> Ttl {
match self.0.checked_mul(rhs) {
Some(secs) => Ttl(secs),
None => Ttl::MAX,
}
}
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const fn checked_div(self, rhs: u32) -> Option<Ttl> {
match self.0.checked_div(rhs) {
Some(ttl) => Some(Ttl(ttl)),
None => None,
}
}
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const fn cap(self) -> Ttl {
if self.0 > Self::CAP.0 {
Self::CAP
} else {
self
}
}
pub fn compose<Target: OctetsBuilder + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
target.append_slice(&(self.as_secs()).to_be_bytes())
}
pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
parser: &mut Parser<'_, Octs>,
) -> Result<Self, ParseError> {
parser
.parse_u32_be()
.map(Ttl::from_secs)
.map_err(Into::into)
}
pub(crate) fn pretty(&self) -> impl fmt::Display {
struct Inner {
inner: Ttl,
}
impl fmt::Display for Inner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let days = self.inner.as_days();
let weeks = days / 7;
let days = days % 7;
let hours = self.inner.as_hours() % 24;
let minutes = self.inner.as_minutes() % 60;
let seconds = self.inner.as_secs() % 60;
let mut first = true;
for (n, unit) in [
(weeks, "week"),
(days, "day"),
(hours as u16, "hour"),
(minutes as u16, "minute"),
(seconds as u16, "second"),
] {
if n == 0 {
continue;
}
if first {
write!(f, " ")?;
}
let s = if n > 1 { "s" } else { "" };
write!(f, "{n} {unit}{s}")?;
first = false;
}
Ok(())
}
}
Inner { inner: *self }
}
}
impl ZonefileFmt for Ttl {
fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
p.write_token(self.as_secs())
}
}
impl core::ops::Add for Ttl {
type Output = Ttl;
fn add(self, rhs: Self) -> Self::Output {
self.checked_add(rhs)
.expect("overflow when adding durations")
}
}
impl core::ops::AddAssign for Ttl {
fn add_assign(&mut self, rhs: Ttl) {
*self = *self + rhs;
}
}
impl core::ops::Sub for Ttl {
type Output = Ttl;
fn sub(self, rhs: Self) -> Self::Output {
self.checked_sub(rhs)
.expect("overflow when subtracting durations")
}
}
impl core::ops::SubAssign for Ttl {
fn sub_assign(&mut self, rhs: Ttl) {
*self = *self - rhs;
}
}
impl core::ops::Mul<u32> for Ttl {
type Output = Ttl;
fn mul(self, rhs: u32) -> Self::Output {
self.checked_mul(rhs)
.expect("overflow when multiplying duration by scalar")
}
}
impl core::ops::MulAssign<u32> for Ttl {
fn mul_assign(&mut self, rhs: u32) {
*self = *self * rhs;
}
}
impl core::ops::Div<u32> for Ttl {
type Output = Ttl;
fn div(self, rhs: u32) -> Ttl {
self.checked_div(rhs)
.expect("divide by zero error when dividing duration by scalar")
}
}
impl core::ops::DivAssign<u32> for Ttl {
fn div_assign(&mut self, rhs: u32) {
*self = *self / rhs;
}
}
macro_rules! sum_durations {
($iter:expr) => {{
let mut total_secs: u32 = 0;
for entry in $iter {
total_secs = total_secs
.checked_add(entry.0)
.expect("overflow in iter::sum over durations");
}
Ttl(total_secs)
}};
}
impl core::iter::Sum for Ttl {
fn sum<I: Iterator<Item = Ttl>>(iter: I) -> Ttl {
sum_durations!(iter)
}
}
impl<'a> core::iter::Sum<&'a Ttl> for Ttl {
fn sum<I: Iterator<Item = &'a Ttl>>(iter: I) -> Ttl {
sum_durations!(iter)
}
}
impl From<Ttl> for Duration {
fn from(value: Ttl) -> Self {
Duration::from_secs(u64::from(value.0))
}
}
#[cfg(test)]
mod test {
#[test]
#[cfg(feature = "bytes")]
fn ds_octets_into() {
use super::*;
use crate::base::iana::{Class, DigestAlgorithm, SecurityAlgorithm};
use crate::base::name::Name;
use crate::rdata::Ds;
use bytes::Bytes;
use octseq::octets::OctetsInto;
let ds: Record<Name<&[u8]>, Ds<&[u8]>> = Record::new(
Name::from_octets(b"\x01a\x07example\0".as_ref()).unwrap(),
Class::IN,
Ttl::from_secs(86400),
Ds::new(
12,
SecurityAlgorithm::RSASHA256,
DigestAlgorithm::SHA256,
b"something".as_ref(),
)
.unwrap(),
);
let ds_bytes: Record<Name<Bytes>, Ds<Bytes>> =
ds.clone().octets_into();
assert_eq!(ds.owner(), ds_bytes.owner());
assert_eq!(ds.data().digest(), ds_bytes.data().digest());
}
}