nonymous 0.7.0

DNS protocol and algorithm library
Documentation
use core::marker::PhantomData;

use crate::{
    core::{Class, Type},
    emit::{
        message::{MessageBuilder, QuestionStep, RecordStep},
        question::QuestionBuilder,
        record::{RecordBuilder, RecordData, RecordName},
        Buffer, Builder, ChildBuilder, GrowError, PushBuilder, Sink,
    },
};

error!(NameError, Grow);
/// failed to emit name
#[derive(Debug, displaydoc::Display)]
#[prefix_enum_doc_attributes]
pub enum NameError {
    /// not enough space
    Grow(GrowError),

    /// name exceeds 255 octets excluding null label (RFC 2181 § 11)
    NameTooLong,

    /// label exceeds 63 octets excluding length octet (RFC 1035 § 3.1)
    LabelTooLong,

    /// empty label (terminating label is emitted automatically)
    EmptyLabel,

    /// decimal escape too high (\\{0:03} > \\255)
    DecimalEscapeRange(u16),

    /// decimal escape has non-ASCII-decimal octet ({0:X}h)
    DecimalEscapeSyntax(u8),

    /// label ended during escape sequence
    UnfinishedEscape,

    /// name ended during label
    UnfinishedLabel,

    /// error updating parent question
    Question,

    /// error updating parent record
    Record,
}

/// # Up transitions
/// * [`finish_question`][`NameBuilder::finish_question`] — for QNAME in [`QuestionBuilder`]
/// * [`try_into_rdata`][`NameBuilder::try_into_rdata`] — for NAME in [`RecordBuilder`]
/// * [`return_to_rdata`][`NameBuilder::return_to_rdata`] — for RDATA in [`RecordBuilder`]
/// * [`finish`][`NameBuilder::finish`] — for direct use with [`Sink`]
#[must_use]
pub struct NameBuilder<'b, P> {
    buffer: PhantomData<&'b mut dyn Buffer>,
    parent: P,
    len: usize,
}

impl<'b, P: Builder<'b>> ChildBuilder<'b, P> for NameBuilder<'b, P> {
    fn parent(&mut self) -> &mut P {
        &mut self.parent
    }
}

impl<'b, P: Builder<'b>> PushBuilder<'b, P> for NameBuilder<'b, P> {
    type Error = NameError;
    fn push(parent: P) -> Result<Self, NameError> {
        Ok(Self {
            buffer: PhantomData,
            parent,
            len: 0,
        })
    }
}

builder! {
    <'b, P> NameBuilder {
        Builder;

        @ <P>:
            pub fn label(mut self, value: &[u8]) -> Result<Self, NameError> = {
                match value.len() {
                    0 => return Err(NameError::EmptyLabel),
                    x if x > 63 => return Err(NameError::LabelTooLong),
                    _ => {}
                }

                if self.len + 1 + value.len() > 255 {
                    return Err(NameError::NameTooLong);
                }

                // guard clause makes the as u8 safe
                self.sink().grow_mut(1).map_err(NameError::Grow)?[0] = value.len() as u8;

                self.sink()
                    .grow_mut(value.len())
                    .map_err(NameError::Grow)?
                    .copy_from_slice(value);
                self.len += 1 + value.len();

                Ok(self)
            }

            pub fn labels(mut self, mut source: &[u8]) -> Result<Self, NameError> = {
                let mut label = [0u8; 63];

                while let Some((label, rest)) = parse_dotted_name(&mut label, source)? {
                    self = self.label(label)?;
                    source = rest;
                }

                Ok(self)
            }
    }
}

pub(crate) fn parse_dotted_name<'i, 'r>(
    result: &'r mut [u8; 63],
    input: &'i [u8],
) -> Result<Option<(&'r [u8], &'i [u8])>, NameError> {
    #[derive(PartialEq)]
    enum State {
        Start,
        Backslash,
        Tens,
        Ones,
    }

    let mut len = 0;
    let mut i = 0;

    let mut state = State::Start;
    let mut octet: u16 = 0;

    while i < input.len() {
        state = match (state, input[i]) {
            (State::Start, b'.') => {
                return Ok(Some((&result[0..len], &input[i + 1..])));
            }
            (State::Start, b'\\') => State::Backslash,
            (State::Start, x) => {
                result[len] = x;
                len += 1;
                State::Start
            }
            (State::Backslash, x) if x.is_ascii_digit() => {
                octet = u16::from(x - b'0');
                State::Tens
            }
            (State::Backslash, x) => {
                result[len] = x;
                len += 1;
                State::Start
            }
            (State::Tens, x) if x.is_ascii_digit() => {
                octet = 10 * octet + u16::from(x - b'0');
                State::Ones
            }
            (State::Ones, x) if x.is_ascii_digit() => {
                octet = 10 * octet + u16::from(x - b'0');
                result[len] = octet
                    .try_into()
                    .map_err(|_| NameError::DecimalEscapeRange(octet))?;
                len += 1;
                State::Start
            }
            (_, x) => {
                return Err(NameError::DecimalEscapeSyntax(x));
            }
        };
        i += 1;
    }

    if state != State::Start {
        return Err(NameError::UnfinishedEscape);
    }

    if len > 0 {
        return Err(NameError::UnfinishedLabel);
    }

    Ok(None)
}

/// <h2>Up transitions</h2>
impl<'b, P> NameBuilder<'b, P> {}
builder! {
    <'b, P> NameBuilder {
        @ <P>:
            fn finish0(mut self) -> Result<P, NameError> = {
                self.sink().grow_range(1).map_err(NameError::Grow)?;

                Ok(self.parent)
            }
        @ <QuestionBuilder<'b, MessageBuilder<'b, P, Q>>> [Q: QuestionStep]:
            /// Finish building the name and the containing question, then return to the message builder.
            pub fn finish_question(
                mut self,
                qtype: Type,
                qclass: Class,
            ) -> Result<MessageBuilder<'b, P, Q>, NameError> = {
                Ok(self.finish0()?.finish(qtype, qclass).map_err(|_| NameError::Question)?)
            }
        @ <RecordBuilder<'b, MessageBuilder<'b, P, Q>, RecordName>> [Q: RecordStep]:
            /// Finish building the name of the record, then return to the record builder.
            pub fn try_into_rdata(mut self) -> Result<RecordBuilder<'b, MessageBuilder<'b, P, Q>, RecordData>, NameError> = {
                Ok(self.finish0()?.try_into_rdata().map_err(|_| NameError::Record)?)
            }
        @ <RecordBuilder<'b, P, RecordData>>:
            /// Finish building a name in RDATA, then return to the record builder.
            pub fn return_to_rdata(mut self) -> Result<RecordBuilder<'b, P, RecordData>, NameError> = {
                Ok(self.finish0()?)
            }
    }
}

impl<'b> NameBuilder<'b, Sink<'b>> {
    /// Finish building the name and return to the parent builder.
    pub fn finish(self) -> Result<Sink<'b>, NameError> {
        self.finish0()
    }
}

#[cfg(test)]
mod test {
    use crate::emit::name::{parse_dotted_name, NameError};

    #[test]
    fn test_parse_dotted_name() -> Result<(), NameError> {
        let mut buffer = [0u8; 63];

        let mut input = &b"daria.daz.cat."[..];
        let (label, rest) = parse_dotted_name(&mut buffer, input)?.unwrap();
        assert_eq!((label, rest), (&b"daria"[..], &b"daz.cat."[..]));

        input = rest;
        let (label, rest) = parse_dotted_name(&mut buffer, input)?.unwrap();
        assert_eq!((label, rest), (&b"daz"[..], &b"cat."[..]));

        input = rest;
        let (label, rest) = parse_dotted_name(&mut buffer, input)?.unwrap();
        assert_eq!((label, rest), (&b"cat"[..], &b""[..]));

        input = rest;
        let result = parse_dotted_name(&mut buffer, input)?;
        assert_eq!(result, None);

        let input = &b"."[..];
        let (label, rest) = parse_dotted_name(&mut buffer, input)?.unwrap();
        assert_eq!((label, rest), (&b""[..], &b""[..])); // !

        Ok(())
    }
}