blob: 29aac94a78544e2cb4114eba2fc6fc40aea3dafd [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Protocol contains functions and traits for mDNS protocol parsing and packet
//! creation.
use std::collections::HashSet;
use std::fmt::{Debug, Display, Formatter};
use std::mem;
use std::net::IpAddr;
use packet::{BufferView, BufferViewMut, InnerPacketBuilder, ParsablePacket, ParseMetadata};
use zerocopy::{
byteorder::network_endian::{U16, U32},
AsBytes, ByteSlice, ByteSliceMut, FromBytes, FromZeros, NoCell, Ref, Unaligned,
};
const IPV4_SIZE: usize = 4;
const IPV6_SIZE: usize = 16;
const SRV_PAYLOAD_SIZE_OCTETS: u16 = 6;
const DOMAIN_COMPRESSION_MASK_U8: u8 = 0xc0;
const DOMAIN_COMPRESSION_MASK_U16: u16 = 0xc000;
const IS_RESPONSE_MASK: u16 = 0x8000;
// https://tools.ietf.org/html/rfc1035#section-3.1
const MAX_DOMAIN_SIZE: usize = 255;
const MAX_LABEL_SIZE: usize = 63;
fn is_compression_byte(b: u8) -> bool {
b & DOMAIN_COMPRESSION_MASK_U8 == DOMAIN_COMPRESSION_MASK_U8
}
fn unwrap_domain_pointer(i: u16) -> u16 {
i ^ DOMAIN_COMPRESSION_MASK_U16
}
/// Packet builder that doesn't do any auxiliary storage.
pub trait EmbeddedPacketBuilder {
fn bytes_len(&self) -> usize;
fn serialize<B: ByteSliceMut, BV: BufferViewMut<B>>(&self, bv: &mut BV);
/// Return the output of packet building as a Vec<u8>, useful for tests that don't care about
/// zerocopy resource constraints.
fn bytes(&self) -> Vec<u8> {
let mut vec = Vec::with_capacity(self.bytes_len());
vec.resize(self.bytes_len(), 0u8);
self.serialize(&mut &mut vec.as_mut_slice());
vec
}
}
struct BufferViewWrapper<B>(B);
impl<B: ByteSlice + Clone> BufferView<B> for BufferViewWrapper<B> {
fn into_rest(self) -> B {
self.0
}
fn take_front(&mut self, n: usize) -> Option<B> {
if self.len() >= n {
let (ret, next) = self.0.clone().split_at(n);
self.0 = next;
Some(ret)
} else {
None
}
}
/// This isn't implemented as it currently is not used in this
/// implementation.
fn take_back(&mut self, _n: usize) -> Option<B> {
unimplemented!()
}
}
impl<B: ByteSlice> AsRef<[u8]> for BufferViewWrapper<B> {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
/// Determines which error was run into during parsing.
///
/// For ones that contain lengths, this tells which length was encountered during
/// parsing. For example, `RDataLen` is a pretty general error relating to the
/// RData being the wrong size for the included type (both the size and type
/// are included. More below).
///
/// For `RData` errors:
/// -- `Type::A` the size was not found to be an IPv4 address.
/// -- `Type::Aaaa` the size was not found to be an IPv6 address.
/// -- `Type::Srv` the size of RData was not large enough to fit a SRV record
/// header as well as a payload.
///
/// `BadPointerIndex` returns the last encountered pointer that attempted to
/// reference data beyond the bounds of the available packet buffer.
///
/// `LabelTooLong` refers to there being too long of a label byte when parsing.
///
/// `DomainTooLong` refers to overrunning the maximum size of a domain
/// (255 bytes) when parsing.
#[derive(Debug, PartialEq)]
pub enum ParseError {
RDataLen(Type, u16),
Malformed,
UnexpectedZeroCharacter,
PointerCycle,
BadPointerIndex(u16),
DomainTooLong(usize),
LabelTooLong(usize),
UnknownType(u16),
UnknownClass(u16),
}
/// Standard mDNS types supported in this protocol library.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Type {
A,
Aaaa,
Ptr,
Srv,
Txt,
}
impl From<Type> for u16 {
fn from(value: Type) -> u16 {
match value {
Type::A => 1,
Type::Aaaa => 28,
Type::Ptr => 12,
Type::Srv => 33,
Type::Txt => 16,
}
}
}
impl TryFrom<u16> for Type {
type Error = ParseError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
1 => Ok(Type::A),
28 => Ok(Type::Aaaa),
12 => Ok(Type::Ptr),
33 => Ok(Type::Srv),
16 => Ok(Type::Txt),
v => Err(ParseError::UnknownType(v)),
}
}
}
/// Standard DNS classes supported by this protocol library.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Class {
In,
Any,
}
impl Class {
/// Used for mapping with flush bool and unicast bool.
fn into_u16_with_bool(self, b: bool) -> u16 {
u16::from(self) | (b as u16) << 15
}
}
impl From<Class> for u16 {
fn from(value: Class) -> u16 {
match value {
Class::In => 1,
Class::Any => 255,
}
}
}
impl TryFrom<u16> for Class {
type Error = ParseError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
1 => Ok(Class::In),
255 => Ok(Class::Any),
v => Err(ParseError::UnknownClass(v)),
}
}
}
/// Represents an mDNS packet header.
#[repr(C)]
#[derive(FromZeros, FromBytes, AsBytes, NoCell, Unaligned)]
pub struct Header {
id: U16,
flags: U16,
question_count: U16,
answer_count: U16,
authority_count: U16,
additional_count: U16,
}
impl Header {
/// Returns true if this is a query (the first bit is zero).
pub fn is_query(&self) -> bool {
!self.is_response()
}
/// Returns true if this is a response (the first bit is 1).
pub fn is_response(&self) -> bool {
self.flags.get() & IS_RESPONSE_MASK != 0
}
/// Returns the question count of this header.
pub fn question_count(&self) -> usize {
self.question_count.get().into()
}
/// Returns the answer count of this header.
pub fn answer_count(&self) -> usize {
self.answer_count.get().into()
}
/// Returns the authority count of this header.
pub fn authority_count(&self) -> usize {
self.authority_count.get().into()
}
/// Returns the additional record count of this header.
pub fn additional_count(&self) -> usize {
self.additional_count.get().into()
}
}
/// Represents a parsed mDNS question.
pub struct Question<B: ByteSlice> {
pub domain: Domain<B>,
pub qtype: Type,
pub class: Class,
pub unicast: bool,
}
impl<B: ByteSlice + Copy> Question<B> {
fn parse<BV: BufferView<B>>(buffer: &mut BV, parent: Option<B>) -> Result<Self, ParseError> {
let domain = Domain::parse(buffer, parent)?;
let qtype = buffer.take_obj_front::<U16>().ok_or(ParseError::Malformed)?;
let class_and_ucast = buffer.take_obj_front::<U16>().ok_or(ParseError::Malformed)?;
let unicast: bool = class_and_ucast.get() & (1u16 << 15) != 0;
let class: u16 = class_and_ucast.get() & 0x7fff;
Ok(Self { domain, qtype: qtype.get().try_into()?, class: class.try_into()?, unicast })
}
}
impl<B: ByteSlice + Copy> Display for Question<B> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl<B: ByteSlice + Copy> Debug for Question<B> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}: type {:?}, class {:?}, unicast {}",
self.domain, self.qtype, self.class, self.unicast,
)
}
}
/// A packet builder for an mDNS question.
pub struct QuestionBuilder {
domain: DomainBuilder,
qtype: Type,
class: Class,
unicast: bool,
}
impl QuestionBuilder {
/// Constructs a QuestionBuilder.
pub fn new(domain: DomainBuilder, qtype: Type, class: Class, unicast: bool) -> Self {
Self { domain, qtype, class, unicast }
}
}
impl EmbeddedPacketBuilder for QuestionBuilder {
fn bytes_len(&self) -> usize {
self.domain.bytes_len()
+ mem::size_of::<U16>() // type
+ mem::size_of::<U16>() // class + unicast
}
fn serialize<B: ByteSliceMut, BV: BufferViewMut<B>>(&self, bv: &mut BV) {
self.domain.serialize(bv);
bv.take_obj_front::<U16>().unwrap().set(self.qtype.into());
bv.take_obj_front::<U16>().unwrap().set(self.class.into_u16_with_bool(self.unicast));
}
}
/// A parsed AAAA type record.
#[derive(FromZeros, FromBytes, NoCell)]
pub struct Aaaa([u8; IPV6_SIZE]);
/// A parsed A type record.
#[derive(FromZeros, FromBytes, NoCell)]
pub struct A([u8; IPV4_SIZE]);
/// A parsed SRV type record.
pub struct SrvRecord<B: ByteSlice> {
priority: u16,
weight: u16,
port: u16,
domain: Domain<B>,
}
impl<B: ByteSlice + Copy> SrvRecord<B> {
fn parse<BV: BufferView<B>>(
buffer: &mut BV,
parent: Option<B>,
len_limit: u16,
) -> Result<Self, ParseError> {
let priority = buffer.take_obj_front::<U16>().ok_or(ParseError::Malformed)?.get();
let weight = buffer.take_obj_front::<U16>().ok_or(ParseError::Malformed)?.get();
let port = buffer.take_obj_front::<U16>().ok_or(ParseError::Malformed)?.get();
let domain_buf = buffer.take_front(len_limit as usize).ok_or(ParseError::Malformed)?;
// Needs a length limit as the SRV record necessitates it.
let mut bv = BufferViewWrapper(domain_buf);
let domain = Domain::parse(&mut bv, parent)?;
// The domain should have consumed the entire buffer view.
if bv.as_ref().len() != 0 {
return Err(ParseError::Malformed);
}
Ok(Self { priority, weight, port, domain })
}
}
/// A parsed RData (can be one of several types). If this has been parsed in a
/// PTR type, this will always be a `RData::Domain`. In a SRV type packet, this
/// will always be a `RData::Srv`, anything else, currently, will be converted
/// into `RData::Bytes`.
pub enum RData<B: ByteSlice> {
A(A),
Aaaa(Aaaa),
Bytes(B),
Domain(Domain<B>),
Srv(SrvRecord<B>),
}
impl<B: ByteSlice> RData<B> {
/// Returns a reference to a `SrvRecord` if possible `None` otherwise.
fn srv(&self) -> Option<&SrvRecord<B>> {
match self {
RData::Srv(s) => Some(s),
_ => None,
}
}
/// Returns a `Domain` if possible, `None` otherwise. If this is a
/// `RData::Srv` then this returns a reference to its internal `Domain`.
fn domain(&self) -> Option<&Domain<B>> {
match self {
RData::Domain(d) => Some(d),
RData::Srv(s) => Some(&s.domain),
_ => None,
}
}
/// Returns a `IpAddr` if possible, `None` otherwise.
pub fn ip_addr(&self) -> Option<IpAddr> {
match self {
RData::Aaaa(aaaa) => Some(IpAddr::from(aaaa.0)),
RData::A(a) => Some(IpAddr::from(a.0)),
_ => None,
}
}
// TODO(awdavies): This is used in tests, and will be useful for getting
// strings out of Txt data later when there is an actual client
// implementation.
#[allow(unused)]
pub fn bytes(&self) -> Option<&B> {
match self {
RData::Bytes(b) => Some(b),
_ => None,
}
}
}
/// Record is the catch-all container for Answer, Authority, and Additional
/// Records sections of an MDNS packet. This is the parsed version that is
/// created when parsing a packet.
pub struct Record<B: ByteSlice> {
pub domain: Domain<B>,
pub rtype: Type,
pub class: Class,
pub ttl: u32,
pub flush: bool,
pub rdata: RData<B>,
}
impl<B: ByteSlice + Copy> Display for Record<B> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl<B: ByteSlice + Copy> Debug for Record<B> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: type {:?}, class {:?}", self.domain, self.rtype, self.class)?;
match self.rtype {
Type::Srv => {
let srv = self.rdata.srv().unwrap();
write!(
f,
", priority {}, weight {}, port {}, target {}",
srv.priority, srv.weight, srv.port, srv.domain
)
}
Type::Ptr => write!(f, ", {}", self.rdata.domain().unwrap()),
// Don't print anything unless it's guaranteed that a certain type
// will have certain data (Srv and Ptr will always be their
// respective types for now).
_ => Ok(()),
}
}
}
fn valid_rdata_len(r: Type, len: u16) -> Result<u16, ParseError> {
match r {
Type::A => {
if len != IPV4_SIZE as u16 {
return Err(ParseError::RDataLen(r, len));
}
}
Type::Aaaa => {
if len != IPV6_SIZE as u16 {
return Err(ParseError::RDataLen(r, len));
}
}
Type::Srv => {
// Minimum size of SRV is the payload and enough for a domain
// pointer or domain of a single character.
if len < SRV_PAYLOAD_SIZE_OCTETS + 2 {
return Err(ParseError::RDataLen(r, len));
}
}
_ => (),
}
Ok(len)
}
impl<B: ByteSlice + Copy> Record<B> {
fn parse<BV: BufferView<B>>(buffer: &mut BV, parent: Option<B>) -> Result<Self, ParseError> {
let domain = Domain::parse(buffer, parent)?;
let rtype: Type =
buffer.take_obj_front::<U16>().ok_or(ParseError::Malformed)?.get().try_into()?;
let class_and_flush = buffer.take_obj_front::<U16>().ok_or(ParseError::Malformed)?;
let flush = class_and_flush.get() & (1u16 << 15) != 0;
let class: Class = (class_and_flush.get() & 0x7fff).try_into()?;
let ttl = buffer.take_obj_front::<U32>().ok_or(ParseError::Malformed)?.get();
let rdata_len = valid_rdata_len(
rtype,
buffer.take_obj_front::<U16>().ok_or(ParseError::Malformed)?.get(),
)?;
let rdata = match rtype {
Type::Srv => {
RData::Srv(SrvRecord::parse(buffer, parent, rdata_len - SRV_PAYLOAD_SIZE_OCTETS)?)
}
Type::Ptr => {
let ptr_domain_buf =
buffer.take_front(rdata_len.into()).ok_or(ParseError::Malformed)?;
let mut ptr_domain_bv = BufferViewWrapper(ptr_domain_buf);
let ptr_domain = Domain::parse(&mut ptr_domain_bv, parent)?;
// Must consume the whole buffer.
if ptr_domain_bv.as_ref().len() != 0 {
return Err(ParseError::Malformed);
}
RData::Domain(ptr_domain)
}
Type::A => {
let buf = buffer.take_front(IPV4_SIZE).ok_or(ParseError::Malformed)?;
RData::A(A::read_from(&buf).ok_or(ParseError::Malformed)?)
}
Type::Aaaa => {
let buf = buffer.take_front(IPV6_SIZE).ok_or(ParseError::Malformed)?;
RData::Aaaa(Aaaa::read_from(&buf).ok_or(ParseError::Malformed)?)
}
_ => RData::Bytes(buffer.take_front(rdata_len.into()).ok_or(ParseError::Malformed)?),
};
Ok(Self { domain, rtype, class, ttl, flush, rdata })
}
}
/// A record builder for creating a serialized version of an mDNS Record, which
/// is the catch-all type for Answers, Additional Records, and Authority
/// records.
pub struct RecordBuilder<'a> {
domain: DomainBuilder,
rtype: Type,
class: Class,
flush: bool,
ttl: u32,
rdata: &'a [u8],
}
impl<'a> RecordBuilder<'a> {
/// Constructs a `RecordBuilder`. Inputs must be valid for constructing an
/// mDNS message or this will panic.
///
/// # Panics
///
/// Will panic if `rdata` is too large to have its length stored in a `u16`,
/// which is necessary for successful serialization.
///
/// Will panic if `rdata` is empty, as this is not supported.
pub fn new(
domain: DomainBuilder,
rtype: Type,
class: Class,
flush: bool,
ttl: u32,
rdata: &'a [u8],
) -> Self {
// Will panic if attempting to create too large of a message.
let len = u16::try_from(rdata.len()).unwrap();
if len == 0 {
panic!("empty rdata not supported");
}
Self { domain, rtype, class, ttl, flush, rdata }
}
}
impl EmbeddedPacketBuilder for RecordBuilder<'_> {
fn bytes_len(&self) -> usize {
self.domain.bytes_len()
+ mem::size_of::<U16>() // type
+ mem::size_of::<U16>() // class + flush
+ mem::size_of::<U32>() // ttl
+ mem::size_of::<U16>() // rdata_len
+ self.rdata.len()
}
fn serialize<B: ByteSliceMut, BV: BufferViewMut<B>>(&self, bv: &mut BV) {
self.domain.serialize(bv);
bv.take_obj_front::<U16>().unwrap().set(self.rtype.into());
bv.take_obj_front::<U16>().unwrap().set(self.class.into_u16_with_bool(self.flush));
bv.take_obj_front::<U32>().unwrap().set(self.ttl);
bv.take_obj_front::<U16>().unwrap().set(u16::try_from(self.rdata.len()).unwrap());
bv.take_front(self.rdata.len()).unwrap().copy_from_slice(self.rdata);
}
}
/// A parsed mDNS message in its entirety.
pub struct Message<B: ByteSlice> {
pub header: Ref<B, Header>,
pub questions: Vec<Question<B>>,
pub answers: Vec<Record<B>>,
pub authority: Vec<Record<B>>,
pub additional: Vec<Record<B>>,
}
impl<B: ByteSlice + Copy> Message<B> {
#[inline]
fn parse_records<BV: BufferView<B>>(
buffer: &mut BV,
parent: Option<B>,
count: usize,
) -> Result<Vec<Record<B>>, ParseError> {
let mut records = Vec::<Record<B>>::with_capacity(count);
for _ in 0..count {
records.push(Record::parse(buffer, parent)?);
}
Ok(records)
}
}
impl<B: ByteSlice + Copy> ParsablePacket<B, ()> for Message<B> {
type Error = ParseError;
fn parse<BV: BufferView<B>>(buffer: BV, _args: ()) -> Result<Self, Self::Error> {
let body = buffer.into_rest();
let mut buffer = BufferViewWrapper(body);
let header = buffer.take_obj_front::<Header>().ok_or(ParseError::Malformed)?;
let mut questions: Vec<Question<B>> = Vec::with_capacity(header.question_count());
for _ in 0..header.question_count.get() {
questions.push(Question::parse(&mut buffer, Some(body))?);
}
let answers = Message::parse_records(&mut buffer, Some(body), header.answer_count())?;
let authority = Message::parse_records(&mut buffer, Some(body), header.authority_count())?;
let additional =
Message::parse_records(&mut buffer, Some(body), header.additional_count())?;
Ok(Self { header, questions, answers, authority, additional })
}
fn parse_metadata(&self) -> ParseMetadata {
// ParseMetadata is only needed if we do undo parse.
unimplemented!()
}
}
/// A builder for creating an mDNS message.
pub struct MessageBuilder<'a> {
pub id: u16,
pub flags: u16,
questions: Vec<QuestionBuilder>,
answers: Vec<RecordBuilder<'a>>,
authority: Vec<RecordBuilder<'a>>,
additional: Vec<RecordBuilder<'a>>,
}
impl<'a> MessageBuilder<'a> {
pub fn new(id: u16, is_query: bool) -> Self {
let mut flags = 0u16;
if !is_query {
flags |= IS_RESPONSE_MASK;
}
Self {
id,
flags,
questions: Vec::new(),
answers: Vec::new(),
authority: Vec::new(),
additional: Vec::new(),
}
}
pub fn add_question(&mut self, q: QuestionBuilder) {
self.questions.push(q);
}
pub fn add_answer(&mut self, a: RecordBuilder<'a>) {
self.answers.push(a);
}
pub fn add_authority(&mut self, a: RecordBuilder<'a>) {
self.authority.push(a);
}
pub fn add_additional(&mut self, a: RecordBuilder<'a>) {
self.additional.push(a);
}
}
impl InnerPacketBuilder for MessageBuilder<'_> {
fn bytes_len(&self) -> usize {
mem::size_of::<Header>()
+ self.questions.iter().fold(0, |r, s| r + s.bytes_len())
+ self.answers.iter().fold(0, |r, s| r + s.bytes_len())
+ self.authority.iter().fold(0, |r, s| r + s.bytes_len())
+ self.additional.iter().fold(0, |r, s| r + s.bytes_len())
}
fn serialize(&self, mut buffer: &mut [u8]) {
// Inherits BufferViewMut trait.
let mut bv = &mut buffer;
let mut header = bv.take_obj_front_zero::<Header>().unwrap();
header.id.set(self.id);
header.flags.set(self.flags);
header.question_count.set(self.questions.len() as u16);
header.answer_count.set(self.answers.len() as u16);
header.authority_count.set(self.authority.len() as u16);
header.additional_count.set(self.additional.len() as u16);
self.questions.iter().for_each(|e| e.serialize(&mut bv));
self.answers.iter().for_each(|e| e.serialize(&mut bv));
self.authority.iter().for_each(|e| e.serialize(&mut bv));
self.additional.iter().for_each(|e| e.serialize(&mut bv));
}
}
/// A parsed mDNS domain. There is no need to worry about message compression
/// when comparing against a string, and can be treated as a contiguous domain.
#[derive(PartialEq, Eq)]
pub struct Domain<B: ByteSlice> {
fragments: Vec<B>,
}
enum DomainData<B: ByteSlice> {
Domain(B),
Pointer(Option<B>, u16),
}
impl<B: ByteSlice + Copy> Domain<B> {
fn fmt_byte_slice(f: &mut Formatter<'_>, b: &B) -> std::fmt::Result {
let mut iter = b.as_ref().iter();
let mut idx = 0;
loop {
let opt = iter.next();
// Here it's possible that there's no null terminator (in the case
// of pointers), but for any valid domain that has passed the
// `parse` method, this should not be an issue.
let c = match opt {
Some(v) => v,
None => break,
};
if *c == 0 {
break;
}
if idx > 0 {
f.write_str(".")?;
}
let skip = *c as usize;
for _ in 0..skip {
write!(f, "{}", *iter.next().unwrap() as char)?;
}
idx += 1;
}
Ok(())
}
fn partial_eq_helper_slice<BV: BufferView<B>>(
other_bv: &mut BV,
b: &B,
) -> Result<bool, ParseError> {
// TODO(awdavies): This comparison and builder logic should probably
// abstracted a bit.
let mut dref = &mut b.as_ref();
// Gets BufferView trait.
let bv = &mut dref;
loop {
let domain_len = match bv.take_byte_front() {
Some(d) => d,
None => break,
};
if domain_len == 0 {
break;
}
let mut other_len = 0u8;
loop {
match other_bv.take_byte_front() {
// At end of string or a '.' symbol.
Some(46) | None => {
if domain_len != other_len {
return Ok(false);
}
break;
}
Some(c) => {
if c != bv.take_byte_front().ok_or(ParseError::Malformed)? {
return Ok(false);
}
other_len += 1;
}
}
}
}
if bv.len() > 0 {
return Ok(false);
}
Ok(true)
}
fn partial_eq_helper_str(&self, other: &str) -> Result<bool, ParseError> {
let mut domain_bv = BufferViewWrapper(other.as_bytes());
for d in self.fragments.iter() {
if !Domain::partial_eq_helper_slice(&mut domain_bv, &d.as_ref())? {
return Ok(false);
}
}
return Ok(domain_bv.as_ref().len() == 0);
}
fn parse_domain_helper<BV: BufferView<B>>(
buffer: &mut BV,
) -> Result<DomainData<B>, ParseError> {
let mut iter = buffer.as_ref().iter();
let mut idx = 0;
loop {
let domain_len = *iter.next().ok_or(ParseError::Malformed)?;
idx += 1;
// If this is a compression byte, then either we're at the end of
// the domain, or this is the first byte of the domain and the whole
// thing is determined by the pointer.
if is_compression_byte(domain_len) {
return match idx {
1 => {
let location =
buffer.take_obj_front::<U16>().ok_or(ParseError::Malformed)?.get();
Ok(DomainData::Pointer(None, unwrap_domain_pointer(location)))
}
_ => {
let data = buffer.take_front(idx - 1).ok_or(ParseError::Malformed)?;
let location =
buffer.take_obj_front::<U16>().ok_or(ParseError::Malformed)?.get();
Ok(DomainData::Pointer(Some(data), unwrap_domain_pointer(location)))
}
};
}
// If this is the null terminator, then we're either done iterating,
// or this is a malformed label (if it is the first byte).
if domain_len == 0 {
if idx == 1 {
return Err(ParseError::UnexpectedZeroCharacter);
}
break;
}
if domain_len as usize > MAX_LABEL_SIZE {
return Err(ParseError::LabelTooLong(domain_len.into()));
}
for _ in 0..domain_len {
if *iter.next().ok_or(ParseError::Malformed)? == 0 {
return Err(ParseError::UnexpectedZeroCharacter)?;
}
idx += 1;
}
}
if idx > MAX_DOMAIN_SIZE {
return Err(ParseError::DomainTooLong(idx))?;
}
Ok(DomainData::Domain(buffer.take_front(idx).ok_or(ParseError::Malformed)?))
}
/// Parse the provided record.
///
/// `parent` is used for resolving compressed names as specified by [RFC
/// 1035 Section 4.1.4]. If `parent` is None, a reference to previous data
/// will be treated as an error.
///
/// [RFC 1035 Section 4.1.4]: https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.4
pub fn parse<BV: BufferView<B>>(
buffer: &mut BV,
parent: Option<B>,
) -> Result<Self, ParseError> {
let mut fragments = Vec::<B>::new();
let mut pointer_set = HashSet::<u16>::new();
let mut result = Domain::parse_domain_helper(buffer)?;
loop {
match result {
DomainData::Domain(data) => {
fragments.push(data);
return Ok(Self { fragments });
}
DomainData::Pointer(data, pointer) => {
if let Some(d) = data {
fragments.push(d);
}
if pointer_set.contains(&pointer) {
return Err(ParseError::PointerCycle);
}
pointer_set.insert(pointer);
let mut bv = parent
.and_then(|parent| {
let mut bv = BufferViewWrapper(parent.clone());
bv.take_front(pointer.into()).map(|_: B| bv)
})
.ok_or(ParseError::BadPointerIndex(pointer))?;
result = Domain::parse_domain_helper(&mut bv)?;
}
}
}
}
}
/// Implementation of PartialEq to make it possible to compare a parsed domain
/// with the initial string that was used to construct it.
impl<B: ByteSlice + Copy> PartialEq<&str> for Domain<B> {
fn eq(&self, other: &&str) -> bool {
self.partial_eq_helper_str(other).or_else::<bool, _>(|_| Ok(false)).unwrap()
}
}
impl<B: ByteSlice + Copy> Display for Domain<B> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl<B: ByteSlice + Copy> Debug for Domain<B> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut iter = self.fragments.iter();
let data = iter.next().unwrap();
Domain::fmt_byte_slice(f, data)?;
loop {
if let Some(next) = iter.next() {
f.write_str(".")?;
Domain::fmt_byte_slice(f, next)?;
} else {
return Ok(());
}
}
}
}
/// An mDNS compliant domain builder. Does not support message compression.
#[derive(Clone, Debug, PartialEq)]
pub struct DomainBuilder {
// TODO(awdavies): This can probably use the Buf struct instead.
data: Vec<u8>,
}
impl DomainBuilder {
/// Attempts to construct a domain from a string formatted according to DNS
/// standards.
///
/// Example usage:
/// ```rust
/// let domain = DomainBuilder::from_str("_fuchsia._udp.local")?;
/// ```
///
/// # Errors
///
/// If the domain you supply is larger than `MAX_DOMAIN_SIZE` this will
/// return an error. It is also an error if any individual label
/// (the section of string between dots) is longer than 63 bytes.
///
/// Currently, terminating a string with a dot is not supported.
pub fn from_str(domain: &str) -> Result<Self, ParseError> {
let mut data = Vec::<u8>::with_capacity(MAX_DOMAIN_SIZE);
let mut domain_iter = domain.as_bytes().as_ref().iter();
loop {
data.push(0);
// When copying is complete there will be one extra byte on the
// beginning and end of the string, so the last_len_idx will be
// equal to the total number of characters in the domain string plus
// one.
let last_len_idx = data.len() - 1;
if last_len_idx == domain.len() + 1 {
break;
}
let mut str_len = 0u8;
loop {
match domain_iter.next() {
// At end of string or a '.' symbol.
Some(46) | None => {
if str_len > MAX_LABEL_SIZE as u8 {
return Err(ParseError::Malformed);
}
data[last_len_idx] = str_len;
break;
}
Some(&c) => {
data.push(c);
str_len += 1;
}
}
}
}
if data.len() == 0 || data.len() > MAX_DOMAIN_SIZE {
return Err(ParseError::Malformed);
}
Ok(Self { data })
}
}
impl EmbeddedPacketBuilder for DomainBuilder {
fn bytes_len(&self) -> usize {
self.data.len()
}
fn serialize<B: ByteSliceMut, BV: BufferViewMut<B>>(&self, bv: &mut BV) {
bv.take_front(self.data.len()).unwrap().copy_from_slice(self.data.as_slice());
}
}
#[cfg(test)]
mod tests {
use super::*;
use packet::{ParseBuffer, Serializer};
use std::fmt::Write;
trait EmbeddedPacketBuilderTestExt: EmbeddedPacketBuilder {
/// Convenience method for testing.
fn serialize_to_buf(&self, mut buf: &mut [u8]) {
let mut bv = &mut buf;
self.serialize(&mut bv);
}
}
impl<B: EmbeddedPacketBuilder> EmbeddedPacketBuilderTestExt for B {}
struct DomainParseTest {
packet: Vec<u8>,
parsing_offset: usize,
expected_result: &'static str,
}
// Some standard-looking domains gathered from the real world.
const DOMAIN_STRING: &str = "_fuchsia._udp.local";
const NODENAME_DOMAIN_STRING: &str = "thumb-set-human-shred._fuchsia._udp.local";
const DOMAIN_BYTES: [u8; 21] = [
0x08, 0x5f, 0x66, 0x75, 0x63, 0x68, 0x73, 0x69, 0x61, 0x04, 0x5f, 0x75, 0x64, 0x70, 0x05,
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00,
];
fn make_buf(size: usize) -> Vec<u8> {
let mut buf = Vec::<u8>::with_capacity(size);
for _ in 0..size {
buf.push(0);
}
return buf;
}
#[test]
fn test_embedded_packet_builder_bytes() {
assert_eq!(
&[3, 'f' as u8, 'o' as u8, 'o' as u8, 0][..],
DomainBuilder::from_str("foo").unwrap().bytes()
);
}
#[test]
fn test_parse_type() {
const TYPES: [Type; 5] = [Type::A, Type::Aaaa, Type::Ptr, Type::Srv, Type::Txt];
for t in TYPES.iter() {
match Type::try_from(u16::from(*t)) {
Ok(parsed_type) => assert_eq!(*t, parsed_type),
Err(e) => panic!("parse error {:?}", e),
}
}
}
#[test]
fn test_parse_class() {
const CLASSES: [Class; 2] = [Class::In, Class::Any];
for c in CLASSES.iter() {
match Class::try_from(u16::from(*c)) {
Ok(parsed_class) => assert_eq!(*c, parsed_class),
Err(e) => panic!("parse error {:?}", e),
}
}
}
#[test]
fn test_domain_parse() {
let mut bv = BufferViewWrapper(&DOMAIN_BYTES[..]);
let _ = Domain::parse(&mut bv, None).expect("Failed to parse");
}
#[test]
fn test_domain_roundtrip() {
for example in [DOMAIN_STRING, NODENAME_DOMAIN_STRING] {
let domain = DomainBuilder::from_str(example).unwrap();
let mut buf = make_buf(domain.bytes_len());
domain.serialize_to_buf(buf.as_mut_slice());
let mut bv = BufferViewWrapper(buf.as_slice());
let parsed = Domain::parse(&mut bv, None).unwrap();
assert_eq!(example, format!("{}", parsed));
}
}
#[test]
fn test_ipv4_parse() {
const ADDR: [u8; IPV4_SIZE] = [192, 168, 0, 2];
let a = RData::<&[u8]>::A(A::read_from(&ADDR[..]).unwrap());
assert_eq!(a.ip_addr(), Some(IpAddr::from(ADDR)));
}
#[test]
fn test_ipv6_parse() {
const ADDR: [u8; IPV6_SIZE] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
let aaaa = RData::<&[u8]>::Aaaa(Aaaa::read_from(&ADDR[..]).unwrap());
assert_eq!(aaaa.ip_addr(), Some(IpAddr::from(ADDR)));
}
#[test]
fn test_domain_build_and_parse() {
const BAD_DOMAIN_SHORT: &'static str = "_fuchsia._udp.loca";
const BAD_DOMAIN_LONG: &'static str = "_fuchsia._udp.local.whatever.toooooolong";
let domain = DomainBuilder::from_str(DOMAIN_STRING).unwrap();
let mut buf = make_buf(domain.bytes_len());
domain.serialize_to_buf(buf.as_mut_slice());
let mut bv = BufferViewWrapper(buf.as_slice());
let parsed = Domain::parse(&mut bv, None).unwrap();
let mut s = String::new();
write!(&mut s, "{}", parsed).unwrap();
assert_eq!(s, DOMAIN_STRING);
assert_eq!(parsed, DOMAIN_STRING);
assert_ne!(parsed, BAD_DOMAIN_SHORT);
assert_ne!(parsed, BAD_DOMAIN_LONG);
}
#[test]
fn test_message_build_and_parse_one_question_one_record() {
let domain = DomainBuilder::from_str(DOMAIN_STRING).unwrap();
let nodename = DomainBuilder::from_str(NODENAME_DOMAIN_STRING).unwrap();
let question = QuestionBuilder::new(domain, Type::Aaaa, Class::In, true);
let record = RecordBuilder::new(
nodename,
Type::Ptr,
Class::Any,
true,
4500,
&[0x03, 'f' as u8, 'o' as u8, 'o' as u8, 0],
);
let mut message = MessageBuilder::new(0, true);
message.add_question(question);
message.add_additional(record);
let mut msg_bytes = message
.into_serializer()
.serialize_vec_outer()
.unwrap_or_else(|_| panic!("Failed to serialize"));
let parsed = msg_bytes.parse::<Message<_>>().expect("Failed to parse!");
// TODO(awdavies): These checks can probably be abstracted a bit.
let q = &parsed.questions[0];
assert_eq!(q.domain, DOMAIN_STRING);
assert_eq!(q.qtype, Type::Aaaa);
assert_eq!(q.class, Class::In);
assert_eq!(q.unicast, true);
assert_eq!(parsed.header.is_query(), true);
assert_eq!(parsed.questions.len(), 1);
assert_eq!(parsed.answers.len(), 0);
assert_eq!(parsed.authority.len(), 0);
assert_eq!(parsed.additional.len(), 1);
let additional = &parsed.additional[0];
assert_eq!(additional.domain, NODENAME_DOMAIN_STRING);
assert_eq!(additional.ttl, 4500);
assert_eq!(additional.flush, true);
assert_eq!(additional.rtype, Type::Ptr);
assert_eq!(additional.class, Class::Any);
assert_eq!(additional.rdata.domain().unwrap(), &"foo");
}
#[test]
fn test_question_build_and_parse() {
let domain = DomainBuilder::from_str(DOMAIN_STRING).unwrap();
let unicast = false;
let qtype = Type::Aaaa;
let class = Class::In;
let question = QuestionBuilder::new(domain, qtype, class, unicast);
let mut buf = make_buf(question.bytes_len());
question.serialize_to_buf(buf.as_mut_slice());
let mut bv = BufferViewWrapper(buf.as_ref());
let parsed = Question::parse(&mut bv, None).unwrap();
assert_eq!(parsed.unicast, unicast);
assert_eq!(parsed.qtype, qtype);
assert_eq!(parsed.class, class);
}
#[test]
fn test_record_build_and_parse() {
for r in [
RecordBuilder {
rdata: &[127, 0, 0, 1],
domain: DomainBuilder::from_str(DOMAIN_STRING).unwrap(),
ttl: 3500,
rtype: Type::A,
class: Class::In,
flush: true,
},
RecordBuilder {
rdata: &[
0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0x8e, 0xae, 0x4c, 0xff, 0xfe, 0xe9, 0xc9, 0xd3,
],
domain: DomainBuilder::from_str(DOMAIN_STRING).unwrap(),
ttl: 1,
rtype: Type::Aaaa,
class: Class::In,
flush: false,
},
RecordBuilder {
rdata: &[1, 2, 3, 4, 5, 6, 0x3, 'f' as u8, 'o' as u8, 'o' as u8, 0],
domain: DomainBuilder::from_str(DOMAIN_STRING).unwrap(),
ttl: 5000,
rtype: Type::Srv,
class: Class::In,
flush: true,
},
RecordBuilder {
rdata: &[0x04, 'q' as u8, 'u' as u8, 'u' as u8, 'x' as u8, 0x00],
domain: DomainBuilder::from_str(DOMAIN_STRING).unwrap(),
ttl: 10,
rtype: Type::Ptr,
class: Class::In,
flush: false,
},
]
.iter()
{
let mut buf = make_buf(r.bytes_len());
r.serialize_to_buf(buf.as_mut_slice());
let mut bv = BufferViewWrapper(buf.as_ref());
let parsed = Record::parse(&mut bv, None).unwrap();
assert_eq!(parsed.domain, DOMAIN_STRING);
assert_eq!(r.rtype, parsed.rtype);
assert_eq!(r.ttl, parsed.ttl);
assert_eq!(r.class, parsed.class);
assert_eq!(r.flush, parsed.flush);
match parsed.rtype {
Type::Srv => {
assert_eq!(parsed.rdata.domain().unwrap(), &"foo");
let srv = parsed.rdata.srv().unwrap();
assert_eq!(srv.domain, "foo");
assert_eq!(srv.priority, 0x0102);
assert_eq!(srv.weight, 0x0304);
assert_eq!(srv.port, 0x0506);
}
Type::A => {
if let IpAddr::V4(addr) = parsed.rdata.ip_addr().unwrap() {
assert_eq!(&addr.octets(), &r.rdata);
} else {
panic!("expected IpAddr::V4");
}
}
Type::Aaaa => {
if let IpAddr::V6(addr) = parsed.rdata.ip_addr().unwrap() {
assert_eq!(&addr.octets(), &r.rdata);
} else {
panic!("expected IpAddr::V6");
}
}
Type::Ptr => assert_eq!(parsed.rdata.domain().unwrap(), &"quux"),
_ => (),
}
}
}
#[test]
fn test_srv_record_bad_sizing() {
for r in [
// RData with extra after null terminator.
RecordBuilder {
rdata: &[1, 2, 3, 4, 5, 6, 0x3, 'f' as u8, 'o' as u8, 'o' as u8, 0, 1, 2, 3, 4],
domain: DomainBuilder::from_str(DOMAIN_STRING).unwrap(),
class: Class::Any,
flush: true,
ttl: 2,
rtype: Type::Srv,
},
// One byte too short.
RecordBuilder {
rdata: &[1, 2, 3, 4, 5],
domain: DomainBuilder::from_str(DOMAIN_STRING).unwrap(),
class: Class::Any,
flush: true,
ttl: 2,
rtype: Type::Srv,
},
// Empty RData.
RecordBuilder {
rdata: &[],
domain: DomainBuilder::from_str(DOMAIN_STRING).unwrap(),
class: Class::Any,
flush: true,
ttl: 1,
rtype: Type::Srv,
},
// Null domain.
RecordBuilder {
rdata: &[1, 2, 3, 4, 5, 6, 0],
domain: DomainBuilder::from_str(DOMAIN_STRING).unwrap(),
class: Class::Any,
flush: true,
ttl: 1,
rtype: Type::Srv,
},
]
.iter()
{
let mut buf = make_buf(r.bytes_len());
r.serialize_to_buf(buf.as_mut_slice());
let mut bv = BufferViewWrapper(buf.as_ref());
// Will panic if there is not an error.
let _ = Record::parse(&mut bv, None).unwrap_err();
}
}
#[test]
fn test_domain_parse_no_trailing() {
let mut bv = BufferViewWrapper(&DOMAIN_BYTES[..DOMAIN_BYTES.len() - 1]);
assert_eq!(Domain::parse(&mut bv, None).unwrap_err(), ParseError::Malformed);
}
#[test]
fn test_domain_parse_middle() {
let packet = &mut DOMAIN_BYTES.to_vec();
packet[3] = 0;
let mut bv = BufferViewWrapper(&packet[..]);
assert_eq!(Domain::parse(&mut bv, None).unwrap_err(), ParseError::UnexpectedZeroCharacter);
}
#[test]
fn test_domain_parse_label_too_long() {
let packet = &mut DOMAIN_BYTES.to_vec();
let bad_len = 65u8;
packet[0] = bad_len;
let mut bv = BufferViewWrapper(&packet[..]);
assert_eq!(
Domain::parse(&mut bv, None).unwrap_err(),
ParseError::LabelTooLong(bad_len.into())
);
}
#[test]
fn test_domain_parse_domain_too_long() {
const LABELS: usize = 10;
const SIZE: usize = MAX_LABEL_SIZE * LABELS;
let mut packet = Vec::<u8>::with_capacity(SIZE);
for _ in 0..LABELS {
packet.push(MAX_LABEL_SIZE as u8);
for _ in 0..MAX_LABEL_SIZE {
packet.push('f' as u8);
}
}
packet.push(0);
let mut bv = BufferViewWrapper(packet.as_ref());
assert_eq!(Domain::parse(&mut bv, None).unwrap_err(), ParseError::DomainTooLong(641));
}
#[test]
fn test_domain_parse_empty_message() {
const PACKET: [u8; 1] = [0];
let mut bv = BufferViewWrapper(&PACKET[..]);
assert_eq!(Domain::parse(&mut bv, None).unwrap_err(), ParseError::UnexpectedZeroCharacter);
}
#[test]
fn test_domain_parse_short_malformed() {
const PACKET: [u8; 2] = [1, 0];
let mut bv = BufferViewWrapper(&PACKET[..]);
assert_eq!(Domain::parse(&mut bv, None).unwrap_err(), ParseError::UnexpectedZeroCharacter);
}
#[test]
fn test_domain_bad_pointer_index() {
let packet: Vec<u8> = vec![0u8, 0x01, 'y' as u8, 0xc0, 0x09];
let slice: &[u8] = packet.as_ref();
let mut bv = BufferViewWrapper(slice);
bv.take_front(3).unwrap();
assert_eq!(
Domain::parse(&mut bv, Some(&slice)).unwrap_err(),
ParseError::BadPointerIndex(0x09)
);
}
#[test]
fn test_domain_pointer_with_no_parent() {
let packet: Vec<u8> = vec![
0u8, 0x03, 'f' as u8, 'o' as u8, 'o' as u8, 0x03, 'b' as u8, 'a' as u8, 'r' as u8,
0x00, 0x03, 'b' as u8, 'a' as u8, 'z' as u8, 0x03, 'b' as u8, 'o' as u8, 'i' as u8,
0xc0, 0x01,
];
let slice: &[u8] = packet.as_ref();
{
let mut bv = BufferViewWrapper(slice);
bv.take_front(10).unwrap();
// Prove that with parent this is valid.
let _: Domain<_> = Domain::parse(&mut bv, Some(&slice)).expect("should succeed");
}
{
// Without parent the indirection is rejected.
let mut bv = BufferViewWrapper(slice);
bv.take_front(10).unwrap();
assert_eq!(Domain::parse(&mut bv, None), Err(ParseError::BadPointerIndex(0x01)));
}
}
#[test]
fn test_domain_pointer_cycles() {
for packet in [vec![0xc0, 0x00], vec![0x02, 0x02, 0x01, 0xc0, 0x05, 0xc0, 0x03]].iter() {
let slice: &[u8] = packet.as_ref();
let mut bv = BufferViewWrapper(slice);
assert_eq!(Domain::parse(&mut bv, Some(slice)).unwrap_err(), ParseError::PointerCycle);
}
}
#[test]
fn test_domain_parse_fragmented_domains() {
for test in [
DomainParseTest {
packet: vec![
0u8, 0x03, 'f' as u8, 'o' as u8, 'o' as u8, 0x03, 'b' as u8, 'a' as u8,
'r' as u8, 0x00, 0xc0, 0x01,
],
expected_result: "foo.bar",
parsing_offset: 10,
},
DomainParseTest {
packet: vec![
0u8, 0x03, 'f' as u8, 'o' as u8, 'o' as u8, 0x03, 'b' as u8, 'a' as u8,
'r' as u8, 0x00, 0x03, 'b' as u8, 'a' as u8, 'z' as u8, 0x03, 'b' as u8,
'o' as u8, 'i' as u8, 0xc0, 0x01,
],
expected_result: "baz.boi.foo.bar",
parsing_offset: 10,
},
DomainParseTest {
packet: vec![
2u8, 3u8, 0u8, 0x03, 'f' as u8, 'o' as u8, 'o' as u8, 0x03, 'b' as u8,
'a' as u8, 'r' as u8, 0x00, 0x03, 'b' as u8, 'a' as u8, 'z' as u8, 0x03,
'b' as u8, 'o' as u8, 'i' as u8, 0x07, '_' as u8, 'm' as u8, 'u' as u8,
'm' as u8, 'b' as u8, 'l' as u8, 'e' as u8, 0xc0, 0x03,
],
expected_result: "baz.boi._mumble.foo.bar",
parsing_offset: 12,
},
DomainParseTest {
packet: vec![
2u8, 3u8, 0u8, 0x03, 'f' as u8, 'o' as u8, 'o' as u8, 0x03, 'b' as u8,
'a' as u8, 'r' as u8, 0xc0, 0x1f, 0x03, 'b' as u8, 'a' as u8, 'z' as u8, 0x03,
'b' as u8, 'o' as u8, 'i' as u8, 0x07, '_' as u8, 'm' as u8, 'u' as u8,
'm' as u8, 'b' as u8, 'l' as u8, 'e' as u8, 0xc0, 0x03, 0x04, 'q' as u8,
'u' as u8, 'u' as u8, 'x' as u8, 0x00,
],
expected_result: "baz.boi._mumble.foo.bar.quux",
parsing_offset: 13,
},
]
.iter()
{
let slice: &[u8] = test.packet.as_ref();
let mut bv = BufferViewWrapper(slice);
bv.take_front(test.parsing_offset).unwrap();
let parsed = Domain::parse(&mut bv, Some(&slice)).unwrap();
let mut s = String::new();
write!(&mut s, "{}", parsed).unwrap();
assert_eq!(s, test.expected_result);
assert_eq!(parsed, test.expected_result);
}
}
#[test]
fn test_real_world_mdns_packet_response() {
// This is a real world mDNS packet from a Fuchsia device. These bytes
// were copied from wireshark (and the fields were extracted from there
// as well).
//
// The structure is as follows:
// Header:
// -- Flags 0x8400
// -- Question count 0
// -- Answer count 1
// -- Authority count 0
// -- Additional count 4
//
// Answer 1:
// -- Type: PTR
// -- Domain: '_fuchsia._udp.local'
// -- Class: IN
// -- Flush: False
// -- TTL: 4500
// -- Data Length: 24
// -- Data (uncompressed): thumb-set-human-shred._fuchsia._udp.local
//
// Additional record 1:
// -- Type: SRV
// -- Domain: thumb-set-human-shred._fuchsia._udp.local
// -- Class: IN
// -- Flush: True
// -- TTL: 120
// -- Data Length: 30
// -- Priority: 0
// -- Weight: 0
// -- Port: 5353
// -- Target: thumb-set-human-shred.local
//
// Additional record 2:
// -- Type: TXT
// -- Domain: thumb-set-human-shred._fuchsia._udp.local
// -- Class: IN
// -- Flush: True
// -- TTL: 4500
// -- Data Length: 0
// -- Data: '\0'
//
// Additional record 3:
// -- Type: A
// -- Domain: thumb-set-human-shred.local
// -- Class: IN
// -- Flush: True
// -- TTL: 120
// -- Data Length: 4
// -- Data: '172.16.243.38'
//
// Additional record 4:
// -- Type: AAAA
// -- Domain: thumb-set-human-shred.local
// -- Class: IN
// -- Flush: True
// -- TTL: 120
// -- Data length: 16
// -- Data: 'fe80::8eae:4cff:fee9:c9d3'
let packet: Vec<u8> = vec![
0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x08, 0x5f,
0x66, 0x75, 0x63, 0x68, 0x73, 0x69, 0x61, 0x04, 0x5f, 0x75, 0x64, 0x70, 0x05, 0x6c,
0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x00, 0x01, 0x00, 0x00, 0x11, 0x94, 0x00,
0x18, 0x15, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x2d, 0x73, 0x65, 0x74, 0x2d, 0x68, 0x75,
0x6d, 0x61, 0x6e, 0x2d, 0x73, 0x68, 0x72, 0x65, 0x64, 0xc0, 0x0c, 0xc0, 0x2b, 0x00,
0x21, 0x80, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x14,
0xe9, 0x15, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x2d, 0x73, 0x65, 0x74, 0x2d, 0x68, 0x75,
0x6d, 0x61, 0x6e, 0x2d, 0x73, 0x68, 0x72, 0x65, 0x64, 0xc0, 0x1a, 0xc0, 0x2b, 0x00,
0x10, 0x80, 0x01, 0x00, 0x00, 0x11, 0x94, 0x00, 0x01, 0x00, 0xc0, 0x55, 0x00, 0x01,
0x80, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x04, 0xac, 0x10, 0xf3, 0x26, 0xc0, 0x55,
0x00, 0x1c, 0x80, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x10, 0xfe, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x8e, 0xae, 0x4c, 0xff, 0xfe, 0xe9, 0xc9, 0xd3,
];
let mut packet_slice = packet.as_slice();
let parsed = packet_slice.parse::<Message<_>>().expect("Failed to parse!");
assert!(parsed.header.is_response());
assert_eq!(parsed.header.question_count(), 0);
assert_eq!(parsed.header.answer_count(), 1);
assert_eq!(parsed.header.authority_count(), 0);
assert_eq!(parsed.header.additional_count(), 4);
let answer = &parsed.answers[0];
assert_eq!(answer.rtype, Type::Ptr);
assert_eq!(answer.domain, "_fuchsia._udp.local");
assert_eq!(answer.class, Class::In);
assert_eq!(answer.flush, false);
assert_eq!(answer.ttl, 4500);
assert_eq!(answer.rdata.domain().unwrap(), &"thumb-set-human-shred._fuchsia._udp.local");
let srv = &parsed.additional[0];
assert_eq!(srv.rtype, Type::Srv);
assert_eq!(srv.domain, "thumb-set-human-shred._fuchsia._udp.local");
assert_eq!(srv.class, Class::In);
assert_eq!(srv.flush, true);
assert_eq!(srv.ttl, 120);
let srv_rdata = srv.rdata.srv().unwrap();
assert_eq!(srv_rdata.weight, 0);
assert_eq!(srv_rdata.priority, 0);
assert_eq!(srv_rdata.port, 5353);
assert_eq!(srv_rdata.domain, "thumb-set-human-shred.local");
let txt = &parsed.additional[1];
assert_eq!(txt.rtype, Type::Txt);
assert_eq!(txt.domain, "thumb-set-human-shred._fuchsia._udp.local");
assert_eq!(txt.class, Class::In);
assert_eq!(txt.flush, true);
assert_eq!(txt.ttl, 4500);
assert_eq!(txt.rdata.bytes().unwrap().len(), 1);
let a = &parsed.additional[2];
assert_eq!(a.rtype, Type::A);
assert_eq!(a.domain, "thumb-set-human-shred.local");
assert_eq!(a.class, Class::In);
assert_eq!(a.ttl, 120);
if let IpAddr::V4(addr) = a.rdata.ip_addr().unwrap() {
assert_eq!(&addr.octets()[..], &[172, 16, 243, 38]);
} else {
panic!("expected IpAddr::V4");
}
let aaaa = &parsed.additional[3];
assert_eq!(aaaa.rtype, Type::Aaaa);
assert_eq!(aaaa.domain, "thumb-set-human-shred.local");
assert_eq!(aaaa.class, Class::In);
assert_eq!(aaaa.ttl, 120);
if let IpAddr::V6(addr) = aaaa.rdata.ip_addr().unwrap() {
assert_eq!(
&addr.octets()[..],
&[
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, 0xae, 0x4c, 0xff, 0xfe,
0xe9, 0xc9, 0xd3
]
);
} else {
panic!("expected IpAddr::V6");
}
}
#[test]
fn test_real_world_mdns_packet_question() {
let packet: Vec<u8> = vec![
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x5f,
0x66, 0x75, 0x63, 0x68, 0x73, 0x69, 0x61, 0x04, 0x5f, 0x75, 0x64, 0x70, 0x05, 0x6c,
0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x00, 0x01,
];
let mut packet_slice = packet.as_slice();
let parsed = packet_slice.parse::<Message<_>>().expect("Failed to parse!");
assert!(parsed.header.is_query());
assert_eq!(parsed.header.question_count(), 1);
assert_eq!(parsed.header.answer_count(), 0);
assert_eq!(parsed.header.authority_count(), 0);
assert_eq!(parsed.header.additional_count(), 0);
let q = &parsed.questions[0];
assert_eq!(q.domain, "_fuchsia._udp.local");
assert_eq!(q.qtype, Type::Ptr);
assert_eq!(q.class, Class::In);
assert_eq!(q.unicast, false);
}
}