use crate::base::{
charstr::DisplayQuoted,
name::FlattenInto,
rdata::ComposeRecordData,
scan::{Scan, Scanner, ScannerError},
wire::{Compose, Parse, ParseError},
zonefile_fmt::{self, Formatter, ZonefileFmt},
CanonicalOrd, CharStr, ParseRecordData, RecordData, Rtype,
};
use core::{cmp::Ordering, fmt, hash};
#[cfg(feature = "serde")]
use octseq::{
builder::{EmptyBuilder, FromBuilder},
serde::DeserializeOctets,
serde::SerializeOctets,
};
use octseq::{Octets, OctetsBuilder, OctetsFrom, OctetsInto, Parser};
#[derive(Clone)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(bound(
serialize = "
Octs: SerializeOctets + AsRef<[u8]>
",
deserialize = "
Octs: FromBuilder + DeserializeOctets<'de>,
<Octs as FromBuilder>::Builder:
OctetsBuilder + EmptyBuilder
+ AsRef<[u8]>,
",
))
)]
pub struct Caa<Octs> {
flags: CaaFlags,
tag: CaaTag<Octs>,
#[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>",
)
)
)]
value: Octs,
}
impl Caa<()> {
pub const RTYPE: Rtype = Rtype::CAA;
}
impl<Octs> Caa<Octs> {
pub fn new(flags: CaaFlags, tag: CaaTag<Octs>, value: Octs) -> Self {
Caa { flags, tag, value }
}
pub fn flags(&self) -> CaaFlags {
self.flags
}
pub fn tag(&self) -> &CaaTag<Octs> {
&self.tag
}
pub fn value(&self) -> &Octs {
&self.value
}
pub(in crate::rdata) fn convert_octets<TOcts: OctetsFrom<Octs>>(
self,
) -> Result<Caa<TOcts>, TOcts::Error> {
Ok(Caa::new(
self.flags,
self.tag.try_octets_into()?,
self.value.try_octets_into()?,
))
}
pub(in crate::rdata) fn flatten<TOcts: OctetsFrom<Octs>>(
self,
) -> Result<Caa<TOcts>, TOcts::Error> {
self.convert_octets()
}
pub fn scan<S: Scanner<Octets = Octs>>(
scanner: &mut S,
) -> Result<Self, S::Error>
where
Octs: AsRef<[u8]>,
{
Ok(Self::new(
CaaFlags::scan(scanner)?,
CaaTag::scan(scanner)?,
scanner.scan_octets()?,
))
}
pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
parser: &mut Parser<'a, Src>,
) -> Result<Self, ParseError>
where
Octs: AsRef<[u8]>,
{
Ok(Self::new(
CaaFlags::parse(parser)?,
CaaTag::parse(parser)?,
parser.parse_octets(parser.remaining())?,
))
}
}
impl<Octs, SrcOcts> OctetsFrom<Caa<SrcOcts>> for Caa<Octs>
where
Octs: OctetsFrom<SrcOcts>,
{
type Error = Octs::Error;
fn try_octets_from(source: Caa<SrcOcts>) -> Result<Self, Self::Error> {
Ok(Caa {
flags: source.flags,
tag: CaaTag::try_octets_from(source.tag)?,
value: Octs::try_octets_from(source.value)?,
})
}
}
impl<Octs, TOcts> FlattenInto<Caa<TOcts>> for Caa<Octs>
where
TOcts: OctetsFrom<Octs>,
{
type AppendError = TOcts::Error;
fn try_flatten_into(self) -> Result<Caa<TOcts>, Self::AppendError> {
self.flatten()
}
}
impl<Octs, OtherOcts> PartialEq<Caa<OtherOcts>> for Caa<Octs>
where
Octs: AsRef<[u8]>,
OtherOcts: AsRef<[u8]>,
{
fn eq(&self, other: &Caa<OtherOcts>) -> bool {
self.flags == other.flags
&& self.tag.eq(&other.tag)
&& self.value.as_ref().eq(other.value.as_ref())
}
}
impl<O: AsRef<[u8]>> Eq for Caa<O> {}
impl<Octs, OtherOcts> PartialOrd<Caa<OtherOcts>> for Caa<Octs>
where
Octs: AsRef<[u8]>,
OtherOcts: AsRef<[u8]>,
{
fn partial_cmp(&self, other: &Caa<OtherOcts>) -> Option<Ordering> {
match self.flags.partial_cmp(&other.flags) {
Some(Ordering::Equal) => (),
other => return other,
}
match self.tag.partial_cmp(&other.tag) {
Some(Ordering::Equal) => (),
other => return other,
}
self.value.as_ref().partial_cmp(other.value.as_ref())
}
}
impl<Octs, OtherOcts> CanonicalOrd<Caa<OtherOcts>> for Caa<Octs>
where
Octs: AsRef<[u8]>,
OtherOcts: AsRef<[u8]>,
{
fn canonical_cmp(&self, other: &Caa<OtherOcts>) -> Ordering {
match self.flags.cmp(&other.flags) {
Ordering::Equal => (),
ord => return ord,
}
match self.tag.canonical_cmp(&other.tag) {
Ordering::Equal => (),
ord => return ord,
}
self.value.as_ref().cmp(other.value.as_ref())
}
}
impl<O: AsRef<[u8]>> Ord for Caa<O> {
fn cmp(&self, other: &Self) -> Ordering {
match self.flags.cmp(&other.flags) {
Ordering::Equal => (),
ord => return ord,
}
match self.tag.cmp(&other.tag) {
Ordering::Equal => (),
ord => return ord,
}
self.value.as_ref().cmp(other.value.as_ref())
}
}
impl<O: AsRef<[u8]>> hash::Hash for Caa<O> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.flags.hash(state);
self.tag.hash(state);
self.value.as_ref().hash(state);
}
}
impl<Octs> RecordData for Caa<Octs> {
fn rtype(&self) -> Rtype {
Caa::RTYPE
}
}
impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
for Caa<Octs::Range<'a>>
{
fn parse_rdata(
rtype: Rtype,
parser: &mut octseq::Parser<'a, Octs>,
) -> Result<Option<Self>, crate::base::wire::ParseError> {
if rtype == Caa::RTYPE {
Self::parse(parser).map(Some)
} else {
Ok(None)
}
}
}
impl<Octs: AsRef<[u8]>> ComposeRecordData for Caa<Octs> {
fn rdlen(&self, _compress: bool) -> Option<u16> {
Some(
u8::COMPOSE_LEN
.checked_add(self.tag.compose_len())
.expect("long tag")
.checked_add(
u16::try_from(self.value.as_ref().len())
.expect("long value"),
)
.expect("long value"),
)
}
fn compose_rdata<Target: crate::base::wire::Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
self.flags.compose(target)?;
self.tag.compose(target)?;
target.append_slice(self.value.as_ref())
}
fn compose_canonical_rdata<
Target: crate::base::wire::Composer + ?Sized,
>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
self.flags.compose(target)?;
self.tag.compose(target)?;
target.append_slice(self.value.as_ref())
}
}
impl<O: AsRef<[u8]>> fmt::Display for Caa<O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {} {}",
self.flags,
self.tag,
DisplayQuoted::from_slice(self.value.as_ref()),
)
}
}
impl<O: AsRef<[u8]>> fmt::Debug for Caa<O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Caa")
.field("flags", &self.flags)
.field("tag", &self.tag)
.field("value", &DisplayQuoted::from_slice(self.value.as_ref()))
.finish()
}
}
impl<O: AsRef<[u8]>> ZonefileFmt for Caa<O> {
fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
p.block(|p| {
p.write_token(self.flags)?;
p.write_comment("flags")?;
p.write_token(&self.tag)?;
p.write_comment("tag")?;
p.write_token(DisplayQuoted::from_slice(self.value.as_ref()))?;
p.write_comment("value")
})
}
}
#[derive(Clone)]
#[repr(transparent)]
pub struct CaaTag<Octs: ?Sized>(CharStr<Octs>);
impl<Octs> CaaTag<Octs> {
pub fn new(charstr: CharStr<Octs>) -> Result<Self, ParseError>
where
Octs: AsRef<[u8]>,
{
CaaTag::check_slice(charstr.as_slice())?;
Ok(CaaTag(charstr))
}
pub fn from_octets(octets: Octs) -> Result<Self, ParseError>
where
Octs: AsRef<[u8]>,
{
CaaTag::check_slice(octets.as_ref())?;
Ok(unsafe { Self::from_octets_unchecked(octets) })
}
pub unsafe fn from_octets_unchecked(octets: Octs) -> Self {
CaaTag(CharStr::from_octets_unchecked(octets))
}
}
impl CaaTag<[u8]> {
pub fn from_slice(slice: &[u8]) -> Result<&Self, ParseError> {
Self::check_slice(slice)?;
Ok(unsafe { Self::from_slice_unchecked(slice) })
}
pub unsafe fn from_slice_unchecked(slice: &[u8]) -> &Self {
&*(CharStr::from_slice_unchecked(slice) as *const CharStr<[u8]>
as *const Self)
}
fn check_slice(octets: &[u8]) -> Result<(), ParseError> {
if octets.iter().any(|e| !e.is_ascii_alphanumeric()) {
return Err(ParseError::form_error(
"CAA tag contains invalid character",
));
}
Ok(())
}
}
impl<Octs: AsRef<[u8]>> CaaTag<Octs> {
pub fn compose_len(&self) -> u16 {
self.0.compose_len()
}
pub fn compose<Target: OctetsBuilder + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
self.0.compose(target)
}
pub fn scan<S: Scanner<Octets = Octs>>(
scanner: &mut S,
) -> Result<Self, S::Error> {
let octets = CharStr::scan(scanner)?;
CaaTag::check_slice(octets.as_slice()).map_err(|_| {
S::Error::custom("CAA tag contains invalid character")
})?;
Ok(CaaTag(octets))
}
pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
parser: &mut Parser<'a, Src>,
) -> Result<Self, ParseError> {
Self::new(CharStr::parse(parser)?)
}
}
impl<Octs, SrcOcts> OctetsFrom<CaaTag<SrcOcts>> for CaaTag<Octs>
where
Octs: OctetsFrom<SrcOcts>,
{
type Error = Octs::Error;
fn try_octets_from(source: CaaTag<SrcOcts>) -> Result<Self, Self::Error> {
Ok(CaaTag(CharStr::try_octets_from(source.0)?))
}
}
impl<Octs, OtherOcts> PartialEq<CaaTag<OtherOcts>> for CaaTag<Octs>
where
Octs: AsRef<[u8]>,
OtherOcts: AsRef<[u8]>,
{
fn eq(&self, other: &CaaTag<OtherOcts>) -> bool {
self.0.eq(&other.0)
}
}
impl<O: AsRef<[u8]>> Eq for CaaTag<O> {}
impl<Octs, OtherOcts> PartialOrd<CaaTag<OtherOcts>> for CaaTag<Octs>
where
Octs: AsRef<[u8]>,
OtherOcts: AsRef<[u8]>,
{
fn partial_cmp(&self, other: &CaaTag<OtherOcts>) -> Option<Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl<Octs, OtherOcts> CanonicalOrd<CaaTag<OtherOcts>> for CaaTag<Octs>
where
Octs: AsRef<[u8]>,
OtherOcts: AsRef<[u8]>,
{
fn canonical_cmp(&self, other: &CaaTag<OtherOcts>) -> Ordering {
self.0.canonical_cmp(&other.0)
}
}
impl<O: AsRef<[u8]>> Ord for CaaTag<O> {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl<O: AsRef<[u8]>> hash::Hash for CaaTag<O> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<O: AsRef<[u8]>> fmt::Display for CaaTag<O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<O: AsRef<[u8]>> fmt::Debug for CaaTag<O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("CaaTag").field(&self.0).finish()
}
}
#[cfg(feature = "serde")]
impl<Octs> serde::Serialize for CaaTag<Octs>
where
Octs: AsRef<[u8]> + octseq::serde::SerializeOctets,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, Octs> serde::Deserialize<'de> for CaaTag<Octs>
where
Octs: FromBuilder + octseq::serde::DeserializeOctets<'de>,
<Octs as FromBuilder>::Builder: AsRef<[u8]> + EmptyBuilder,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Self::new(CharStr::deserialize(deserializer)?)
.map_err(serde::de::Error::custom)
}
}
#[derive(
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default,
)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CaaFlags(u8);
impl CaaFlags {
pub fn new(bits: u8) -> Self {
CaaFlags(bits)
}
pub fn critical() -> Self {
CaaFlags(0x80)
}
pub fn bits(&self) -> u8 {
self.0
}
}
impl fmt::Display for CaaFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Compose for CaaFlags {
fn compose<Target: octseq::OctetsBuilder + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
self.0.compose(target)
}
}
impl<S: Scanner> Scan<S> for CaaFlags {
fn scan(scanner: &mut S) -> Result<Self, S::Error> {
Ok(CaaFlags(u8::scan(scanner)?))
}
}
impl<'a, Octs: AsRef<[u8]> + ?Sized> Parse<'a, Octs> for CaaFlags {
fn parse(parser: &mut Parser<'a, Octs>) -> Result<Self, ParseError> {
Ok(CaaFlags(u8::parse(parser)?))
}
}
#[cfg(test)]
#[cfg(all(feature = "std", feature = "bytes"))]
mod test {
use super::*;
use crate::std::string::ToString;
use octseq::array::Array;
#[test]
fn caa_eq() {
let caa1 = Caa::new(
CaaFlags::default(),
CaaTag::from_octets("ISSUE".as_bytes()).unwrap(),
"ca.example.net".as_bytes(),
);
let caa2 = Caa::new(
CaaFlags::default(),
CaaTag::from_octets("issue".as_bytes()).unwrap(),
"ca.example.net".as_bytes(),
);
assert_eq!(caa1, caa2);
}
#[test]
fn caa_octets_info() {
let caa = Caa::new(
CaaFlags::default(),
CaaTag::from_octets("issue".as_bytes()).unwrap(),
"ca.example.net".as_bytes(),
);
let caa_bytes: Caa<bytes::Bytes> = caa.clone().octets_into();
assert_eq!(caa.flags, caa_bytes.flags);
assert_eq!(caa.tag, caa_bytes.tag);
assert_eq!(caa.value, caa_bytes.value);
}
#[test]
fn caa_display() {
let caa = Caa::new(
CaaFlags::default(),
CaaTag::from_octets("issue".as_bytes()).unwrap(),
"ca.example.net".as_bytes(),
);
assert_eq!(caa.to_string(), r#"0 issue "ca.example.net""#);
}
#[test]
fn caa_tag_creation_and_validation() {
assert!(CaaTag::from_octets("issue".as_bytes()).is_ok());
assert!(CaaTag::from_octets("bad tag".as_bytes()).is_err());
}
#[test]
fn caa_tag_display_and_debug() {
let tag = CaaTag::from_octets("ISSUE".as_bytes()).unwrap();
assert_eq!(tag.to_string(), "ISSUE");
assert_eq!(format!("{:?}", tag), "CaaTag(CharStr(ISSUE))");
}
#[test]
fn caa_tag_compose_canonical_lowercases() {
let tag = CaaTag::from_octets("Issue".as_bytes()).unwrap();
let mut buf = Array::<8>::new();
tag.compose(&mut buf).unwrap();
assert_eq!(buf.as_ref(), &[5, b'I', b's', b's', b'u', b'e']);
}
#[test]
fn caa_flags_display() {
let flags = CaaFlags::default();
assert_eq!(flags.bits(), 0);
}
#[test]
fn caa_flags_critical() {
let flags = CaaFlags::critical();
assert_eq!(flags.bits(), 0x80);
}
}