dns-server 0.2.6

A threaded DNS server.
Documentation
use crate::DnsError;
use core::convert::TryFrom;
use core::fmt::{Display, Formatter};
use fixed_buffer::{escape_ascii, FixedBuf};

/// > 3.3. Standard RRs
/// >
/// > ...
/// >
/// > `character-string` is a single length octet followed by that number of characters.
/// > `character-string` is treated as binary information, and can be up to 256 characters in length
/// > (including the length octet).
///
/// <https://datatracker.ietf.org/doc/html/rfc1035#section-3.3>
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DnsString(Vec<u8>);
impl DnsString {
    /// # Errors
    /// Returns an error when `value` is not a valid DNS string.
    pub fn new(value: impl AsRef<[u8]>) -> Result<Self, String> {
        let bytes = value.as_ref();
        if bytes.len() > 255 {
            return Err(format!(
                "longer than 255 bytes, not a valid DNS string: '{}'",
                escape_ascii(bytes)
            ));
        }
        Ok(Self(bytes.to_vec()))
    }

    /// # Errors
    /// Returns an error when `buf` does not contain a valid string.
    pub fn read_one<const N: usize>(buf: &mut FixedBuf<N>) -> Result<DnsString, DnsError> {
        let len = buf.try_read_byte().ok_or(DnsError::Truncated)?;
        let bytes = buf
            .try_read_bytes(len as usize)
            .ok_or(DnsError::Truncated)?;
        Ok(Self(bytes.to_vec()))
    }

    /// # Errors
    /// Returns an error when `buf` does not contain a valid string.
    pub fn read_multiple<const N: usize>(
        buf: &mut FixedBuf<N>,
    ) -> Result<Vec<DnsString>, DnsError> {
        let mut strings = Vec::new();
        while !buf.is_empty() {
            let string = Self::read_one(buf)?;
            strings.push(string);
        }
        Ok(strings)
    }

    /// # Errors
    /// Returns an error when `buf` fills up.
    pub fn write<const N: usize>(&self, out: &mut FixedBuf<N>) -> Result<(), DnsError> {
        let len: u8 = self
            .0
            .len()
            .try_into()
            .map_err(|_e| DnsError::StringTooLong)?;
        out.write_bytes(&[len])
            .map_err(|_| DnsError::ResponseBufferFull)?;
        out.write_bytes(self.0.as_slice())
            .map_err(|_| DnsError::ResponseBufferFull)?;
        Ok(())
    }

    /// # Errors
    /// Returns an error when the string is longer than 255 bytes.  This cannot happen.
    pub fn as_bytes(&self) -> Result<FixedBuf<256>, DnsError> {
        let mut buf: FixedBuf<256> = FixedBuf::new();
        self.write(&mut buf)?;
        Ok(buf)
    }

    #[must_use]
    pub fn inner(&self) -> &[u8] {
        self.0.as_slice()
    }
}
impl Display for DnsString {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
        write!(f, "{}", escape_ascii(self.0.as_slice()))
    }
}
impl TryFrom<&'static str> for DnsString {
    type Error = String;

    fn try_from(value: &'static str) -> Result<Self, Self::Error> {
        DnsString::new(value)
    }
}

#[cfg(test)]
#[test]
fn test_str() {
    DnsString::new("0").unwrap();
    DnsString::new("a").unwrap();
    DnsString::new("a".repeat(255).as_str()).unwrap();
    assert_eq!(
        "longer than 255 bytes, not a valid DNS string: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'",
        DnsString::new("a".repeat(256).as_str()).unwrap_err().as_str()
    );
}

#[cfg(test)]
#[test]
fn test_bytes() {
    DnsString::new([]).unwrap();
    DnsString::new([97u8]).unwrap();
    DnsString::new([97u8; 255]).unwrap();
    assert_eq!(
        "longer than 255 bytes, not a valid DNS string: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'",
        DnsString::new([97u8; 256]).unwrap_err().as_str()
    );
    for c in 0..=255u8 {
        let array = [c];
        DnsString::new(array).unwrap_or_else(|_| panic!("{}", escape_ascii(&array)));
    }
}

// TODO: Test read()
// TODO: Test write()

#[cfg(test)]
#[test]
fn test_inner() {
    assert_eq!(b"abc", DnsString::new("abc").unwrap().inner());
}

#[cfg(test)]
#[test]
fn test_display() {
    assert_eq!(
        "\\x00abc",
        format!("{}", DnsString::new(b"\x00abc").unwrap())
    );
}

// TODO: Test TryFrom