mirror of
https://github.com/NLnetLabs/domain.git
synced 2026-09-30 13:44:59 +02:00
Add support for CAA record type (#434)
This commit is contained in:
+12
-6
@@ -398,7 +398,7 @@ impl<Octs: AsRef<[u8]> + ?Sized> CharStr<Octs> {
|
||||
/// quotes. It will escape double quotes, backslashes, and non-printable
|
||||
/// octets only.
|
||||
pub fn display_quoted(&self) -> DisplayQuoted<'_> {
|
||||
DisplayQuoted(self.for_slice())
|
||||
DisplayQuoted(self.0.as_ref())
|
||||
}
|
||||
|
||||
/// Returns an object that formats in unquoted presentation format.
|
||||
@@ -407,7 +407,7 @@ impl<Octs: AsRef<[u8]> + ?Sized> CharStr<Octs> {
|
||||
/// delimiters and escapes space, double quotes, semicolons, backslashes,
|
||||
/// and non-printable octets.
|
||||
pub fn display_unquoted(&self) -> DisplayUnquoted<'_> {
|
||||
DisplayUnquoted(self.for_slice())
|
||||
DisplayUnquoted(self.0.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,12 +927,18 @@ impl Iterator for Iter<'_> {
|
||||
///
|
||||
/// A value of this type can be obtained via `CharStr::display_quoted`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DisplayQuoted<'a>(&'a CharStr<[u8]>);
|
||||
pub struct DisplayQuoted<'a>(&'a [u8]);
|
||||
|
||||
impl<'a> DisplayQuoted<'a> {
|
||||
pub fn from_slice(octets: &'a [u8]) -> Self {
|
||||
DisplayQuoted(octets)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DisplayQuoted<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("\"")?;
|
||||
for &ch in self.0.as_ref() {
|
||||
for &ch in self.0 {
|
||||
fmt::Display::fmt(&Symbol::quoted_from_octet(ch), f)?;
|
||||
}
|
||||
f.write_str("\"")
|
||||
@@ -945,11 +951,11 @@ impl fmt::Display for DisplayQuoted<'_> {
|
||||
///
|
||||
/// A value of this type can be obtained via `CharStr::display_serialized`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DisplayUnquoted<'a>(&'a CharStr<[u8]>);
|
||||
pub struct DisplayUnquoted<'a>(&'a [u8]);
|
||||
|
||||
impl fmt::Display for DisplayUnquoted<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for &ch in self.0.as_ref() {
|
||||
for &ch in self.0 {
|
||||
fmt::Display::fmt(&Symbol::from_octet(ch), f)?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -322,6 +322,7 @@ mod test {
|
||||
use crate::base::iana::{Class, DigestAlgorithm, SecurityAlgorithm};
|
||||
use crate::base::zonefile_fmt::{DisplayKind, ZonefileFmt};
|
||||
use crate::base::{Name, Record, Ttl};
|
||||
use crate::rdata::caa::{CaaFlags, CaaTag};
|
||||
use crate::rdata::{Cds, Cname, Ds, Mx, Txt, A};
|
||||
|
||||
fn create_record<Data>(data: Data) -> Record<&'static Name<[u8]>, Data> {
|
||||
@@ -495,4 +496,18 @@ mod test {
|
||||
record.display_zonefile(DisplayKind::Tabbed).to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caa_record() {
|
||||
use crate::rdata::Caa;
|
||||
let record = create_record(Caa::new(
|
||||
CaaFlags::default(),
|
||||
CaaTag::from_octets("issue".as_bytes()).unwrap(),
|
||||
"ca.example.net".as_bytes(),
|
||||
));
|
||||
assert_eq!(
|
||||
"example.com. 3600 IN CAA 0 issue \"ca.example.net\"",
|
||||
record.display_zonefile(DisplayKind::Simple).to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,701 @@
|
||||
//! Record data from [RFC 8659]: CAA records.
|
||||
//!
|
||||
//! This RFC defines the CAA record type.
|
||||
//!
|
||||
//! [RFC 8659]: https://www.rfc-editor.org/info/rfc8659
|
||||
|
||||
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};
|
||||
|
||||
//------------ Caa ---------------------------------------------------------
|
||||
|
||||
/// Caa record data.
|
||||
///
|
||||
/// The Certification Authority Authorization (CAA) DNS Resource Record allows
|
||||
/// a DNS domain name holder to specify one or more Certification Authorities
|
||||
/// (CAs) authorized to issue certificates for that domain name.
|
||||
///
|
||||
/// CAA Resource Records allow a public CA to implement additional controls to reduce the
|
||||
/// risk of unintended certificate mis-issue.
|
||||
///
|
||||
/// The Caa record type is defined in [RFC 8659, section 4.1][1].
|
||||
///
|
||||
/// [1]: https://www.rfc-editor.org/rfc/rfc8659#section-4.1
|
||||
#[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<()> {
|
||||
/// The rtype of this record data type.
|
||||
pub const RTYPE: Rtype = Rtype::CAA;
|
||||
}
|
||||
|
||||
impl<Octs> Caa<Octs> {
|
||||
/// Creates a new CAA record data from the flags, tag, and value.
|
||||
pub fn new(flags: CaaFlags, tag: CaaTag<Octs>, value: Octs) -> Self {
|
||||
Caa { flags, tag, value }
|
||||
}
|
||||
|
||||
/// Returns the flags. If the value is set to "1", the Property is critical.
|
||||
/// A CA MUST NOT issue certificates for any FQDN if the
|
||||
/// Relevant RRset for that FQDN contains a CAA critical
|
||||
/// Property for an unknown or unsupported Property Tag.
|
||||
pub fn flags(&self) -> CaaFlags {
|
||||
self.flags
|
||||
}
|
||||
|
||||
/// Returns the Property identifier
|
||||
pub fn tag(&self) -> &CaaTag<Octs> {
|
||||
&self.tag
|
||||
}
|
||||
|
||||
/// Returns the Property Value
|
||||
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())?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
//--- OctetsFrom
|
||||
|
||||
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)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//--- FlattenInto
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
//--- PartialEq and Eq
|
||||
|
||||
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> {}
|
||||
|
||||
//--- PartialOrd, Ord, and CanonicalOrd
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
//--- Hash
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
//--- RecordData, ParseRecordData, ComposeRecordData
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
//--- Display
|
||||
|
||||
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()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//--- Debug
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
//--- ZonefileFmt
|
||||
|
||||
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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A CAA property tag as defined in [RFC 8659 section 4.1].
|
||||
///
|
||||
/// A CAA tag identifies the property name that an issuer must honor when
|
||||
/// evaluating a certificate issuance request. RFC 8659 restricts the tag to
|
||||
/// printable ASCII alphabetic characters and digits with a maximal length of
|
||||
/// 255 octets, and the wire format is a length-prefixed string of those
|
||||
/// characters.
|
||||
///
|
||||
/// [RFC 8659 section 4.1]: https://www.rfc-editor.org/rfc/rfc8659#section-4.1
|
||||
#[derive(Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct CaaTag<Octs: ?Sized>(CharStr<Octs>);
|
||||
|
||||
impl<Octs> CaaTag<Octs> {
|
||||
/// Constructs a CAA tag from a `CharStr`, validating that it only contains
|
||||
/// ASCII letters/digits and is at most 255 octets long.
|
||||
pub fn new(charstr: CharStr<Octs>) -> Result<Self, ParseError>
|
||||
where
|
||||
Octs: AsRef<[u8]>,
|
||||
{
|
||||
CaaTag::check_slice(charstr.as_slice())?;
|
||||
Ok(CaaTag(charstr))
|
||||
}
|
||||
|
||||
/// Parses a CAA tag from an octets sequence while enforcing the same
|
||||
/// validation as [`CaaTag::new`].
|
||||
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) })
|
||||
}
|
||||
|
||||
/// Creates a CAA tag from octets without validation.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure `octets` consists only of ASCII alphanumeric
|
||||
/// characters and is no longer than 255 octets, as required by RFC 8659.
|
||||
pub unsafe fn from_octets_unchecked(octets: Octs) -> Self {
|
||||
CaaTag(CharStr::from_octets_unchecked(octets))
|
||||
}
|
||||
}
|
||||
|
||||
impl CaaTag<[u8]> {
|
||||
/// Parses a CAA tag from a slice, validating it against the same rules as
|
||||
/// [`CaaTag::new`].
|
||||
pub fn from_slice(slice: &[u8]) -> Result<&Self, ParseError> {
|
||||
Self::check_slice(slice)?;
|
||||
Ok(unsafe { Self::from_slice_unchecked(slice) })
|
||||
}
|
||||
|
||||
/// Creates a new value from a slice without checking.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller needs to make sure that the slice only contains ascii
|
||||
/// alphanumeric characters and is not longer than 255 bytes.
|
||||
pub unsafe fn from_slice_unchecked(slice: &[u8]) -> &Self {
|
||||
// SAFETY: CaaTag has repr(transparent)
|
||||
&*(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> {
|
||||
/// Returns the length of the wire-format tag, which mirrors the length of
|
||||
/// the underlying `CharStr`.
|
||||
pub fn compose_len(&self) -> u16 {
|
||||
self.0.compose_len()
|
||||
}
|
||||
|
||||
/// Writes the tag into `target` using the standard length-prefixed wire
|
||||
/// format built from the ASCII characters of the tag.
|
||||
pub fn compose<Target: OctetsBuilder + ?Sized>(
|
||||
&self,
|
||||
target: &mut Target,
|
||||
) -> Result<(), Target::AppendError> {
|
||||
self.0.compose(target)
|
||||
}
|
||||
|
||||
/// Scans a CAA tag from the scanner, enforcing the ASCII rules used by
|
||||
/// the CAA property tag.
|
||||
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))
|
||||
}
|
||||
|
||||
/// Parses a CAA tag from the parser while validating it for ASCII letters
|
||||
/// and digits with a valid length.
|
||||
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)?))
|
||||
}
|
||||
}
|
||||
|
||||
//--- PartialEq and Eq
|
||||
|
||||
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> {}
|
||||
|
||||
//--- PartialOrd, Ord, and CanonicalOrd
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
//--- Hash
|
||||
impl<O: AsRef<[u8]>> hash::Hash for CaaTag<O> {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
//--- Display and Debug
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
//--- Serialize and Deserialize
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
|
||||
/// CAA flags as defined in [RFC 8659 section 4.1].
|
||||
///
|
||||
/// The only defined flag is the critical flag (bit 7).
|
||||
/// You can create a plain CAA flags instance with [CaaFlags::default()]
|
||||
/// or critical CAA flags with [CaaFlags::critical()].
|
||||
///
|
||||
/// The [CaaFlags::new()] method allows creating a CAA flags instance
|
||||
/// from any underlying byte, but be aware that only bit 7 is defined.
|
||||
#[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 {
|
||||
/// Creates a new CAA flags instance from the underlying byte.
|
||||
pub fn new(bits: u8) -> Self {
|
||||
CaaFlags(bits)
|
||||
}
|
||||
|
||||
/// Creates a CAA flags instance with the critical flag set.
|
||||
pub fn critical() -> Self {
|
||||
CaaFlags(0x80)
|
||||
}
|
||||
|
||||
/// Returns the underlying flags byte.
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@
|
||||
mod macros;
|
||||
|
||||
pub mod aaaa;
|
||||
pub mod caa;
|
||||
pub mod cds;
|
||||
pub mod dname;
|
||||
pub mod dnssec;
|
||||
@@ -102,6 +103,11 @@ rdata_types! {
|
||||
Aaaa,
|
||||
}
|
||||
}
|
||||
caa::{
|
||||
zone {
|
||||
Caa<O>,
|
||||
}
|
||||
}
|
||||
cds::{
|
||||
zone {
|
||||
Cdnskey<O>,
|
||||
|
||||
Reference in New Issue
Block a user