From 30e80d1f6d4f924a32076b25038496942b0f2e46 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Tue, 12 Aug 2025 17:48:11 +0200 Subject: [PATCH 01/15] [new/rdata] Implement 'DName' --- src/new/base/record.rs | 7 +- src/new/rdata/dname.rs | 182 +++++++++++++++++++++++++++++++++++++++++ src/new/rdata/mod.rs | 15 ++++ 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 src/new/rdata/dname.rs diff --git a/src/new/base/record.rs b/src/new/base/record.rs index 300d4aeb1..0d829c745 100644 --- a/src/new/base/record.rs +++ b/src/new/base/record.rs @@ -276,6 +276,9 @@ impl RType { /// The type of an [`Aaaa`](crate::new::rdata::Aaaa) record. pub const AAAA: Self = Self::new(28); + /// The type of a [`DName`](crate::new::rdata::DName) record. + pub const DNAME: Self = Self::new(39); + /// The type of an [`Opt`](crate::new::rdata::Opt) record. pub const OPT: Self = Self::new(41); @@ -327,7 +330,7 @@ impl RType { /// - `NAPTR` /// - `KX` /// - `SRV` - /// - `DNAME` + /// - [`DNAME`](RType::DNAME) /// - `A6` (obsolete) /// - [`RRSIG`](RType::RRSIG) /// @@ -342,6 +345,7 @@ impl RType { | Self::SOA | Self::PTR | Self::MX + | Self::DNAME | Self::RRSIG ) } @@ -377,6 +381,7 @@ impl fmt::Debug for RType { Self::MX => "RType::MX", Self::TXT => "RType::TXT", Self::AAAA => "RType::AAAA", + Self::DNAME => "RType::DNAME", Self::OPT => "RType::OPT", Self::DS => "RType::DS", Self::RRSIG => "RType::RRSIG", diff --git a/src/new/rdata/dname.rs b/src/new/rdata/dname.rs new file mode 100644 index 000000000..f09868b35 --- /dev/null +++ b/src/new/rdata/dname.rs @@ -0,0 +1,182 @@ +//! The DNAME record data type. +//! +//! See [RFC 6672](https://datatracker.ietf.org/doc/html/rfc6672). + +use core::cmp::Ordering; + +use crate::new::base::build::{ + AsBytes, BuildBytes, BuildInMessage, NameCompressor, +}; +use crate::new::base::name::{CanonicalName, Name}; +use crate::new::base::wire::{ + ParseBytes, ParseError, SplitBytes, TruncationError, +}; +use crate::new::base::{ + CanonicalRecordData, ParseRecordData, ParseRecordDataBytes, RType, +}; +use crate::utils::dst::UnsizedCopy; + +//----------- DName ---------------------------------------------------------- + +/// Redirection for the descendants of this domain. +/// +/// A [`DName`] record indicates that a doamin name is a (partial!) alias. +/// Queries for data _under_ that domain name (not that domain name itself) +/// are redirected to the specified target. A domain name can have at most +/// one [`DName`] record; in this case, it cannot define subordinate records +/// nor have a [`CName`] record. It is conceptually similar to [`CName`]. +/// +/// [`DName`] is specified by [RFC 6672]. +/// +/// [`CName`]: super::CName +/// [RFC 6672]: https://datatracker.ietf.org/doc/html/rfc6672 +/// +/// ## Wire Format +/// +/// The wire format of a [`DName`] record is simply the target domain name. +/// This domain name *cannot* be compressed in DNS messages. +/// +/// The memory layout of the [`DName`] type is identical to its serialization +/// in the wire format. This means that it can be parsed from the wire format +/// in a zero-copy fashion, which is more efficient. +/// +/// ## Usage +/// +/// Because [`DName`] is a record data type, it is usually handled within +/// an enum like [`RecordData`]. This section describes how to use it +/// independently (or when building new record data from scratch). +/// +/// [`RecordData`]: crate::new::rdata::RecordData +/// +/// [`DName`] is a _dynamically sized type_ (DST). It is not possible to +/// store an [`DName`] in place (e.g. in a local variable); it must be held +/// indirectly, via a reference or a smart pointer type like [`Box`]. This +/// makes it more difficult to _create_ new [`DName`]s; but once they are +/// placed somewhere, they can be used by reference (i.e. `&DName`) exactly +/// like any other type. +/// +/// [`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html +/// +/// It is currently a bit difficult to build a new [`DName`] from scratch. It +/// is easiest to use [`DName::new()`] on a reference to a [`Name`], or to +/// build the wire format representation of the [`DName`] manually and then to +/// parse it. +/// +/// ``` +/// # use domain::new::base::wire::{AsBytes, ParseBytes}; +/// # use domain::new::base::name::NameBuf; +/// # use domain::new::rdata::DName; +/// # use domain::utils::dst::UnsizedCopy; +/// # +/// // Parse a 'Name' and build a 'DName' from there: +/// let name = "example.org".parse::().unwrap(); +/// let dname = DName::new(&name); +/// +/// // Parse a 'DName' from the DNS wire format: +/// let bytes = b"\x07example\x03org\x00"; +/// let from_bytes: &DName = <&DName>::parse_bytes(bytes).unwrap(); +/// assert_eq!(dname.as_bytes(), bytes); +/// +/// // Copy a 'DName' onto the heap: +/// let heaped: Box = dname.unsized_copy_into(); +/// ``` +/// +/// As a DST, [`DName`] does not implement [`Copy`] or [`Clone`]. Instead, it +/// implements [`UnsizedCopy`]. A [`DName`], held by reference, can be copied +/// into a different container (e.g. `Box`) using [`unsized_copy_into()`] +/// +/// [`unsized_copy_into()`]: UnsizedCopy::unsized_copy_into() +/// +/// For debugging, [`DName`] can be formatted using [`fmt::Debug`]. +/// +/// [`fmt::Debug`]: core::fmt::Debug +/// +/// To serialize a [`DName`] in the wire format, use [`BuildBytes`] +/// (which will serialize it to a given buffer) or [`AsBytes`] (which will +/// cast the [`DName`] into a byte sequence in place). It also supports +/// [`BuildInMessage`]. +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + AsBytes, + BuildBytes, + UnsizedCopy, +)] +#[repr(transparent)] +pub struct DName { + /// The target name. + pub name: Name, +} + +//--- Construction + +impl DName { + /// Wrap a domain name as a [`DName`]. + pub const fn new(target: &Name) -> &Self { + // SAFETY: 'DName' is 'repr(transparent)' to 'Name'. + unsafe { core::mem::transmute::<&Name, &Self>(target) } + } +} + +//--- Canonical operations + +impl CanonicalRecordData for DName { + fn build_canonical_bytes<'b>( + &self, + bytes: &'b mut [u8], + ) -> Result<&'b mut [u8], TruncationError> { + self.name.build_lowercased_bytes(bytes) + } + + fn cmp_canonical(&self, other: &Self) -> Ordering { + self.name.cmp_lowercase_composed(&other.name) + } +} + +//--- Building into DNS messages + +impl BuildInMessage for DName { + fn build_in_message( + &self, + contents: &mut [u8], + start: usize, + compressor: &mut NameCompressor, + ) -> Result { + self.name.build_in_message(contents, start, compressor) + } +} + +//--- Parsing from bytes + +impl<'a> ParseBytes<'a> for &'a DName { + fn parse_bytes(bytes: &'a [u8]) -> Result { + <&Name>::parse_bytes(bytes).map(DName::new) + } +} + +impl<'a> SplitBytes<'a> for &'a DName { + fn split_bytes(bytes: &'a [u8]) -> Result<(Self, &'a [u8]), ParseError> { + <&Name>::split_bytes(bytes) + .map(|(name, rest)| (DName::new(name), rest)) + } +} + +//--- Parsing record data + +impl<'a> ParseRecordData<'a> for &'a DName {} + +impl<'a> ParseRecordDataBytes<'a> for &'a DName { + fn parse_record_data_bytes( + bytes: &'a [u8], + rtype: RType, + ) -> Result { + match rtype { + RType::DNAME => Self::parse_bytes(bytes), + _ => Err(ParseError), + } + } +} diff --git a/src/new/rdata/mod.rs b/src/new/rdata/mod.rs index 362c2e724..4974af132 100644 --- a/src/new/rdata/mod.rs +++ b/src/new/rdata/mod.rs @@ -46,6 +46,9 @@ //! IPv6 support (RFC 3596): //! - [`Aaaa`] //! +//! DNAME support (RFC 6672): +//! - [`DName`] +//! //! EDNS support (RFC 6891): //! - [`Opt`] //! @@ -92,6 +95,9 @@ use crate::new::base::name::{Name, NameBuf}; mod basic; pub use basic::{CName, HInfo, Mx, Ns, Ptr, Soa, Txt, A}; +mod dname; +pub use dname::DName; + mod ipv6; pub use ipv6::Aaaa; @@ -260,6 +266,9 @@ define_record_data! { /// The IPv6 address of a host responsible for this domain. Aaaa(Aaaa) = AAAA, + /// Redirection for the descendants of this domain. + DName(&'a DName) = DNAME, + /// Extended DNS options. Opt(&'a Opt) = OPT, @@ -301,6 +310,7 @@ impl<'a, N> RecordData<'a, N> { Self::Mx(r) => RecordData::Mx(r.map_name(f)), Self::Txt(r) => RecordData::Txt(r), Self::Aaaa(r) => RecordData::Aaaa(r), + Self::DName(r) => RecordData::DName(r), Self::Opt(r) => RecordData::Opt(r), Self::Ds(r) => RecordData::Ds(r), Self::RRSig(r) => RecordData::RRSig(r), @@ -327,6 +337,7 @@ impl<'a, N> RecordData<'a, N> { Self::Mx(r) => RecordData::Mx(r.map_name_by_ref(f)), Self::Txt(r) => RecordData::Txt(r), Self::Aaaa(r) => RecordData::Aaaa(*r), + Self::DName(r) => RecordData::DName(r), Self::Opt(r) => RecordData::Opt(r), Self::Ds(r) => RecordData::Ds(r), Self::RRSig(r) => RecordData::RRSig(r.clone()), @@ -359,6 +370,7 @@ impl<'a, N> RecordData<'a, N> { Self::Mx(r) => RecordData::Mx(r.clone()), Self::Txt(r) => RecordData::Txt(copy_to_bump(*r, bump)), Self::Aaaa(r) => RecordData::Aaaa(*r), + Self::DName(r) => RecordData::DName(copy_to_bump(*r, bump)), Self::Opt(r) => RecordData::Opt(copy_to_bump(*r, bump)), Self::Ds(r) => RecordData::Ds(copy_to_bump(*r, bump)), Self::RRSig(r) => RecordData::RRSig(r.clone_to_bump(bump)), @@ -409,6 +421,9 @@ impl<'a, N: SplitMessageBytes<'a>> ParseRecordData<'a> for RecordData<'a, N> { RType::AAAA => { Aaaa::parse_bytes(&contents[start..]).map(Self::Aaaa) } + RType::DNAME => { + <&DName>::parse_bytes(&contents[start..]).map(Self::DName) + } RType::OPT => { <&Opt>::parse_bytes(&contents[start..]).map(Self::Opt) } From cad599d2f0556dc2bccb4a15402eda07c1a3d175 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Tue, 26 Aug 2025 03:38:22 +0200 Subject: [PATCH 02/15] [new/base/name] Impl 'serde' conversions - Also enhance 'FromStr' impls to parse more of the zonefile format, for parity with the existing 'Display' impls - Also impl conversions between 'NameBuf' and 'RevNameBuf' --- Cargo.toml | 2 +- src/new/base/name/absolute.rs | 237 ++++++++++++++++++++++++---- src/new/base/name/label.rs | 285 +++++++++++++++++++++++++++++----- src/new/base/name/reversed.rs | 117 ++++++++++---- 4 files changed, 537 insertions(+), 104 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0684fbca4..54c823e57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ alloc = [] bumpalo = ["dep:bumpalo", "std"] bytes = ["dep:bytes", "octseq/bytes"] heapless = ["dep:heapless", "octseq/heapless"] -serde = ["dep:serde", "octseq/serde"] +serde = ["std", "dep:serde", "octseq/serde"] smallvec = ["dep:smallvec", "octseq/smallvec"] std = ["alloc", "dep:hashbrown", "bumpalo?/std", "bytes?/std", "octseq/std", "time/std"] tracing = ["dep:log", "dep:tracing"] diff --git a/src/new/base/name/absolute.rs b/src/new/base/name/absolute.rs index eaae23db2..9138be201 100644 --- a/src/new/base/name/absolute.rs +++ b/src/new/base/name/absolute.rs @@ -332,6 +332,24 @@ impl fmt::Debug for Name { } } +//--- Serialize + +#[cfg(feature = "serde")] +impl serde::Serialize for Name { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use std::string::ToString; + + if serializer.is_human_readable() { + serializer.serialize_newtype_struct("Name", &self.to_string()) + } else { + serializer.serialize_newtype_struct("Name", self.as_bytes()) + } + } +} + //----------- NameBuf -------------------------------------------------------- /// A 256-byte buffer containing a [`Name`]. @@ -555,35 +573,6 @@ impl NameBuf { } } -//--- Parsing from strings - -impl FromStr for NameBuf { - type Err = NameParseError; - - /// Parse a name from a string. - /// - /// This is intended for easily constructing hard-coded domain names. The - /// labels in the name should be given in the conventional order (i.e. not - /// reversed), and should be separated by ASCII periods. The labels will - /// be parsed using [`LabelBuf::from_str()`]; see its documentation. This - /// function cannot parse all valid domain names; if an exceptional name - /// needs to be parsed, use [`Name::from_bytes_unchecked()`]. If the - /// input is empty, the root name is returned. - fn from_str(s: &str) -> Result { - let mut this = Self::empty(); - for label in s.split('.') { - let label = - label.parse::().map_err(NameParseError::Label)?; - if 255 - this.size < 1 + label.as_bytes().len() as u8 { - return Err(NameParseError::Overlong); - } - this.append_label(&label); - } - this.append_label(Label::ROOT); - Ok(this) - } -} - //--- Access to the underlying 'Name' impl Deref for NameBuf { @@ -668,6 +657,171 @@ impl fmt::Debug for NameBuf { } } +//--- Parsing from strings + +impl NameBuf { + /// Parse a domain name from the zonefile format. + pub fn parse_str(mut s: &[u8]) -> Result<(Self, &[u8]), NameParseError> { + // The buffer we'll fill into. + let mut this = Self::empty(); + + // Parse label by label. + loop { + let (label, rest) = LabelBuf::parse_str(s)?; + + if 255 - this.size < 1 + label.as_bytes().len() as u8 { + return Err(NameParseError::Overlong); + } + this.append_label(&label); + + match *rest { + [b' ' | b'\n' | b'\r' | b'\t', ..] => break, + [b'.', ref rest @ ..] => s = rest, + _ => return Err(NameParseError::InvalidChar), + } + } + this.append_label(Label::ROOT); + + Ok((this, s)) + } +} + +impl FromStr for NameBuf { + type Err = NameParseError; + + /// Parse a name from a string. + fn from_str(s: &str) -> Result { + match Self::parse_str(s.as_bytes()) { + Ok((this, &[])) => Ok(this), + Ok(_) => Err(NameParseError::InvalidChar), + Err(err) => Err(err), + } + } +} + +//--- Serialize, Deserialize + +#[cfg(feature = "serde")] +impl serde::Serialize for NameBuf { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + (**self).serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'a> serde::Deserialize<'a> for NameBuf { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + if deserializer.is_human_readable() { + struct V; + + impl serde::de::Visitor<'_> for V { + type Value = NameBuf; + + fn expecting( + &self, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + f.write_str("a domain name, in the DNS zonefile format") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + v.parse().map_err(|err| E::custom(err)) + } + } + + struct NV; + + impl<'a> serde::de::Visitor<'a> for NV { + type Value = NameBuf; + + fn expecting( + &self, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + f.write_str("an absolute domain name") + } + + fn visit_newtype_struct( + self, + deserializer: D, + ) -> Result + where + D: serde::Deserializer<'a>, + { + deserializer.deserialize_str(V) + } + } + + deserializer.deserialize_newtype_struct("Name", NV) + } else { + struct V; + + impl serde::de::Visitor<'_> for V { + type Value = NameBuf; + + fn expecting( + &self, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + f.write_str("a domain name, in the DNS wire format") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + NameBuf::parse_bytes(v).map_err(|_| E::custom("misformatted domain name for the DNS wire format")) + } + } + + struct NV; + + impl<'a> serde::de::Visitor<'a> for NV { + type Value = NameBuf; + + fn expecting( + &self, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + f.write_str("an absolute domain name") + } + + fn visit_newtype_struct( + self, + deserializer: D, + ) -> Result + where + D: serde::Deserializer<'a>, + { + deserializer.deserialize_bytes(V) + } + } + + deserializer.deserialize_newtype_struct("Name", NV) + } + } +} + +#[cfg(feature = "serde")] +impl<'a> serde::Deserialize<'a> for std::boxed::Box { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + NameBuf::deserialize(deserializer) + .map(|this| this.unsized_copy_into()) + } +} + //------------ NameParseError ------------------------------------------------ /// An error in parsing a [`Name`] from a string. @@ -681,10 +835,25 @@ pub enum NameParseError { /// Valid names are between 1 and 255 bytes, inclusive. Overlong, + /// The name contained an invalid character. + /// + /// Valid names contain any of the following characters: + /// - ASCII alphanumeric characters + /// - `-`, `_`, `*` (within labels) + /// - `.` (between labels) + /// - Correctly escaped characters + InvalidChar, + /// A label in the name could not be parsed. Label(LabelParseError), } +impl From for NameParseError { + fn from(value: LabelParseError) -> Self { + Self::Label(value) + } +} + // TODO(1.81.0): Use 'core::error::Error' instead. #[cfg(feature = "std")] impl std::error::Error for NameParseError {} @@ -693,10 +862,16 @@ impl fmt::Display for NameParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { Self::Overlong => "the domain name was too long", + Self::InvalidChar | Self::Label(LabelParseError::InvalidChar) => { + "the domain name contained an invalid character" + } Self::Label(LabelParseError::Overlong) => "a label was too long", Self::Label(LabelParseError::Empty) => "a label was empty", - Self::Label(LabelParseError::InvalidChar) => { - "the domain name contained an invalid character" + Self::Label(LabelParseError::PartialEscape) => { + "a label contained an incomplete escape" + } + Self::Label(LabelParseError::InvalidEscape) => { + "a label contained an invalid escape" } }) } diff --git a/src/new/base/name/label.rs b/src/new/base/name/label.rs index 125c79a9e..0f629298a 100644 --- a/src/new/base/name/label.rs +++ b/src/new/base/name/label.rs @@ -274,14 +274,18 @@ impl fmt::Display for Label { /// /// The label is printed in the conventional zone file format, with bytes /// outside printable ASCII formatted as `\\DDD` (a backslash followed by - /// three zero-padded decimal digits), and `.` and `\\` simply escaped by - /// a backslash. + /// three zero-padded decimal digits), and uncommon ASCII characters just + /// escaped by a backslash. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_wildcard() { + return f.write_str("*"); + } + self.contents().iter().try_for_each(|&byte| { - if b".\\".contains(&byte) { - write!(f, "\\{}", byte as char) - } else if byte.is_ascii_graphic() { + if byte.is_ascii_alphanumeric() || b"-_".contains(&byte) { write!(f, "{}", byte as char) + } else if byte.is_ascii_graphic() { + write!(f, "\\{}", byte as char) } else { write!(f, "\\{:03}", byte) } @@ -292,9 +296,25 @@ impl fmt::Display for Label { impl fmt::Debug for Label { /// Print a label for debugging purposes. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Label") - .field(&format_args!("{}", self)) - .finish() + write!(f, "Label({self})") + } +} + +//--- Serialize + +#[cfg(feature = "serde")] +impl serde::Serialize for Label { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use std::string::ToString; + + if serializer.is_human_readable() { + serializer.serialize_newtype_struct("Label", &self.to_string()) + } else { + serializer.serialize_newtype_struct("Label", self.contents()) + } } } @@ -328,38 +348,6 @@ impl UnsizedCopyFrom for LabelBuf { } } -//--- Parsing from strings - -impl FromStr for LabelBuf { - type Err = LabelParseError; - - /// Parse a label from a string. - /// - /// This is intended for easily constructing hard-coded labels. The input - /// is not expected to be in the zonefile format; it should simply contain - /// 1 to 63 characters, each being a plain ASCII alphanumeric or a hyphen. - /// To construct a label containing bytes outside this range, use - /// [`Label::from_bytes_unchecked()`]. To construct a root label, use - /// [`Label::ROOT`]. - fn from_str(s: &str) -> Result { - if s == "*" { - Ok(Self::copy_from(Label::WILDCARD)) - } else if !s.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-') { - Err(LabelParseError::InvalidChar) - } else if s.is_empty() { - Err(LabelParseError::Empty) - } else if s.len() > 63 { - Err(LabelParseError::Overlong) - } else { - let bytes = s.as_bytes(); - let mut data = [0u8; 64]; - data[0] = bytes.len() as u8; - data[1..1 + bytes.len()].copy_from_slice(bytes); - Ok(Self { data }) - } - } -} - //--- Parsing from DNS messages impl ParseMessageBytes<'_> for LabelBuf { @@ -498,6 +486,207 @@ impl Hash for LabelBuf { } } +//--- Parsing from strings + +impl LabelBuf { + /// Parse a label from the zonefile format. + pub fn parse_str(mut s: &[u8]) -> Result<(Self, &[u8]), LabelParseError> { + if let &[b'*', ref rest @ ..] = s { + return Ok((Self::copy_from(Label::WILDCARD), rest)); + } + + // The buffer we'll fill into. + let mut this = Self { data: [0u8; 64] }; + + // Parse character by character. + loop { + let full = s; + let &[b, ref rest @ ..] = s else { break }; + s = rest; + let value = if b.is_ascii_alphanumeric() || b"-_".contains(&b) { + // A regular label character. + b + } else if b == b'\\' { + // An escape character. + let &[b, ref rest @ ..] = s else { break }; + s = rest; + if b.is_ascii_digit() { + let digits = rest + .get(..3) + .ok_or(LabelParseError::PartialEscape)?; + let digits = str::from_utf8(digits) + .map_err(|_| LabelParseError::InvalidEscape)?; + digits + .parse() + .map_err(|_| LabelParseError::InvalidEscape)? + } else if b.is_ascii_graphic() { + b + } else { + return Err(LabelParseError::InvalidEscape); + } + } else if b". \n\r\t".contains(&b) { + // The label has ended. + s = full; + break; + } else { + return Err(LabelParseError::InvalidChar); + }; + + let off = this.data[0] as usize + 1; + this.data[0] += 1; + let ptr = + this.data.get_mut(off).ok_or(LabelParseError::Overlong)?; + *ptr = value; + } + + if this.data[0] == 0 { + return Err(LabelParseError::Empty); + } + + Ok((this, s)) + } +} + +impl FromStr for LabelBuf { + type Err = LabelParseError; + + /// Parse a label from a string. + fn from_str(s: &str) -> Result { + match Self::parse_str(s.as_bytes()) { + Ok((this, &[])) => Ok(this), + Ok(_) => Err(LabelParseError::InvalidChar), + Err(err) => Err(err), + } + } +} + +//--- Serialize, Deserialize + +#[cfg(feature = "serde")] +impl serde::Serialize for LabelBuf { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + (**self).serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'a> serde::Deserialize<'a> for LabelBuf { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + if deserializer.is_human_readable() { + struct V; + + impl serde::de::Visitor<'_> for V { + type Value = LabelBuf; + + fn expecting( + &self, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + f.write_str("a label, in the DNS zonefile format") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + v.parse().map_err(|err| E::custom(err)) + } + } + + struct NV; + + impl<'a> serde::de::Visitor<'a> for NV { + type Value = LabelBuf; + + fn expecting( + &self, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + f.write_str("a DNS label") + } + + fn visit_newtype_struct( + self, + deserializer: D, + ) -> Result + where + D: serde::Deserializer<'a>, + { + deserializer.deserialize_str(V) + } + } + + deserializer.deserialize_newtype_struct("Label", NV) + } else { + struct V; + + impl serde::de::Visitor<'_> for V { + type Value = LabelBuf; + + fn expecting( + &self, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + f.write_str("a label, in the DNS wire format") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + LabelBuf::parse_bytes(v).map_err(|_| { + E::custom( + "misformatted label for the DNS wire format", + ) + }) + } + } + + struct NV; + + impl<'a> serde::de::Visitor<'a> for NV { + type Value = LabelBuf; + + fn expecting( + &self, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + f.write_str("a DNS label") + } + + fn visit_newtype_struct( + self, + deserializer: D, + ) -> Result + where + D: serde::Deserializer<'a>, + { + deserializer.deserialize_bytes(V) + } + } + + deserializer.deserialize_newtype_struct("Label", NV) + } + } +} + +#[cfg(feature = "serde")] +impl<'a> serde::Deserialize<'a> for std::boxed::Box