use std::{
fmt::Display,
io,
net::{Ipv4Addr, Ipv6Addr},
};
use bytes::Buf;
use endpoint::{EndpointAddr, be_endpoint_addr_compat};
use nom::{
Parser,
bytes::streaming::take,
combinator::map,
number::streaming::{be_u16, be_u32, be_u128},
};
use ptr::{Ptr, be_ptr};
use srv::{Srv, be_srv};
use txt::Txt;
use super::name::{Name, be_name};
pub mod endpoint;
pub mod ptr;
pub mod srv;
pub mod txt;
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct ResourceRecord {
pub(crate) name: Name,
pub(crate) typ: Type,
pub(crate) multicast_unique: bool,
pub(crate) cls: Class,
pub(crate) ttl: u32,
pub(crate) data: RData,
}
impl ResourceRecord {
pub fn data(&self) -> &RData {
&self.data
}
pub fn name(&self) -> Name {
self.name.clone()
}
pub fn typ(&self) -> Type {
self.typ
}
pub fn cls(&self) -> Class {
self.cls
}
pub fn ttl(&self) -> u32 {
self.ttl
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum Class {
IN = 1,
CS = 2,
CH = 3,
HS = 4,
}
impl TryFrom<u16> for Class {
type Error = io::Error;
fn try_from(value: u16) -> Result<Self, Self::Error> {
let value = value & 0x7FFF; let class = match value {
1 => Self::IN,
2 => Self::CS,
3 => Self::CH,
4 => Self::HS,
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Unknown record class: {value}"),
));
}
};
Ok(class)
}
}
impl From<Class> for u16 {
fn from(value: Class) -> Self {
match value {
Class::IN => 1,
Class::CS => 2,
Class::CH => 3,
Class::HS => 4,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum Type {
A,
Ns,
#[allow(clippy::upper_case_acronyms)]
AAAA,
Cname,
Txt,
Srv,
Ptr,
E,
}
impl TryFrom<u16> for Type {
type Error = io::Error;
fn try_from(value: u16) -> Result<Self, Self::Error> {
let typ = match value {
1 => Self::A,
2 => Self::Ns,
28 => Self::AAAA,
5 => Self::Cname,
16 => Self::Txt,
33 => Self::Srv,
12 => Self::Ptr,
266 => Self::E,
267..=269 => Self::E,
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Unknown record type: {value}"),
));
}
};
Ok(typ)
}
}
impl From<Type> for u16 {
fn from(value: Type) -> Self {
match value {
Type::A => 1,
Type::Ns => 2,
Type::AAAA => 28,
Type::Cname => 5,
Type::Txt => 16,
Type::Srv => 33,
Type::Ptr => 12,
Type::E => 266,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum RData {
A(Ipv4Addr),
AAAA(Ipv6Addr),
CName(Name),
Txt(Txt),
Srv(Srv),
Ptr(Ptr),
E(EndpointAddr),
}
impl Display for RData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RData::A(ip) => write!(f, "{ip}"),
RData::AAAA(ip) => write!(f, "{ip}"),
RData::CName(name) => write!(f, "CName({name})"),
RData::Txt(txt) => write!(f, "{txt:?})"),
RData::Srv(srv) => write!(f, "{srv:?}"),
RData::Ptr(ptr) => write!(f, "{ptr:?}"),
RData::E(e) => write!(f, "{e}"),
}
}
}
pub fn be_record<'a>(input: &'a [u8], origin: &'a [u8]) -> nom::IResult<&'a [u8], ResourceRecord> {
let (remain, name) = be_name(input, origin)?;
let (remain, typ) = be_u16(remain)?;
let (remain, cls) = be_u16(remain)?;
let (remain, ttl) = be_u32(remain)?;
let (mut remain, rdlen) = be_u16(remain)?;
let Ok(typ) = Type::try_from(typ) else {
if remain.len() < rdlen as usize {
return Err(nom::Err::Incomplete(nom::Needed::new(
rdlen as usize - remain.len(),
)));
}
remain.advance(rdlen as usize);
return Err(nom::Err::Error(nom::error::make_error(
remain,
nom::error::ErrorKind::Alt,
)));
};
if remain.len() < rdlen as usize {
return Err(nom::Err::Incomplete(nom::Needed::new(
rdlen as usize - remain.len(),
)));
}
let (remain_after_rdata, rdata_bytes) = take(rdlen)(remain)?;
let (rdata_remain, rdata) = be_rdata(rdata_bytes, origin, typ, rdlen)?;
if !rdata_remain.is_empty() {
return Err(nom::Err::Error(nom::error::make_error(
remain_after_rdata,
nom::error::ErrorKind::Eof,
)));
}
let mut remain = remain_after_rdata;
let multicast_unique = cls & 0x8000 == 0x8000;
let cls = cls & 0x7FFF;
let Ok(cls) = Class::try_from(cls) else {
if remain.len() < rdlen as usize {
return Err(nom::Err::Incomplete(nom::Needed::new(
rdlen as usize - remain.len(),
)));
}
remain.advance(rdlen as usize);
return Err(nom::Err::Error(nom::error::make_error(
remain,
nom::error::ErrorKind::Alt,
)));
};
Ok((
remain,
ResourceRecord {
name,
typ,
multicast_unique,
cls,
ttl,
data: rdata,
},
))
}
fn be_rdata<'a>(
input: &'a [u8],
origin: &'a [u8],
typ: Type,
rdlen: u16,
) -> nom::IResult<&'a [u8], RData> {
match typ {
Type::A => map(be_u32, |ip| RData::A(Ipv4Addr::from(ip))).parse(input),
Type::AAAA => map(be_u128, |ip| RData::AAAA(Ipv6Addr::from(ip))).parse(input),
Type::Cname => be_name(input, origin).map(|(remain, name)| (remain, RData::CName(name))),
Type::Txt => map(take(rdlen), |txt: &[u8]| RData::Txt(Txt::new(txt.to_vec()))).parse(input),
Type::Srv => {
let (remain, srv) = be_srv(input, origin)?;
Ok((remain, RData::Srv(srv)))
}
Type::Ptr => be_ptr(input, origin).map(|(remain, ptr)| (remain, RData::Ptr(ptr))),
Type::Ns => be_name(input, origin).map(|(remain, name)| (remain, RData::CName(name))),
Type::E => be_endpoint_addr_compat(input, rdlen).map(|(remain, e)| (remain, RData::E(e))),
}
}
#[cfg(test)]
mod test {
use super::*;
fn record_prefix_for_a(name: &[u8], rdlen: u16) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(name);
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(&0u32.to_be_bytes());
buf.extend_from_slice(&rdlen.to_be_bytes());
buf
}
#[test]
fn parse_record_incomplete_rdata_returns_incomplete() {
let name = b"\x07example\x03com\x00";
let mut buf = record_prefix_for_a(name, 4);
buf.extend_from_slice(&[127, 0, 0]);
let ret = be_record(&buf, &buf);
assert!(matches!(ret, Err(nom::Err::Incomplete(_))));
}
#[test]
fn parse_record_extra_rdata_bytes_is_error() {
let name = b"\x07example\x03com\x00";
let mut buf = record_prefix_for_a(name, 5);
buf.extend_from_slice(&[127, 0, 0, 1, 9]);
let ret = be_record(&buf, &buf);
assert!(matches!(ret, Err(nom::Err::Error(_))));
}
}