use crate::binutils::*;
use crate::ParseError;
use thiserror::Error;
use std::borrow::Cow;
use std::fmt;
use std::iter::zip;
use std::ops::Deref;
use std::str;
const INIT_NUM_LABELS: usize = 8;
pub(crate) const MAX_JUMPS: u8 = 5;
pub(crate) const MAX_LABEL_SIZE: usize = 63;
pub(crate) const MAX_NAME_SIZE: usize = 255;
#[derive(Error, Debug)]
pub enum NameError {
#[error(
"Specified label length ({0}) is empty or is bigger than DNS specification (maximum {}).",
MAX_LABEL_SIZE
)]
LabelLength(usize),
#[error("The provided label is not a valid domain name label")]
LabelContent,
#[error(
"Name length ({0}) is too long, is bigger than DNS specification (maximum {}).",
MAX_NAME_SIZE
)]
NameLength(usize),
}
#[derive(Clone)]
pub struct Name<'a> {
labels: Vec<Cow<'a, str>>,
len: u8,
}
impl fmt::Display for Name<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for l in self.iter_human() {
write!(f, "{}.", l)?;
}
Ok(())
}
}
impl fmt::Debug for Name<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for l in self.iter_human() {
write!(f, "{}.", l)?;
}
Ok(())
}
}
impl Default for Name<'_> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl From<Name<'_>> for Vec<u8> {
#[inline]
fn from(name: Name<'_>) -> Self {
let mut out = Vec::with_capacity(name.len as _);
name.serialize(&mut out);
out
}
}
impl<'a> TryFrom<&'a str> for Name<'a> {
type Error = NameError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
let mut name = Name::default();
for label in value.rsplit('.') {
name.push_label(label.into())?;
}
Ok(name)
}
}
impl<'a> Name<'a> {
#[inline]
pub fn parse(buff: &'a [u8], pos: usize) -> Result<(Self, usize), ParseError> {
let mut name = Name::new();
let blen = buff.len();
let (mut pos, mut size, mut jumps) = (pos, 0, 0);
loop {
if jumps > MAX_JUMPS {
Err(ParseError::ExcesiveJumps(jumps))?;
}
match read_label_metadata(buff, pos)? {
LabelMeta::Pointer(ptr) if ptr >= pos => Err(ParseError::InvalidJump)?,
LabelMeta::Size(s) if s > MAX_LABEL_SIZE => Err(NameError::LabelLength(s))?,
LabelMeta::Size(s) if blen <= pos + s => Err(NameError::LabelLength(s))?,
LabelMeta::Size(s) if name.len as usize + s > MAX_NAME_SIZE => {
Err(NameError::NameLength(name.len as usize + s))?
}
LabelMeta::Size(s) if jumps == 0 => {
name.push_bytes(&buff[pos + 1..pos + s + 1])?;
pos += s + 1;
size += s + 1;
}
LabelMeta::Size(s) => {
name.push_bytes(&buff[pos + 1..pos + s + 1])?;
pos += s + 1;
}
LabelMeta::Pointer(ptr) if jumps == 0 => {
(pos, size, jumps) = (ptr, size + 2, jumps + 1);
}
LabelMeta::Pointer(ptr) => (pos, jumps) = (ptr, jumps + 1),
LabelMeta::End if jumps == 0 => {
name.labels.reverse();
return Ok((name, size + 1));
}
LabelMeta::End => {
name.labels.reverse();
return Ok((name, size));
}
}
}
}
fn push_bytes(&mut self, bytes: &'a [u8]) -> Result<(), NameError> {
if valid_label(bytes) {
let label = unsafe { str::from_utf8_unchecked(bytes) };
self.labels.push(label.into());
self.len += bytes.len() as u8 + 1;
Ok(())
} else {
Err(NameError::LabelContent)
}
}
#[inline]
pub fn serialize(&self, packet: &mut Vec<u8>) {
for label in self.iter_human() {
packet.push(label.len() as _);
packet.extend(label.as_bytes());
}
packet.push(0u8);
}
#[inline]
pub fn new() -> Self {
Name {
labels: Vec::with_capacity(INIT_NUM_LABELS),
len: 0,
}
}
#[inline]
pub fn tld(&self) -> Option<&'_ str> {
self.labels.first().map(|cow| cow.deref())
}
#[inline]
pub fn push_label(&mut self, label: Cow<'a, str>) -> Result<(), NameError> {
let len = label.len();
if label.is_empty() || len > MAX_LABEL_SIZE {
Err(NameError::LabelLength(len))
} else if len + self.len as usize > MAX_NAME_SIZE {
Err(NameError::NameLength(len + self.len as usize))
} else if !valid_label(label.as_bytes()) {
Err(NameError::LabelContent)
} else {
self.len += len as u8;
self.labels.push(label);
Ok(())
}
}
#[inline]
pub fn label_count(&self) -> usize {
self.labels.len()
}
#[inline]
pub fn is_subdomain(&self, sub: &Name<'_>) -> bool {
if self.labels.len() >= sub.labels.len() {
false
} else {
zip(self.iter_hierarchy(), sub.iter_hierarchy()).fold(true, |acc, (x, y)| acc && x == y)
}
}
#[inline]
pub fn iter_human(&self) -> impl DoubleEndedIterator<Item = &'_ str> {
self.iter_hierarchy().rev()
}
#[inline]
pub fn iter_hierarchy(&self) -> impl DoubleEndedIterator<Item = &'_ str> {
self.labels.iter().map(|cow| cow.deref())
}
}
fn valid_label(label: &[u8]) -> bool {
label
.iter()
.all(|&b| b.is_ascii_alphanumeric() || b == b'-')
}
enum LabelMeta {
End,
Size(usize),
Pointer(usize),
}
#[inline]
fn read_label_metadata(buff: &[u8], pos: usize) -> Result<LabelMeta, ParseError> {
let b = safe_u8_read(buff, pos)?;
match b {
0 => Ok(LabelMeta::End),
1..=0b0011_1111 => Ok(LabelMeta::Size(b as _)),
0b1100_0000..=0xFF => Ok(LabelMeta::Pointer(
(safe_u16_read(buff, pos)? ^ 0b1100_0000_0000_0000) as _,
)),
_ => Err(ParseError::LabelPrefix(b))?,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_labels() {
let valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";
let invalid = "hello.world";
assert!(valid_label(valid.as_bytes()));
assert!(!valid_label(invalid.as_bytes()));
}
#[test]
fn no_jumps() {
let buff = [
5, 104, 101, 108, 108, 111, 5, 119, 111, 114, 108, 100, 3, 99, 111, 109, 0, 1, 1, 1, ];
let (name, n) = Name::parse(&buff[..], 0).unwrap();
assert_eq!(n, 17);
assert_eq!(name.to_string(), "hello.world.com.".to_string())
}
#[test]
fn with_jumps() {
let buff = [
5, 119, 111, 114, 108, 100, 3, 99, 111, 109, 0, 1, 1, 1, 5, 104, 101, 108, 108, 111, 192, 0, 1, 1, 1, 1, 1, 1, ];
let (name, n) = Name::parse(&buff[..], 14).unwrap();
assert_eq!(n, 8);
assert_eq!(name.to_string(), "hello.world.com.".to_string())
}
#[test]
fn name_parse_with_jumps() {
let buff = [
5, 119, 111, 114, 108, 100, 3, 99, 111, 109, 0, 1, 1, 1, 5, 104, 101, 108, 108, 111, 192, 0, 1, 1, 1, 1, 1, 1, ];
let (name, n) = Name::parse(&buff[..], 14).unwrap();
assert_eq!(n, 8);
assert_eq!(name.to_string(), "hello.world.com.".to_string())
}
#[test]
fn serialize() {
let buff = [
5, 104, 101, 108, 108, 111, 5, 119, 111, 114, 108, 100, 3, 99, 111, 109, 0, 1, 1, 1, ];
let (name, _) = Name::parse(&buff[..], 0).unwrap();
assert_eq!(name.to_string(), "hello.world.com.".to_string());
let out: Vec<u8> = name.into();
assert_eq!(&buff[..17], &out[..17])
}
#[test]
fn get_tld() {
let mut name = Name::new();
name.push_label("com".into()).unwrap();
name.push_label("world".into()).unwrap();
name.push_label("hello".into()).unwrap();
let tld = name.tld();
assert_eq!(tld, Some("com"));
}
#[test]
fn add_str_subdomain() {
let buff = [5, 119, 111, 114, 108, 100, 3, 99, 111, 109, 0, 1, 1, 1]; let (mut name, _) = Name::parse(&buff[..], 0).unwrap();
name.push_label("hello".into()).unwrap();
assert_eq!(name.to_string(), "hello.world.com.".to_string())
}
#[test]
fn add_string_subdomain() {
let sub = String::from("hello");
let buff = [5, 119, 111, 114, 108, 100, 3, 99, 111, 109, 0, 1, 1, 1]; let (mut name, _) = Name::parse(&buff[..], 0).unwrap();
name.push_label(sub.into()).unwrap();
assert_eq!(name.to_string(), "hello.world.com.".to_string())
}
#[test]
fn iterate_human() {
let mut name = Name::new();
name.push_label("com".into()).unwrap();
name.push_label("world".into()).unwrap();
name.push_label("hello".into()).unwrap();
let mut human = name.iter_human();
assert_eq!(human.next(), Some("hello"));
assert_eq!(human.next(), Some("world"));
assert_eq!(human.next(), Some("com"));
}
#[test]
fn iterate_hierarchy() {
let mut name = Name::new();
name.push_label("com".into()).unwrap();
name.push_label("world".into()).unwrap();
name.push_label("hello".into()).unwrap();
let mut human = name.iter_hierarchy();
assert_eq!(human.next(), Some("com"));
assert_eq!(human.next(), Some("world"));
assert_eq!(human.next(), Some("hello"));
}
#[test]
fn check_subdomain() {
let mut parent = Name::new();
parent.push_label("com".into()).unwrap();
parent.push_label("world".into()).unwrap();
let mut sub = Name::new();
sub.push_label("com".into()).unwrap();
sub.push_label("world".into()).unwrap();
sub.push_label("hello".into()).unwrap();
assert!(parent.is_subdomain(&sub));
assert!(!sub.is_subdomain(&parent));
}
#[test]
fn root_subdomain() {
let root = Name::default();
let subd = Name::try_from("example.com").unwrap();
assert!(root.is_subdomain(&subd));
assert!(!subd.is_subdomain(&root));
}
}