use crate::constants::{MAX_LABEL_BYTES, MAX_NAME_BYTES};
use derive_more::{Display, IsVariant, TryUnwrap, Unwrap};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Display, thiserror::Error)]
#[display("label of {len} bytes exceeds max {MAX_LABEL_BYTES}")]
pub struct LabelTooLongDetail {
len: usize,
}
impl LabelTooLongDetail {
#[inline(always)]
pub(crate) const fn new(len: usize) -> Self {
Self { len }
}
#[inline(always)]
pub const fn len(&self) -> usize {
self.len
}
#[inline(always)]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Display, thiserror::Error)]
#[display("name of {len} bytes exceeds max {MAX_NAME_BYTES}")]
pub struct NameTooLongDetail {
len: usize,
}
impl NameTooLongDetail {
cfg_storage! {
#[inline(always)]
pub(crate) const fn new(len: usize) -> Self {
Self { len }
}
}
#[inline(always)]
pub const fn len(&self) -> usize {
self.len
}
#[inline(always)]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
}
#[derive(
Debug, Clone, Copy, Eq, PartialEq, Hash, IsVariant, Unwrap, TryUnwrap, thiserror::Error,
)]
#[unwrap(ref)]
#[try_unwrap(ref)]
#[non_exhaustive]
pub enum NameError {
#[error(transparent)]
LabelTooLong(LabelTooLongDetail),
#[error(transparent)]
NameTooLong(NameTooLongDetail),
#[error("name contains an empty label")]
EmptyLabel,
}
cfg_storage! {
fn validate_name(s: &str) -> Result<(), NameError> {
if s.len() > MAX_NAME_BYTES {
return Err(NameError::NameTooLong(NameTooLongDetail::new(s.len())));
}
if s.is_empty() {
return Ok(());
}
let trimmed = match s.strip_suffix('.') {
Some(rest) => rest,
None => s,
};
let mut wire_len: usize = 1; for label in trimmed.split('.') {
if label.is_empty() {
return Err(NameError::EmptyLabel);
}
let len = label.len();
if len > MAX_LABEL_BYTES as usize {
return Err(NameError::LabelTooLong(LabelTooLongDetail::new(len)));
}
wire_len = wire_len.saturating_add(1).saturating_add(len);
}
if wire_len > MAX_NAME_BYTES {
return Err(NameError::NameTooLong(NameTooLongDetail::new(wire_len)));
}
Ok(())
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
type NameInner = smol_str::SmolStr;
#[cfg(all(feature = "no-atomic", not(any(feature = "alloc", feature = "std"))))]
type NameInner = portable_atomic_util::Arc<str>;
#[cfg(all(
feature = "heapless",
not(any(feature = "alloc", feature = "std", feature = "no-atomic"))
))]
type NameInner = heapless::String<MAX_NAME_BYTES>;
cfg_storage! {
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Name(NameInner);
impl Name {
#[inline(always)]
pub fn as_str(&self) -> &str {
self.0.as_ref()
}
#[inline(always)]
pub fn len(&self) -> usize {
self.as_str().len()
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.as_str().is_empty()
}
}
impl core::fmt::Display for Name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
}
cfg_heap! {
const _: () = {
use std::string::String;
impl Name {
pub fn try_from_str(s: &str) -> Result<Self, NameError> {
validate_name(s)?;
let mut buf = String::with_capacity(s.len());
for ch in s.chars() {
buf.push(ch.to_ascii_lowercase());
}
Ok(Self(NameInner::from(buf)))
}
pub fn from_wire_labels<'a, E, I>(labels: I) -> Option<Self>
where
I: IntoIterator<Item = Result<&'a [u8], E>>,
{
let mut buf = String::with_capacity(MAX_NAME_BYTES);
let mut wire_len: usize = 1; for label in labels {
let label = label.ok()?;
if label.len() > MAX_LABEL_BYTES as usize {
return None;
}
wire_len = wire_len.saturating_add(1).saturating_add(label.len());
if wire_len > MAX_NAME_BYTES {
return None;
}
if label.contains(&b'.') {
return None;
}
for ch in core::str::from_utf8(label).ok()?.chars() {
buf.push(ch.to_ascii_lowercase());
}
buf.push('.');
}
validate_name(&buf).ok()?;
Some(Self(NameInner::from(buf)))
}
}
};
}
#[cfg(all(
feature = "heapless",
not(any(feature = "alloc", feature = "std", feature = "no-atomic"))
))]
const _: () = {
impl Name {
pub fn try_from_str(s: &str) -> Result<Self, NameError> {
validate_name(s)?;
let mut buf: NameInner = heapless::String::new();
for ch in s.chars() {
buf
.push(ch.to_ascii_lowercase())
.map_err(|_| NameError::NameTooLong(NameTooLongDetail::new(s.len())))?;
}
Ok(Self(buf))
}
pub fn from_wire_labels<'a, E, I>(labels: I) -> Option<Self>
where
I: IntoIterator<Item = Result<&'a [u8], E>>,
{
let mut buf: NameInner = heapless::String::new();
let mut wire_len: usize = 1; for label in labels {
let label = label.ok()?;
if label.len() > MAX_LABEL_BYTES as usize {
return None;
}
wire_len = wire_len.saturating_add(1).saturating_add(label.len());
if wire_len > MAX_NAME_BYTES {
return None;
}
if label.contains(&b'.') {
return None;
}
for ch in core::str::from_utf8(label).ok()?.chars() {
buf.push(ch.to_ascii_lowercase()).ok()?;
}
buf.push('.').ok()?;
}
validate_name(&buf).ok()?;
Some(Self(buf))
}
}
};
#[cfg(all(test, any(feature = "alloc", feature = "std", feature = "heapless")))]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests;