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);
#[derive(Debug, displaydoc::Display)]
#[prefix_enum_doc_attributes]
pub enum NameError {
Grow(GrowError),
NameTooLong,
LabelTooLong,
EmptyLabel,
DecimalEscapeRange(u16),
DecimalEscapeSyntax(u8),
UnfinishedEscape,
UnfinishedLabel,
Question,
Record,
}
#[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);
}
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)
}
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]:
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]:
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>>:
pub fn return_to_rdata(mut self) -> Result<RecordBuilder<'b, P, RecordData>, NameError> = {
Ok(self.finish0()?)
}
}
}
impl<'b> NameBuilder<'b, Sink<'b>> {
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(())
}
}