use mdns::protocol::{Domain, ParseError as MdnsParseError};
use net_types::ip::{IpAddress as _, Ipv6Addr, PrefixTooLongError, Subnet};
use num_derive::FromPrimitive;
use packet::records::{
ParsedRecord, RecordBuilder, RecordParseResult, RecordSequenceBuilder, Records, RecordsImpl,
RecordsImplLayout,
};
use packet::{BufferView, BufferViewMut, InnerPacketBuilder, ParsablePacket, ParseMetadata};
use std::convert::Infallible as Never;
use std::slice::Iter;
use std::{mem, str};
use thiserror::Error;
use uuid::Uuid;
use zerocopy::byteorder::network_endian::{U16, U32};
use zerocopy::{
FromBytes, Immutable, IntoByteSlice, IntoBytes, KnownLayout, Ref, SplitByteSlice, Unaligned,
};
#[allow(missing_docs)]
#[derive(Debug, Error, PartialEq)]
pub enum ParseError {
#[error("invalid message type: {}", _0)]
InvalidMessageType(u8),
#[error("invalid option code: {}", _0)]
InvalidOpCode(u16),
#[error("invalid option length {} for option code {:?}", _1, _0)]
InvalidOpLen(OptionCode, usize),
#[error("invalid status code: {}", _0)]
InvalidStatusCode(u16),
#[error("invalid error status code: {}", _0)]
InvalidErrorStatusCode(u16),
#[error("buffer exhausted while more bytes are expected")]
BufferExhausted,
#[error("failed to parse domain {:?}", _0)]
DomainParseError(MdnsParseError),
#[error("failed to parse UTF8 string: {:?}", _0)]
Utf8Error(#[from] str::Utf8Error),
}
impl From<Never> for ParseError {
fn from(err: Never) -> ParseError {
match err {}
}
}
#[allow(missing_docs)]
#[derive(Debug, PartialEq, FromPrimitive, IntoBytes, Immutable, Copy, Clone)]
#[repr(u8)]
pub enum MessageType {
Solicit = 1,
Advertise = 2,
Request = 3,
Confirm = 4,
Renew = 5,
Rebind = 6,
Reply = 7,
Release = 8,
Decline = 9,
Reconfigure = 10,
InformationRequest = 11,
RelayForw = 12,
RelayRepl = 13,
}
impl From<MessageType> for u8 {
fn from(t: MessageType) -> u8 {
t as u8
}
}
impl TryFrom<u8> for MessageType {
type Error = ParseError;
fn try_from(b: u8) -> Result<MessageType, ParseError> {
<Self as num_traits::FromPrimitive>::from_u8(b).ok_or(ParseError::InvalidMessageType(b))
}
}
#[allow(missing_docs)]
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum StatusCode {
Success,
Failure(ErrorStatusCode),
}
impl From<StatusCode> for u16 {
fn from(t: StatusCode) -> u16 {
match t {
StatusCode::Success => 0,
StatusCode::Failure(error_status) => error_status.into(),
}
}
}
impl TryFrom<u16> for StatusCode {
type Error = ParseError;
fn try_from(b: u16) -> Result<StatusCode, ParseError> {
match b {
0 => Ok(Self::Success),
b => ErrorStatusCode::try_from(b).map(Self::Failure).map_err(|e| match e {
ParseError::InvalidErrorStatusCode(b) => ParseError::InvalidStatusCode(b),
e => unreachable!("unexpected error parsing u16 as ErrorStatusCode: {}", e),
}),
}
}
}
impl StatusCode {
pub fn into_result(self) -> Result<(), ErrorStatusCode> {
match self {
Self::Success => Ok(()),
Self::Failure(error_status) => Err(error_status),
}
}
}
#[allow(missing_docs)]
#[derive(thiserror::Error, Debug, PartialEq, FromPrimitive, IntoBytes, Immutable, Copy, Clone)]
#[repr(u16)]
pub enum ErrorStatusCode {
#[error("unspecified failure")]
UnspecFail = 1,
#[error("no addresses available")]
NoAddrsAvail = 2,
#[error("no binding")]
NoBinding = 3,
#[error("not on-link")]
NotOnLink = 4,
#[error("use multicast")]
UseMulticast = 5,
#[error("no prefixes available")]
NoPrefixAvail = 6,
}
impl From<ErrorStatusCode> for u16 {
fn from(code: ErrorStatusCode) -> u16 {
code as u16
}
}
impl From<ErrorStatusCode> for StatusCode {
fn from(code: ErrorStatusCode) -> Self {
Self::Failure(code)
}
}
impl TryFrom<u16> for ErrorStatusCode {
type Error = ParseError;
fn try_from(b: u16) -> Result<Self, ParseError> {
<Self as num_traits::FromPrimitive>::from_u16(b)
.ok_or(ParseError::InvalidErrorStatusCode(b))
}
}
#[allow(missing_docs)]
#[derive(Debug, PartialEq, FromPrimitive, Clone, Copy)]
#[repr(u8)]
pub enum OptionCode {
ClientId = 1,
ServerId = 2,
Iana = 3,
IaAddr = 5,
Oro = 6,
Preference = 7,
ElapsedTime = 8,
StatusCode = 13,
DnsServers = 23,
DomainList = 24,
IaPd = 25,
IaPrefix = 26,
InformationRefreshTime = 32,
SolMaxRt = 82,
}
impl From<OptionCode> for u16 {
fn from(code: OptionCode) -> u16 {
code as u16
}
}
impl TryFrom<u16> for OptionCode {
type Error = ParseError;
fn try_from(n: u16) -> Result<OptionCode, ParseError> {
<Self as num_traits::FromPrimitive>::from_u16(n).ok_or(ParseError::InvalidOpCode(n))
}
}
#[allow(missing_docs)]
#[derive(Debug, PartialEq)]
pub enum ParsedDhcpOption<'a> {
ClientId(&'a Duid),
ServerId(&'a Duid),
Iana(IanaData<&'a [u8]>),
IaAddr(IaAddrData<&'a [u8]>),
Oro(Vec<OptionCode>),
Preference(u8),
ElapsedTime(u16),
StatusCode(U16, &'a str),
IaPd(IaPdData<&'a [u8]>),
IaPrefix(IaPrefixData<&'a [u8]>),
InformationRefreshTime(u32),
SolMaxRt(U32),
DnsServers(Vec<Ipv6Addr>),
DomainList(Vec<checked::Domain>),
}
#[derive(Debug, PartialEq)]
pub struct IanaData<B: SplitByteSlice> {
header: Ref<B, IanaHeader>,
options: Records<B, ParsedDhcpOptionImpl>,
}
mod private {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct NonZeroOrMaxU32(u32);
impl NonZeroOrMaxU32 {
pub const fn new(t: u32) -> Option<NonZeroOrMaxU32> {
if t == 0 || t == u32::MAX {
return None;
}
Some(NonZeroOrMaxU32(t))
}
pub fn get(self) -> u32 {
let NonZeroOrMaxU32(t) = self;
t
}
}
}
pub use private::*;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum NonZeroTimeValue {
Finite(NonZeroOrMaxU32),
Infinity,
}
impl From<NonZeroTimeValue> for TimeValue {
fn from(v: NonZeroTimeValue) -> TimeValue {
TimeValue::NonZero(v)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum TimeValue {
Zero,
NonZero(NonZeroTimeValue),
}
impl TimeValue {
pub const fn new(t: u32) -> TimeValue {
match t {
0 => TimeValue::Zero,
u32::MAX => TimeValue::NonZero(NonZeroTimeValue::Infinity),
t => TimeValue::NonZero(NonZeroTimeValue::Finite(
const_unwrap::const_unwrap_option(NonZeroOrMaxU32::new(t)),
)),
}
}
}
impl<'a, B: SplitByteSlice> IanaData<B> {
fn new(buf: B) -> Result<Self, ParseError> {
let buf_len = buf.len();
let (header, options) =
Ref::from_prefix(buf).map_err(Into::into).map_err(|_: zerocopy::SizeError<_, _>| {
ParseError::InvalidOpLen(OptionCode::Iana, buf_len)
})?;
let options = Records::<B, ParsedDhcpOptionImpl>::parse_with_context(options, ())?;
Ok(IanaData { header, options })
}
pub fn iaid(&self) -> u32 {
self.header.iaid.get()
}
pub fn t1(&self) -> TimeValue {
TimeValue::new(self.header.t1.get())
}
pub fn t2(&self) -> TimeValue {
TimeValue::new(self.header.t2.get())
}
pub fn iter_options(&'a self) -> impl 'a + Iterator<Item = ParsedDhcpOption<'a>> {
self.options.iter()
}
}
#[derive(
KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug, PartialEq, Copy, Clone,
)]
#[repr(C)]
struct IanaHeader {
iaid: U32,
t1: U32,
t2: U32,
}
#[derive(Debug, PartialEq)]
pub struct IaAddrData<B: SplitByteSlice> {
header: Ref<B, IaAddrHeader>,
options: Records<B, ParsedDhcpOptionImpl>,
}
impl<'a, B: SplitByteSlice> IaAddrData<B> {
pub fn new(buf: B) -> Result<Self, ParseError> {
let buf_len = buf.len();
let (header, options) =
Ref::from_prefix(buf).map_err(Into::into).map_err(|_: zerocopy::SizeError<_, _>| {
ParseError::InvalidOpLen(OptionCode::IaAddr, buf_len)
})?;
let options = Records::<B, ParsedDhcpOptionImpl>::parse_with_context(options, ())?;
Ok(IaAddrData { header, options })
}
pub fn addr(&self) -> Ipv6Addr {
self.header.addr
}
pub fn preferred_lifetime(&self) -> TimeValue {
TimeValue::new(self.header.preferred_lifetime.get())
}
pub fn valid_lifetime(&self) -> TimeValue {
TimeValue::new(self.header.valid_lifetime.get())
}
pub fn iter_options(&'a self) -> impl 'a + Iterator<Item = ParsedDhcpOption<'a>> {
self.options.iter()
}
}
#[derive(
KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug, PartialEq, Copy, Clone,
)]
#[repr(C)]
struct IaAddrHeader {
addr: Ipv6Addr,
preferred_lifetime: U32,
valid_lifetime: U32,
}
#[derive(
KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug, PartialEq, Copy, Clone,
)]
#[repr(C)]
struct IaPdHeader {
iaid: U32,
t1: U32,
t2: U32,
}
#[derive(Debug, PartialEq)]
pub struct IaPdData<B: SplitByteSlice> {
header: Ref<B, IaPdHeader>,
options: Records<B, ParsedDhcpOptionImpl>,
}
impl<'a, B: SplitByteSlice> IaPdData<B> {
fn new(buf: B) -> Result<Self, ParseError> {
let buf_len = buf.len();
let (header, options) =
Ref::from_prefix(buf).map_err(Into::into).map_err(|_: zerocopy::SizeError<_, _>| {
ParseError::InvalidOpLen(OptionCode::IaPd, buf_len)
})?;
let options = Records::<B, ParsedDhcpOptionImpl>::parse_with_context(options, ())?;
Ok(IaPdData { header, options })
}
pub fn iaid(&self) -> u32 {
self.header.iaid.get()
}
pub fn t1(&self) -> TimeValue {
TimeValue::new(self.header.t1.get())
}
pub fn t2(&self) -> TimeValue {
TimeValue::new(self.header.t2.get())
}
pub fn iter_options(&'a self) -> impl 'a + Iterator<Item = ParsedDhcpOption<'a>> {
self.options.iter()
}
}
#[derive(
KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug, PartialEq, Copy, Clone,
)]
#[repr(C)]
struct IaPrefixHeader {
preferred_lifetime_secs: U32,
valid_lifetime_secs: U32,
prefix_length: u8,
prefix: Ipv6Addr,
}
#[derive(Debug, PartialEq)]
pub struct IaPrefixData<B: SplitByteSlice> {
header: Ref<B, IaPrefixHeader>,
options: Records<B, ParsedDhcpOptionImpl>,
}
impl<'a, B: SplitByteSlice> IaPrefixData<B> {
pub fn new(buf: B) -> Result<Self, ParseError> {
let buf_len = buf.len();
let (header, options) =
Ref::from_prefix(buf).map_err(Into::into).map_err(|_: zerocopy::SizeError<_, _>| {
ParseError::InvalidOpLen(OptionCode::IaPrefix, buf_len)
})?;
let options = Records::<B, ParsedDhcpOptionImpl>::parse_with_context(options, ())?;
Ok(IaPrefixData { header, options })
}
pub fn prefix(&self) -> Result<Subnet<Ipv6Addr>, PrefixTooLongError> {
Subnet::from_host(self.header.prefix, self.header.prefix_length)
}
pub fn preferred_lifetime(&self) -> TimeValue {
TimeValue::new(self.header.preferred_lifetime_secs.get())
}
pub fn valid_lifetime(&self) -> TimeValue {
TimeValue::new(self.header.valid_lifetime_secs.get())
}
pub fn iter_options(&'a self) -> impl 'a + Iterator<Item = ParsedDhcpOption<'a>> {
self.options.iter()
}
}
mod checked {
use std::str::FromStr;
use mdns::protocol::{DomainBuilder, EmbeddedPacketBuilder};
use packet::BufferViewMut;
use zerocopy::SplitByteSliceMut;
use super::ParseError;
#[derive(Debug, PartialEq)]
pub struct Domain {
domain: String,
builder: DomainBuilder,
}
impl FromStr for Domain {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, ParseError> {
Self::try_from(s.to_string())
}
}
impl TryFrom<String> for Domain {
type Error = ParseError;
fn try_from(domain: String) -> Result<Self, ParseError> {
let builder = DomainBuilder::from_str(&domain).map_err(ParseError::DomainParseError)?;
Ok(Domain { domain, builder })
}
}
impl Domain {
pub(crate) fn bytes_len(&self) -> usize {
self.builder.bytes_len()
}
pub(crate) fn serialize<B: SplitByteSliceMut, BV: BufferViewMut<B>>(&self, bv: &mut BV) {
let () = self.builder.serialize(bv);
}
}
}
macro_rules! option_to_code {
($option:ident, $($option_name:ident::$variant:tt($($v:tt)*)),*) => {
match $option {
$($option_name::$variant($($v)*)=>OptionCode::$variant,)*
}
}
}
impl ParsedDhcpOption<'_> {
pub fn code(&self) -> OptionCode {
option_to_code!(
self,
ParsedDhcpOption::ClientId(_),
ParsedDhcpOption::ServerId(_),
ParsedDhcpOption::Iana(_),
ParsedDhcpOption::IaAddr(_),
ParsedDhcpOption::Oro(_),
ParsedDhcpOption::Preference(_),
ParsedDhcpOption::ElapsedTime(_),
ParsedDhcpOption::StatusCode(_, _),
ParsedDhcpOption::IaPd(_),
ParsedDhcpOption::IaPrefix(_),
ParsedDhcpOption::InformationRefreshTime(_),
ParsedDhcpOption::SolMaxRt(_),
ParsedDhcpOption::DnsServers(_),
ParsedDhcpOption::DomainList(_)
)
}
}
type Duid = [u8];
#[derive(Debug, PartialEq)]
enum ParsedDhcpOptionImpl {}
impl RecordsImplLayout for ParsedDhcpOptionImpl {
type Context = ();
type Error = ParseError;
}
impl RecordsImpl for ParsedDhcpOptionImpl {
type Record<'a> = ParsedDhcpOption<'a>;
fn parse_with_context<'a, BV: BufferView<&'a [u8]>>(
data: &mut BV,
_context: &mut Self::Context,
) -> RecordParseResult<Self::Record<'a>, Self::Error> {
if data.len() == 0 {
return Ok(ParsedRecord::Done);
}
let opt_code = data.take_obj_front::<U16>().ok_or(ParseError::BufferExhausted)?;
let opt_len = data.take_obj_front::<U16>().ok_or(ParseError::BufferExhausted)?;
let opt_len = usize::from(opt_len.get());
let mut opt_val = data.take_front(opt_len).ok_or(ParseError::BufferExhausted)?;
let opt_code = match OptionCode::try_from(opt_code.get()) {
Ok(opt_code) => opt_code,
Err(ParseError::InvalidOpCode(_)) => {
return Ok(ParsedRecord::Skipped);
}
Err(e) => unreachable!("unexpected error from op code conversion: {}", e),
};
let opt = match opt_code {
OptionCode::ClientId => Ok(ParsedDhcpOption::ClientId(opt_val)),
OptionCode::ServerId => Ok(ParsedDhcpOption::ServerId(opt_val)),
OptionCode::Iana => IanaData::new(opt_val).map(ParsedDhcpOption::Iana),
OptionCode::IaAddr => IaAddrData::new(opt_val).map(ParsedDhcpOption::IaAddr),
OptionCode::Oro => {
let options = opt_val
.chunks(2)
.map(|opt| {
let opt: [u8; 2] = opt.try_into().map_err(
|std::array::TryFromSliceError { .. }| {
ParseError::InvalidOpLen(OptionCode::Oro, opt_val.len())
},
)?;
OptionCode::try_from(u16::from_be_bytes(opt))
})
.collect::<Result<_, ParseError>>()?;
Ok(ParsedDhcpOption::Oro(options))
}
OptionCode::Preference => match opt_val {
&[b] => Ok(ParsedDhcpOption::Preference(b)),
opt_val => Err(ParseError::InvalidOpLen(OptionCode::Preference, opt_val.len())),
},
OptionCode::ElapsedTime => match opt_val {
&[b0, b1] => Ok(ParsedDhcpOption::ElapsedTime(u16::from_be_bytes([b0, b1]))),
opt_val => Err(ParseError::InvalidOpLen(OptionCode::ElapsedTime, opt_val.len())),
},
OptionCode::StatusCode => {
let mut opt_val = &mut opt_val;
let code = (&mut opt_val).take_obj_front::<U16>().ok_or_else(|| {
ParseError::InvalidOpLen(OptionCode::StatusCode, opt_val.len())
})?;
let message = str::from_utf8(opt_val)?;
Ok(ParsedDhcpOption::StatusCode(*code, message))
}
OptionCode::IaPd => IaPdData::new(opt_val).map(ParsedDhcpOption::IaPd),
OptionCode::IaPrefix => IaPrefixData::new(opt_val).map(ParsedDhcpOption::IaPrefix),
OptionCode::InformationRefreshTime => match opt_val {
&[b0, b1, b2, b3] => {
Ok(ParsedDhcpOption::InformationRefreshTime(u32::from_be_bytes([
b0, b1, b2, b3,
])))
}
opt_val => {
Err(ParseError::InvalidOpLen(OptionCode::InformationRefreshTime, opt_val.len()))
}
},
OptionCode::SolMaxRt => {
let mut opt_val = &mut opt_val;
let sol_max_rt = (&mut opt_val)
.take_obj_front::<U32>()
.ok_or_else(|| ParseError::InvalidOpLen(OptionCode::SolMaxRt, opt_val.len()))?;
Ok(ParsedDhcpOption::SolMaxRt(*sol_max_rt))
}
OptionCode::DnsServers => {
let addresses = opt_val
.chunks(16)
.map(|opt| {
let opt: [u8; 16] = opt.try_into().map_err(
|std::array::TryFromSliceError { .. }| {
ParseError::InvalidOpLen(OptionCode::DnsServers, opt_val.len())
},
)?;
Ok(Ipv6Addr::from(opt))
})
.collect::<Result<_, ParseError>>()?;
Ok(ParsedDhcpOption::DnsServers(addresses))
}
OptionCode::DomainList => {
let mut opt_val = &mut opt_val;
let mut domains = Vec::new();
while opt_val.len() > 0 {
domains.push(checked::Domain::try_from(
Domain::parse(
&mut opt_val,
None,
)
.map_err(ParseError::DomainParseError)?
.to_string(),
)?);
}
Ok(ParsedDhcpOption::DomainList(domains))
}
}?;
Ok(ParsedRecord::Parsed(opt))
}
}
pub fn duid_uuid() -> [u8; 18] {
let mut duid = [0u8; 18];
duid[1] = 4;
let uuid = Uuid::new_v4();
let uuid = uuid.as_bytes();
for i in 0..16 {
duid[2 + i] = uuid[i];
}
duid
}
#[allow(missing_docs)]
#[derive(Debug)]
pub enum DhcpOption<'a> {
ClientId(&'a Duid),
ServerId(&'a Duid),
Iana(IanaSerializer<'a>),
IaAddr(IaAddrSerializer<'a>),
Oro(&'a [OptionCode]),
Preference(u8),
ElapsedTime(u16),
StatusCode(u16, &'a str),
IaPd(IaPdSerializer<'a>),
IaPrefix(IaPrefixSerializer<'a>),
InformationRefreshTime(u32),
SolMaxRt(u32),
DnsServers(&'a [Ipv6Addr]),
DomainList(&'a [checked::Domain]),
}
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub struct IAID(u32);
impl IAID {
pub const fn new(iaid: u32) -> Self {
Self(iaid)
}
pub fn get(&self) -> u32 {
let IAID(iaid) = self;
*iaid
}
}
#[derive(Debug)]
pub struct IanaSerializer<'a> {
header: IanaHeader,
options: RecordSequenceBuilder<DhcpOption<'a>, Iter<'a, DhcpOption<'a>>>,
}
impl<'a> IanaSerializer<'a> {
pub fn new(iaid: IAID, t1: u32, t2: u32, options: &'a [DhcpOption<'a>]) -> IanaSerializer<'a> {
IanaSerializer {
header: IanaHeader { iaid: U32::new(iaid.get()), t1: U32::new(t1), t2: U32::new(t2) },
options: RecordSequenceBuilder::new(options.iter()),
}
}
}
#[derive(Debug)]
pub struct IaAddrSerializer<'a> {
header: IaAddrHeader,
options: RecordSequenceBuilder<DhcpOption<'a>, Iter<'a, DhcpOption<'a>>>,
}
impl<'a> IaAddrSerializer<'a> {
pub fn new(
addr: Ipv6Addr,
preferred_lifetime: u32,
valid_lifetime: u32,
options: &'a [DhcpOption<'a>],
) -> IaAddrSerializer<'a> {
IaAddrSerializer {
header: IaAddrHeader {
addr,
preferred_lifetime: U32::new(preferred_lifetime),
valid_lifetime: U32::new(valid_lifetime),
},
options: RecordSequenceBuilder::new(options.iter()),
}
}
}
#[derive(Debug)]
pub struct IaPdSerializer<'a> {
header: IaPdHeader,
options: RecordSequenceBuilder<DhcpOption<'a>, Iter<'a, DhcpOption<'a>>>,
}
impl<'a> IaPdSerializer<'a> {
pub fn new(iaid: IAID, t1: u32, t2: u32, options: &'a [DhcpOption<'a>]) -> IaPdSerializer<'a> {
IaPdSerializer {
header: IaPdHeader { iaid: U32::new(iaid.get()), t1: U32::new(t1), t2: U32::new(t2) },
options: RecordSequenceBuilder::new(options.iter()),
}
}
}
#[derive(Debug)]
pub struct IaPrefixSerializer<'a> {
header: IaPrefixHeader,
options: RecordSequenceBuilder<DhcpOption<'a>, Iter<'a, DhcpOption<'a>>>,
}
impl<'a> IaPrefixSerializer<'a> {
pub fn new(
preferred_lifetime_secs: u32,
valid_lifetime_secs: u32,
prefix: Subnet<Ipv6Addr>,
options: &'a [DhcpOption<'a>],
) -> IaPrefixSerializer<'a> {
IaPrefixSerializer {
header: IaPrefixHeader {
preferred_lifetime_secs: U32::new(preferred_lifetime_secs),
valid_lifetime_secs: U32::new(valid_lifetime_secs),
prefix_length: prefix.prefix(),
prefix: prefix.network(),
},
options: RecordSequenceBuilder::new(options.iter()),
}
}
}
impl DhcpOption<'_> {
pub fn code(&self) -> OptionCode {
option_to_code!(
self,
DhcpOption::ClientId(_),
DhcpOption::ServerId(_),
DhcpOption::Iana(_),
DhcpOption::IaAddr(_),
DhcpOption::Oro(_),
DhcpOption::Preference(_),
DhcpOption::ElapsedTime(_),
DhcpOption::StatusCode(_, _),
DhcpOption::IaPd(_),
DhcpOption::IaPrefix(_),
DhcpOption::InformationRefreshTime(_),
DhcpOption::SolMaxRt(_),
DhcpOption::DnsServers(_),
DhcpOption::DomainList(_)
)
}
}
impl<'a> RecordBuilder for DhcpOption<'a> {
fn serialized_len(&self) -> usize {
4 + match self {
DhcpOption::ClientId(duid) | DhcpOption::ServerId(duid) => {
u16::try_from(duid.len()).unwrap_or(18).into()
}
DhcpOption::Iana(IanaSerializer { header, options }) => {
u16::try_from(header.as_bytes().len() + options.serialized_len())
.expect("overflows")
.into()
}
DhcpOption::IaAddr(IaAddrSerializer { header, options }) => {
u16::try_from(header.as_bytes().len() + options.serialized_len())
.expect("overflows")
.into()
}
DhcpOption::Oro(opts) => u16::try_from(2 * opts.len()).unwrap_or(0).into(),
DhcpOption::Preference(v) => std::mem::size_of_val(v),
DhcpOption::ElapsedTime(v) => std::mem::size_of_val(v),
DhcpOption::StatusCode(v, message) => std::mem::size_of_val(v) + message.len(),
DhcpOption::IaPd(IaPdSerializer { header, options }) => {
u16::try_from(header.as_bytes().len() + options.serialized_len())
.expect("overflows")
.into()
}
DhcpOption::IaPrefix(IaPrefixSerializer { header, options }) => {
u16::try_from(header.as_bytes().len() + options.serialized_len())
.expect("overflows")
.into()
}
DhcpOption::InformationRefreshTime(v) => std::mem::size_of_val(v),
DhcpOption::SolMaxRt(v) => std::mem::size_of_val(v),
DhcpOption::DnsServers(recursive_name_servers) => {
u16::try_from(16 * recursive_name_servers.len()).unwrap_or(0).into()
}
DhcpOption::DomainList(domains) => {
u16::try_from(domains.iter().fold(0, |tot, domain| tot + domain.bytes_len()))
.unwrap_or(0)
.into()
}
}
}
fn serialize_into(&self, mut buf: &mut [u8]) {
let mut buf = &mut buf;
let () = buf.write_obj_front(&U16::new(self.code().into())).expect("buffer is too small");
match self {
DhcpOption::ClientId(duid) | DhcpOption::ServerId(duid) => {
match u16::try_from(duid.len()) {
Ok(len) => {
let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
let () = buf.write_obj_front(*duid).expect("buffer is too small");
}
Err(std::num::TryFromIntError { .. }) => {
let duid = duid_uuid();
let len = u16::try_from(duid.len()).expect("uuid length is too long");
let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
let () = buf.write_obj_front(&duid).expect("buffer is too small");
}
}
}
DhcpOption::Iana(IanaSerializer { header, options }) => {
let len = u16::try_from(header.as_bytes().len() + options.serialized_len())
.expect("overflows");
let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
let () = buf.write_obj_front(header).expect("buffer is too small");
let () = options.serialize_into(buf);
}
DhcpOption::IaAddr(IaAddrSerializer { header, options }) => {
let len = u16::try_from(header.as_bytes().len() + options.serialized_len())
.expect("overflows");
let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
let () = buf.write_obj_front(header).expect("buffer is too small");
let () = options.serialize_into(buf);
}
DhcpOption::Oro(requested_opts) => {
let (requested_opts, len) = u16::try_from(2 * requested_opts.len()).map_or_else(
|std::num::TryFromIntError { .. }| {
(&[][..], 0)
},
|len| (*requested_opts, len),
);
let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
for opt_code in requested_opts.into_iter() {
let () = buf
.write_obj_front(&u16::from(*opt_code).to_be_bytes())
.expect("buffer is too small");
}
}
DhcpOption::Preference(pref_val) => {
let () = buf.write_obj_front(&U16::new(1)).expect("buffer is too small");
let () = buf.write_obj_front(pref_val).expect("buffer is too small");
}
DhcpOption::ElapsedTime(elapsed_time) => {
let () = buf
.write_obj_front(&U16::new(
mem::size_of_val(elapsed_time).try_into().expect("overflows"),
))
.expect("buffer is too small");
let () =
buf.write_obj_front(&U16::new(*elapsed_time)).expect("buffer is too small");
}
DhcpOption::StatusCode(code, message) => {
let opt_len = u16::try_from(2 + message.len()).expect("overflows");
let () = buf.write_obj_front(&U16::new(opt_len)).expect("buffer is too small");
let () = buf.write_obj_front(&U16::new(*code)).expect("buffer is too small");
let () = buf.write_obj_front(message.as_bytes()).expect("buffer is too small");
}
DhcpOption::IaPd(IaPdSerializer { header, options }) => {
let len = u16::try_from(header.as_bytes().len() + options.serialized_len())
.expect("overflows");
let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
buf.write_obj_front(header).expect("buffer is too small");
let () = options.serialize_into(buf);
}
DhcpOption::IaPrefix(IaPrefixSerializer { header, options }) => {
let len = u16::try_from(header.as_bytes().len() + options.serialized_len())
.expect("overflows");
let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
buf.write_obj_front(header).expect("buffer is too small");
let () = options.serialize_into(buf);
}
DhcpOption::InformationRefreshTime(information_refresh_time) => {
let () = buf
.write_obj_front(&U16::new(
mem::size_of_val(information_refresh_time).try_into().expect("overflows"),
))
.expect("buffer is too small");
let () = buf
.write_obj_front(&U32::new(*information_refresh_time))
.expect("buffer is too small");
}
DhcpOption::SolMaxRt(sol_max_rt) => {
let () = buf
.write_obj_front(&U16::new(
mem::size_of_val(sol_max_rt).try_into().expect("overflows"),
))
.expect("buffer is too small");
let () = buf.write_obj_front(&U32::new(*sol_max_rt)).expect("buffer is too small");
}
DhcpOption::DnsServers(recursive_name_servers) => {
let (recursive_name_servers, len) =
u16::try_from(16 * recursive_name_servers.len()).map_or_else(
|std::num::TryFromIntError { .. }| {
(&[][..], 0)
},
|len| (*recursive_name_servers, len),
);
let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
recursive_name_servers.iter().for_each(|server_addr| {
let () = buf.write_obj_front(server_addr.bytes()).expect("buffer is too small");
})
}
DhcpOption::DomainList(domains) => {
let (domains, len) =
u16::try_from(domains.iter().map(|domain| domain.bytes_len()).sum::<usize>())
.map_or_else(
|std::num::TryFromIntError { .. }| {
(&[][..], 0)
},
|len| (*domains, len),
);
let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
domains.iter().for_each(|domain| {
domain.serialize(&mut buf);
})
}
}
}
}
type TransactionId = [u8; 3];
#[derive(Debug)]
pub struct Message<'a, B> {
msg_type: MessageType,
transaction_id: &'a TransactionId,
options: Records<B, ParsedDhcpOptionImpl>,
}
impl<'a, B: SplitByteSlice> Message<'a, B> {
pub fn msg_type(&self) -> MessageType {
self.msg_type
}
pub fn transaction_id(&self) -> &TransactionId {
&self.transaction_id
}
pub fn options<'b: 'a>(&'b self) -> impl 'b + Iterator<Item = ParsedDhcpOption<'a>> {
self.options.iter()
}
}
impl<'a, B: 'a + SplitByteSlice + IntoByteSlice<'a>> ParsablePacket<B, ()> for Message<'a, B> {
type Error = ParseError;
fn parse_metadata(&self) -> ParseMetadata {
let Self { msg_type, transaction_id, options } = self;
ParseMetadata::from_packet(
0,
mem::size_of_val(msg_type) + mem::size_of_val(transaction_id) + options.bytes().len(),
0,
)
}
fn parse<BV: BufferView<B>>(mut buf: BV, _args: ()) -> Result<Self, ParseError> {
let msg_type =
MessageType::try_from(buf.take_byte_front().ok_or(ParseError::BufferExhausted)?)?;
let transaction_id = Ref::into_ref(
buf.take_obj_front::<TransactionId>().ok_or(ParseError::BufferExhausted)?,
);
let options = Records::<_, ParsedDhcpOptionImpl>::parse(buf.take_rest_front())?;
Ok(Message { msg_type, transaction_id, options })
}
}
#[derive(Debug)]
pub struct MessageBuilder<'a> {
msg_type: MessageType,
transaction_id: TransactionId,
options: RecordSequenceBuilder<DhcpOption<'a>, Iter<'a, DhcpOption<'a>>>,
}
impl<'a> MessageBuilder<'a> {
pub fn new(
msg_type: MessageType,
transaction_id: TransactionId,
options: &'a [DhcpOption<'a>],
) -> MessageBuilder<'a> {
MessageBuilder {
msg_type,
transaction_id,
options: RecordSequenceBuilder::new(options.iter()),
}
}
}
impl InnerPacketBuilder for MessageBuilder<'_> {
fn bytes_len(&self) -> usize {
let Self { msg_type, transaction_id, options } = self;
mem::size_of_val(msg_type) + mem::size_of_val(transaction_id) + options.serialized_len()
}
fn serialize(&self, mut buffer: &mut [u8]) {
let Self { msg_type, transaction_id, options } = self;
let mut buffer = &mut buffer;
let () = buffer.write_obj_front(msg_type).expect("buffer is too small");
let () = buffer.write_obj_front(transaction_id).expect("buffer is too small");
let () = options.serialize_into(buffer);
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use net_declare::{net_ip_v6, net_subnet_v6};
use std::str::FromStr;
use test_case::test_case;
fn test_buf_with_no_options() -> Vec<u8> {
let builder = MessageBuilder::new(MessageType::Solicit, [1, 2, 3], &[]);
let mut buf = vec![0; builder.bytes_len()];
let () = builder.serialize(&mut buf);
buf
}
#[test]
fn test_message_serialization() {
let iaaddr_options = [DhcpOption::StatusCode(0, "Success.")];
let iana_options = [
DhcpOption::Preference(42),
DhcpOption::IaAddr(IaAddrSerializer::new(
Ipv6Addr::from([0, 1, 2, 3, 4, 5, 6, 107, 108, 109, 110, 111, 212, 213, 214, 215]),
3600,
7200,
&iaaddr_options,
)),
];
let iaprefix_options = [DhcpOption::StatusCode(0, "Success.")];
let iapd_options = [DhcpOption::IaPrefix(IaPrefixSerializer::new(
9999,
6666,
net_subnet_v6!("abcd:1234::/56"),
&iaprefix_options,
))];
let dns_servers = [
Ipv6Addr::from([0, 1, 2, 3, 4, 5, 6, 107, 108, 109, 110, 111, 212, 213, 214, 215]),
Ipv6Addr::from([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]),
];
let domains = [
checked::Domain::from_str("fuchsia.dev").expect("failed to construct test domain"),
checked::Domain::from_str("www.google.com").expect("failed to construct test domain"),
];
let options = [
DhcpOption::ClientId(&[4, 5, 6]),
DhcpOption::ServerId(&[8]),
DhcpOption::Iana(IanaSerializer::new(IAID::new(42), 3000, 6500, &iana_options)),
DhcpOption::Oro(&[OptionCode::ClientId, OptionCode::ServerId]),
DhcpOption::Preference(42),
DhcpOption::ElapsedTime(3600),
DhcpOption::StatusCode(0, "Success."),
DhcpOption::IaPd(IaPdSerializer::new(IAID::new(44), 5000, 7000, &iapd_options)),
DhcpOption::InformationRefreshTime(86400),
DhcpOption::SolMaxRt(86400),
DhcpOption::DnsServers(&dns_servers),
DhcpOption::DomainList(&domains),
];
let builder = MessageBuilder::new(MessageType::Solicit, [1, 2, 3], &options);
assert_eq!(builder.bytes_len(), 256);
let mut buf = vec![0; builder.bytes_len()];
let () = builder.serialize(&mut buf);
#[rustfmt::skip]
assert_eq!(
buf[..],
[
1, 1, 2, 3, 0, 1, 0, 3, 4, 5, 6, 0, 2, 0, 1, 8, 0, 3, 0, 59, 0, 0, 0, 42, 0, 0, 11, 184, 0, 0, 25, 100, 0, 7, 0, 1, 42, 0, 5, 0, 38, 0, 1, 2, 3, 4, 5, 6, 107, 108, 109, 110, 111, 212, 213, 214, 215, 0, 0, 14, 16, 0, 0, 28, 32, 0, 13, 0, 10, 0, 0, 83, 117, 99, 99, 101, 115, 115, 46,
0, 6, 0, 4, 0, 1, 0, 2, 0, 7, 0, 1, 42, 0, 8, 0, 2, 14, 16, 0, 13, 0, 10, 0, 0, 83, 117, 99, 99, 101, 115, 115, 46,
0, 25, 0, 55,
0, 0, 0, 44,
0, 0, 19, 136,
0, 0, 27, 88,
0, 26, 0, 39,
0, 0, 39, 15,
0, 0, 26, 10,
56,
0xab, 0xcd, 0x12, 0x34, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0, 13, 0, 10, 0, 0, 83, 117, 99, 99, 101, 115, 115, 46,
0, 32, 0, 4, 0, 1, 81, 128, 0, 82, 0, 4, 0, 1, 81, 128, 0, 23, 0, 32,
0, 1, 2, 3, 4, 5, 6, 107, 108, 109, 110, 111, 212, 213, 214, 215,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
0, 24, 0, 29,
7, 102, 117, 99, 104, 115, 105, 97, 3, 100, 101, 118, 0,
3, 119, 119, 119, 6, 103, 111, 111, 103, 108, 101, 3, 99, 111, 109, 0
],
);
}
#[test]
fn test_message_serialization_parsing_roundtrip() {
let iaaddr_suboptions = [DhcpOption::StatusCode(0, "Success.")];
let iana_suboptions = [
DhcpOption::Preference(42),
DhcpOption::IaAddr(IaAddrSerializer::new(
Ipv6Addr::from([0, 1, 2, 3, 4, 5, 6, 107, 108, 109, 110, 111, 212, 213, 214, 215]),
7200,
9000,
&iaaddr_suboptions,
)),
];
let iaprefix_options = [DhcpOption::StatusCode(0, "Success.")];
let iapd_options = [DhcpOption::IaPrefix(IaPrefixSerializer::new(
89658902,
82346231,
net_subnet_v6!("1234:5678:1231::/48"),
&iaprefix_options,
))];
let dns_servers = [net_ip_v6!("::")];
let domains = [
checked::Domain::from_str("fuchsia.dev").expect("failed to construct test domain"),
checked::Domain::from_str("www.google.com").expect("failed to construct test domain"),
];
let options = [
DhcpOption::ClientId(&[4, 5, 6]),
DhcpOption::ServerId(&[8]),
DhcpOption::Iana(IanaSerializer::new(IAID::new(1234), 7000, 8800, &iana_suboptions)),
DhcpOption::Oro(&[OptionCode::ClientId, OptionCode::ServerId]),
DhcpOption::Preference(42),
DhcpOption::ElapsedTime(3600),
DhcpOption::StatusCode(0, "Success."),
DhcpOption::IaPd(IaPdSerializer::new(IAID::new(1412), 6513, 9876, &iapd_options)),
DhcpOption::InformationRefreshTime(86400),
DhcpOption::SolMaxRt(86400),
DhcpOption::DnsServers(&dns_servers),
DhcpOption::DomainList(&domains),
];
let builder = MessageBuilder::new(MessageType::Solicit, [1, 2, 3], &options);
let mut buf = vec![0; builder.bytes_len()];
let () = builder.serialize(&mut buf);
let mut buf = &buf[..];
let msg = Message::parse(&mut buf, ()).expect("parse should succeed");
assert_eq!(msg.msg_type, MessageType::Solicit);
assert_eq!(msg.transaction_id, &[1, 2, 3]);
let got_options: Vec<_> = msg.options.iter().collect();
let iana_buf = [
0, 0, 4, 210, 0, 0, 27, 88, 0, 0, 34, 96, 0, 7, 0, 1, 42, 0, 5, 0, 38, 0, 1, 2, 3, 4,
5, 6, 107, 108, 109, 110, 111, 212, 213, 214, 215, 0, 0, 28, 32, 0, 0, 35, 40, 0, 13,
0, 10, 0, 0, 83, 117, 99, 99, 101, 115, 115, 46,
];
let iapd_buf = [
0, 0, 5, 132, 0, 0, 25, 113, 0, 0, 38, 148, 0, 26, 0, 39, 5, 88, 22, 22, 4, 232, 128, 247, 48, 0x12, 0x34, 0x56, 0x78, 0x12, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0, 13, 0, 10, 0, 0, 83, 117, 99, 99, 101, 115, 115, 46,
];
let options = [
ParsedDhcpOption::ClientId(&[4, 5, 6]),
ParsedDhcpOption::ServerId(&[8]),
ParsedDhcpOption::Iana(IanaData::new(&iana_buf[..]).expect("construction failed")),
ParsedDhcpOption::Oro(vec![OptionCode::ClientId, OptionCode::ServerId]),
ParsedDhcpOption::Preference(42),
ParsedDhcpOption::ElapsedTime(3600),
ParsedDhcpOption::StatusCode(U16::new(0), "Success."),
ParsedDhcpOption::IaPd(
IaPdData::new(&iapd_buf[..]).expect("IA_PD construction failed"),
),
ParsedDhcpOption::InformationRefreshTime(86400),
ParsedDhcpOption::SolMaxRt(U32::new(86400)),
ParsedDhcpOption::DnsServers(vec![Ipv6Addr::from([0; 16])]),
ParsedDhcpOption::DomainList(vec![
checked::Domain::from_str("fuchsia.dev").expect("failed to construct test domain"),
checked::Domain::from_str("www.google.com")
.expect("failed to construct test domain"),
]),
];
assert_eq!(got_options, options);
}
const OVERFLOW_LENGTH: usize = u16::MAX as usize + 1;
#[test]
fn test_message_serialization_duid_too_long() {
let options = [DhcpOption::ClientId(&[0u8; OVERFLOW_LENGTH])];
let builder = MessageBuilder::new(MessageType::Solicit, [1, 2, 3], &options);
let mut buf = vec![0; builder.bytes_len()];
let () = builder.serialize(&mut buf);
assert_eq!(buf.len(), 26);
assert_eq!(
buf[..8],
[
1, 1, 2, 3, 0, 1, 0, 18, ],
);
let mut buf = &buf[..];
let _: Message<'_, _> = Message::parse(&mut buf, ()).expect("parse should succeed");
}
#[test]
fn test_message_serialization_oro_too_long() {
let options = [DhcpOption::Oro(&[OptionCode::Preference; OVERFLOW_LENGTH][..])];
let builder = MessageBuilder::new(MessageType::Solicit, [1, 2, 3], &options);
let mut buf = vec![0; builder.bytes_len()];
let () = builder.serialize(&mut buf);
assert_eq!(
buf[..],
[
1, 1, 2, 3, 0, 6, 0, 0, ],
);
let mut buf = &buf[..];
let _: Message<'_, _> = Message::parse(&mut buf, ()).expect("parse should succeed");
}
#[test]
fn test_option_serialization_parsing_roundtrip() {
let mut buf = [0u8; 6];
let option = DhcpOption::ElapsedTime(42);
option.serialize_into(&mut buf);
assert_eq!(buf, [0, 8, 0, 2, 0, 42]);
let options = Records::<_, ParsedDhcpOptionImpl>::parse_with_context(&buf[..], ())
.expect("parse should succeed");
let options: Vec<ParsedDhcpOption<'_>> = options.iter().collect();
assert_eq!(options[..], [ParsedDhcpOption::ElapsedTime(42)]);
}
#[test]
fn test_buffer_too_short() {
let buf = [];
assert_matches!(Message::parse(&mut &buf[..], ()), Err(ParseError::BufferExhausted));
let buf = [
1, 0, ];
assert_matches!(Message::parse(&mut &buf[..], ()), Err(ParseError::BufferExhausted));
let buf = [
1, 1, 2, 3, 0, ];
assert_matches!(Message::parse(&mut &buf[..], ()), Err(ParseError::BufferExhausted));
let buf = [
1, 1, 2, 3, 0, 1, 0, ];
assert_matches!(Message::parse(&mut &buf[..], ()), Err(ParseError::BufferExhausted));
let buf = [
1, 1, 2, 3, 0, 1, 0, 100, 1, 2, ];
assert_matches!(Message::parse(&mut &buf[..], ()), Err(ParseError::BufferExhausted));
}
#[test]
fn test_invalid_message_type() {
let mut buf = test_buf_with_no_options();
buf[0] = 0;
assert_matches!(Message::parse(&mut &buf[..], ()), Err(ParseError::InvalidMessageType(0)));
}
#[test]
fn test_skip_invalid_op_code() {
let mut buf = test_buf_with_no_options();
buf.extend_from_slice(&[
0, 0, 0, 1, 0, 0, 1, 0, 3, 4, 5, 6, ]);
let mut buf = &buf[..];
let msg = Message::parse(&mut buf, ()).expect("parse should succeed");
let got_options: Vec<_> = msg.options.iter().collect();
assert_eq!(got_options, [ParsedDhcpOption::ClientId(&[4, 5, 6])]);
}
#[test]
fn test_iana_no_suboptions_serialization_parsing_roundtrip() {
let mut buf = [0u8; 16];
let option = DhcpOption::Iana(IanaSerializer::new(IAID::new(3456), 1024, 54321, &[]));
option.serialize_into(&mut buf);
assert_eq!(buf, [0, 3, 0, 12, 0, 0, 13, 128, 0, 0, 4, 0, 0, 0, 212, 49]);
let options = Records::<_, ParsedDhcpOptionImpl>::parse_with_context(&buf[..], ())
.expect("parse should succeed");
let options: Vec<ParsedDhcpOption<'_>> = options.iter().collect();
let iana_buf = [0, 0, 13, 128, 0, 0, 4, 0, 0, 0, 212, 49];
assert_eq!(
options[..],
[ParsedDhcpOption::Iana(IanaData::new(&iana_buf[..]).expect("construction failed"))]
);
}
#[test]
fn test_iana_invalid_opt_len() {
let mut buf = test_buf_with_no_options();
buf.extend_from_slice(&[
0, 3, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0,
]);
assert_matches!(
Message::parse(&mut &buf[..], ()),
Err(ParseError::InvalidOpLen(OptionCode::Iana, 8))
);
}
#[test]
fn test_iaaddr_no_suboptions_serialization_parsing_roundtrip() {
let mut buf = [0u8; 28];
let option = DhcpOption::IaAddr(IaAddrSerializer::new(
Ipv6Addr::from([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]),
0,
0,
&[],
));
option.serialize_into(&mut buf);
assert_eq!(
buf,
[
0, 5, 0, 24, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0,
0, 0, 0, 0, 0, 0
]
);
let options = Records::<_, ParsedDhcpOptionImpl>::parse_with_context(&buf[..], ())
.expect("parse should succeed");
let options: Vec<ParsedDhcpOption<'_>> = options.iter().collect();
let iaaddr_buf = [
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 0, 0,
];
assert_eq!(
options[..],
[ParsedDhcpOption::IaAddr(
IaAddrData::new(&iaaddr_buf[..]).expect("construction failed")
)]
);
}
#[test]
fn test_iaaddr_invalid_opt_len() {
let mut buf = test_buf_with_no_options();
buf.extend_from_slice(&[
0, 5, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0,
]);
assert_matches!(
Message::parse(&mut &buf[..], ()),
Err(ParseError::InvalidOpLen(OptionCode::IaAddr, 8))
);
}
#[test]
fn test_invalid_oro_opt_len() {
let mut buf = test_buf_with_no_options();
buf.extend_from_slice(&[
0, 6, 0, 1, 0,
]);
assert_matches!(
Message::parse(&mut &buf[..], ()),
Err(ParseError::InvalidOpLen(OptionCode::Oro, 1))
);
}
#[test]
fn test_invalid_preference_opt_len() {
let mut buf = test_buf_with_no_options();
buf.extend_from_slice(&[
0, 7, 0, 2, 0, 0,
]);
assert_matches!(
Message::parse(&mut &buf[..], ()),
Err(ParseError::InvalidOpLen(OptionCode::Preference, 2))
);
}
#[test]
fn test_elapsed_time_invalid_opt_len() {
let mut buf = test_buf_with_no_options();
buf.extend_from_slice(&[
0, 8, 0, 3, 0, 0, 0,
]);
assert_matches!(
Message::parse(&mut &buf[..], ()),
Err(ParseError::InvalidOpLen(OptionCode::ElapsedTime, 3))
);
}
#[test]
fn test_status_code_invalid_opt_len() {
let mut buf = test_buf_with_no_options();
buf.extend_from_slice(&[
0, 13, 0, 1, 0, 0, 0,
]);
assert_matches!(
Message::parse(&mut &buf[..], ()),
Err(ParseError::InvalidOpLen(OptionCode::StatusCode, 1))
);
}
#[test]
fn test_information_refresh_time_invalid_opt_len() {
let mut buf = test_buf_with_no_options();
buf.extend_from_slice(&[
0, 32, 0, 3, 0, 0, 0,
]);
assert_matches!(
Message::parse(&mut &buf[..], ()),
Err(ParseError::InvalidOpLen(OptionCode::InformationRefreshTime, 3))
);
}
#[test]
fn test_sol_max_rt_invalid_opt_len() {
let mut buf = test_buf_with_no_options();
buf.extend_from_slice(&[
0, 82, 0, 3, 0, 0, 0,
]);
assert_matches!(
Message::parse(&mut &buf[..], ()),
Err(ParseError::InvalidOpLen(OptionCode::SolMaxRt, 3))
);
}
#[test]
fn test_dns_servers_invalid_opt_len() {
let mut buf = test_buf_with_no_options();
buf.extend_from_slice(&[
0, 23, 0, 17, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
]);
assert_matches!(
Message::parse(&mut &buf[..], ()),
Err(ParseError::InvalidOpLen(OptionCode::DnsServers, 17))
);
}
#[test_case(TimeValue::new(0), TimeValue::Zero)]
#[test_case(TimeValue::new(5), TimeValue::NonZero(NonZeroTimeValue::Finite(NonZeroOrMaxU32::new(5).expect("should succeed for non zero or u32::MAX values"))))]
#[test_case(TimeValue::new(u32::MAX), TimeValue::NonZero(NonZeroTimeValue::Infinity))]
fn test_time_value_new(time_value: TimeValue, expected_variant: TimeValue) {
assert_eq!(time_value, expected_variant);
}
#[test_case(
NonZeroTimeValue::Finite(
NonZeroOrMaxU32::new(1)
.expect("should succeed for non zero or u32::MAX values")
))]
#[test_case(NonZeroTimeValue::Infinity)]
fn test_time_value_ord(non_zero_tv: NonZeroTimeValue) {
assert!(TimeValue::Zero < TimeValue::NonZero(non_zero_tv));
}
#[test]
fn test_non_zero_time_value_ord() {
assert!(
NonZeroTimeValue::Finite(
NonZeroOrMaxU32::new(u32::MAX - 1)
.expect("should succeed for non zero or u32::MAX values")
) < NonZeroTimeValue::Infinity
);
}
#[test_case(0, None)]
#[test_case(60, Some(NonZeroOrMaxU32::new(60).unwrap()))]
#[test_case(u32::MAX, None)]
fn test_non_zero_or_max_u32_new(t: u32, expected: Option<NonZeroOrMaxU32>) {
assert_eq!(NonZeroOrMaxU32::new(t), expected);
}
#[test_case(1)]
#[test_case(4321)]
#[test_case(u32::MAX - 1)]
fn test_non_zero_or_max_u32_get(t: u32) {
assert_eq!(
NonZeroOrMaxU32::new(t).expect("should succeed for non-zero or u32::MAX values").get(),
t
);
}
}