use core::hash::Hasher;
use crate::{
dns::MAX_SERVICE_NAME_LENGTH,
lib::{
fmt::{Debug, Display, Formatter, Result as FmtResult},
Cow, Hash, String, ToString, Vec,
},
};
use super::MAX_LABEL_LENGTH;
#[derive(Eq, Clone)]
pub struct Label<'a> {
pub(crate) data: Cow<'a, [u8]>,
}
impl<'a> Label<'a> {
pub fn new<T: Into<Cow<'a, [u8]>>>(data: T) -> crate::Result<Self> {
let data = data.into();
let label = if let Some((i, _)) = data.iter().enumerate().find(|(_, c)| **c == b'\\') {
unescape_label(data, i)?
} else {
Self { data }
};
if label.data.is_empty() || label.data.len() > MAX_LABEL_LENGTH {
return Err(crate::SimpleDnsError::InvalidServiceLabel);
}
Ok(label)
}
pub fn new_unchecked<T: Into<Cow<'a, [u8]>>>(data: T) -> Self {
Self { data: data.into() }
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn into_owned<'b>(self) -> Label<'b> {
Label {
data: self.data.into_owned().into(),
}
}
pub fn is_valid_hostname(&self) -> bool {
if self.data.is_empty() || self.data.len() > MAX_LABEL_LENGTH {
return false;
}
if let Some(first) = self.data.first() {
if !first.is_ascii_alphanumeric() {
return false;
}
}
if !self
.data
.iter()
.skip(1)
.all(|c| c.is_ascii_alphanumeric() || *c == b'-')
{
return false;
}
if let Some(last) = self.data.last() {
if !last.is_ascii_alphanumeric() {
return false;
}
}
true
}
pub fn is_valid_service_label(&self) -> bool {
if self.data.is_empty() || self.data.len() > MAX_SERVICE_NAME_LENGTH {
return false;
}
let Some((b'_', name)) = self.data.split_first() else {
return false;
};
name.iter().all(|c| c.is_ascii_alphanumeric() || *c == b'-')
&& name.iter().any(|c| c.is_ascii_alphabetic())
&& !name.starts_with(b"-")
&& !name.ends_with(b"-")
&& !name.windows(2).any(|w| w == b"--")
}
pub fn is_valid_instance_label(&self) -> bool {
!(self.data.is_empty() || self.data.len() > MAX_LABEL_LENGTH)
}
}
impl PartialEq for Label<'_> {
fn eq(&self, other: &Self) -> bool {
self.data.eq_ignore_ascii_case(&other.data)
}
}
impl Hash for Label<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_usize(self.data.len());
for &b in self.data.iter() {
state.write_u8(b.to_ascii_lowercase());
}
}
}
impl PartialOrd for Label<'_> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Label<'_> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.data
.iter()
.map(u8::to_ascii_lowercase)
.cmp(other.data.iter().map(u8::to_ascii_lowercase))
}
}
fn unescape_label<'a>(bytes: Cow<'a, [u8]>, mut position: usize) -> crate::Result<Label<'a>> {
let mut out = Vec::with_capacity(bytes.len());
out.extend_from_slice(&bytes[..position]);
while position < bytes.len() {
match bytes[position] {
b'\\' if position + 1 == bytes.len() => {
return Err(crate::SimpleDnsError::InvalidServiceLabel);
}
b'\\' => {
position += 1;
if bytes[position].is_ascii_digit() {
if (bytes.len() - position) < 3
|| !bytes[position + 1].is_ascii_digit()
|| !bytes[position + 2].is_ascii_digit()
{
return Err(crate::SimpleDnsError::InvalidServiceLabel);
}
let mut digit = (bytes[position] - b'0') as u16 * 100;
digit += (bytes[position + 1] - b'0') as u16 * 10;
digit += (bytes[position + 2] - b'0') as u16;
if digit > 255 {
return Err(crate::SimpleDnsError::InvalidServiceLabel);
}
out.push(digit as u8);
position += 3;
} else {
out.push(bytes[position]);
position += 1;
}
}
c => {
out.push(c);
position += 1;
}
}
}
Ok(Label { data: out.into() })
}
impl Display for Label<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
use core::fmt::Write;
let s = String::from_utf8_lossy(&self.data);
for c in s.chars() {
match c {
'.' => {
f.write_char('\\')?;
f.write_char(c)?;
}
'\\' => {
f.write_str(r"\\")?;
}
_ => {
f.write_char(c)?;
}
}
}
Ok(())
}
}
impl Debug for Label<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.debug_struct("Label")
.field("data", &self.to_string())
.finish()
}
}
impl AsRef<[u8]> for Label<'_> {
fn as_ref(&self) -> &[u8] {
self.data.as_ref()
}
}
#[derive(Clone, Copy)]
pub(crate) struct ExactLabels<'a, 'b>(&'b [Label<'a>]);
impl<'a, 'b> ExactLabels<'a, 'b> {
pub(crate) fn new(labels: &'b [Label<'a>]) -> Self {
Self(labels)
}
}
impl PartialEq for ExactLabels<'_, '_> {
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
}
}
impl Eq for ExactLabels<'_, '_> {}
impl PartialOrd for ExactLabels<'_, '_> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ExactLabels<'_, '_> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.0
.iter()
.map(|l| &l.data[..])
.cmp(other.0.iter().map(|l| &l.data[..]))
}
}
pub(crate) struct LabelsIter<'a> {
bytes: &'a [u8],
current: usize,
}
impl<'a> LabelsIter<'a> {
pub fn new(bytes: &'a [u8]) -> Self {
Self { bytes, current: 0 }
}
}
impl<'a> Iterator for LabelsIter<'a> {
type Item = Cow<'a, [u8]>;
fn next(&mut self) -> Option<Self::Item> {
let mut i = self.current;
while i < self.bytes.len() {
match self.bytes[i] {
b'\\' if i + 1 == self.bytes.len() => {
break;
}
b'\\' => {
i += 2;
}
b'.' => {
let current = crate::lib::mem::replace(&mut self.current, i + 1);
if i - current == 0 {
i = self.current;
continue;
}
return Some(self.bytes[current..i].into());
}
_ => {
i += 1;
}
}
}
if self.current < self.bytes.len() {
let current = crate::lib::mem::replace(&mut self.current, self.bytes.len());
Some(self.bytes[current..].into())
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_invalid_label() {
let input = b"invalid\xF0\x90\x80label";
let label = Label::new_unchecked(input);
assert_eq!(label.to_string(), "invalid�label");
}
#[test]
fn labels_are_escaped() {
let l = Label::new(b"escaped.label").expect("failed to create label");
assert_eq!(r"escaped\.label", l.to_string());
}
fn label(s: &str) -> Label<'_> {
Label::new_unchecked(s.as_bytes())
}
#[test]
fn hostname_label_valid() {
assert!(label("example").is_valid_hostname());
assert!(label("a").is_valid_hostname());
assert!(label("EXAMPLE").is_valid_hostname());
assert!(label("a-b").is_valid_hostname());
assert!(label("a--b").is_valid_hostname());
assert!(label("1abc").is_valid_hostname());
assert!(label("abc1").is_valid_hostname());
assert!(label("123").is_valid_hostname());
assert!(label(&"a".repeat(63)).is_valid_hostname());
}
#[test]
fn hostname_label_invalid() {
assert!(!label("").is_valid_hostname());
assert!(!label(&"a".repeat(64)).is_valid_hostname());
assert!(!label("-a").is_valid_hostname());
assert!(!label("a-").is_valid_hostname());
assert!(!label("-").is_valid_hostname());
assert!(!label("_a").is_valid_hostname());
assert!(!label("a_b").is_valid_hostname());
assert!(!label("a.b").is_valid_hostname());
assert!(!label("a b").is_valid_hostname());
assert!(!label(r"a\b").is_valid_hostname());
assert!(!label("caf\u{e9}").is_valid_hostname());
assert!(!label("\u{1F600}").is_valid_hostname());
}
#[test]
fn service_label_valid() {
assert!(label("_http").is_valid_service_label());
assert!(label("_a").is_valid_service_label());
assert!(label("_HTTP").is_valid_service_label());
assert!(label("_ipp-tls").is_valid_service_label());
assert!(label("_a-b-c").is_valid_service_label());
assert!(label("_9p").is_valid_service_label());
assert!(label("_1a").is_valid_service_label());
assert!(label("_a1").is_valid_service_label());
assert!(label("_abcdefghijklmno").is_valid_service_label());
}
#[test]
fn service_label_invalid() {
assert!(!label("").is_valid_service_label());
assert!(!label("_").is_valid_service_label());
assert!(!label("http").is_valid_service_label());
assert!(!label("_abcdefghijklmnop").is_valid_service_label());
assert!(!label("_123").is_valid_service_label());
assert!(!label("_1-2").is_valid_service_label());
assert!(!label("_-a").is_valid_service_label());
assert!(!label("_a-").is_valid_service_label());
assert!(!label("_a--b").is_valid_service_label());
assert!(!label("_a_b").is_valid_service_label());
assert!(!label("_a.b").is_valid_service_label());
assert!(!label("_a b").is_valid_service_label());
assert!(!label("_caf\u{e9}").is_valid_service_label());
assert!(!label("__http").is_valid_service_label());
}
#[test]
fn instance_label_valid() {
assert!(label("My Printer").is_valid_instance_label());
assert!(label("a").is_valid_instance_label());
assert!(label("Stuart's Test Server").is_valid_instance_label());
assert!(label("My Printer. Floor 2").is_valid_instance_label());
assert!(label(r"back\slash").is_valid_instance_label());
assert!(label("-leading and trailing-").is_valid_instance_label());
assert!(label("_underscore").is_valid_instance_label());
assert!(label("caf\u{e9}").is_valid_instance_label());
assert!(label("\u{1F5A8}").is_valid_instance_label());
assert!(label(&"a".repeat(63)).is_valid_instance_label());
}
#[test]
fn instance_label_invalid() {
assert!(!label("").is_valid_instance_label());
assert!(!label(&"a".repeat(64)).is_valid_instance_label());
}
#[test]
fn instance_label_limit_counts_utf8_octets() {
assert!(!label(&"\u{e9}".repeat(32)).is_valid_instance_label());
assert!(label(&("\u{e9}".repeat(31) + "a")).is_valid_instance_label());
}
}