use bitflags::bitflags; use byteorder::{ByteOrder, NetworkEndian}; use core::fmt; use super::{Error, Result}; use crate::time::Duration; use crate::wire::{Ipv6Address, Ipv6Packet, Ipv6Repr, MAX_HARDWARE_ADDRESS_LEN}; use crate::wire::RawHardwareAddress; enum_with_unknown! { /// NDISC Option Type pub enum Type(u8) { /// Source Link-layer Address SourceLinkLayerAddr = 0x1, /// Target Link-layer Address TargetLinkLayerAddr = 0x2, /// Prefix Information PrefixInformation = 0x3, /// Redirected Header RedirectedHeader = 0x4, /// MTU Mtu = 0x5 } } impl fmt::Display for Type { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Type::SourceLinkLayerAddr => write!(f, "source link-layer address"), Type::TargetLinkLayerAddr => write!(f, "target link-layer address"), Type::PrefixInformation => write!(f, "prefix information"), Type::RedirectedHeader => write!(f, "redirected header"), Type::Mtu => write!(f, "mtu"), Type::Unknown(id) => write!(f, "{id}"), } } } bitflags! { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PrefixInfoFlags: u8 { const ON_LINK = 0b10000000; const ADDRCONF = 0b01000000; } } /// A read/write wrapper around an [NDISC Option]. /// /// [NDISC Option]: https://tools.ietf.org/html/rfc4861#section-4.6 #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct NdiscOption> { buffer: T, } // Format of an NDISC Option // // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Type | Length | ... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ~ ... ~ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // See https://tools.ietf.org/html/rfc4861#section-4.6 for details. mod field { #![allow(non_snake_case)] use crate::wire::field::*; // 8-bit identifier of the type of option. pub const TYPE: usize = 0; // 8-bit unsigned integer. Length of the option, in units of 8 octets. pub const LENGTH: usize = 1; // Minimum length of an option. pub const MIN_OPT_LEN: usize = 8; // Variable-length field. Option-Type-specific data. pub const fn DATA(length: u8) -> Field { 2..length as usize * 8 } // Source/Target Link-layer Option fields. // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Type | Length | Link-Layer Address ... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // Prefix Information Option fields. // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Type | Length | Prefix Length |L|A| Reserved1 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Valid Lifetime | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Preferred Lifetime | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Reserved2 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | | // + + // | | // + Prefix + // | | // + + // | | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // Prefix length. pub const PREFIX_LEN: usize = 2; // Flags field of prefix header. pub const FLAGS: usize = 3; // Valid lifetime. pub const VALID_LT: Field = 4..8; // Preferred lifetime. pub const PREF_LT: Field = 8..12; // Reserved bits pub const PREF_RESERVED: Field = 12..16; // Prefix pub const PREFIX: Field = 16..32; // Redirected Header Option fields. // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Type | Length | Reserved | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Reserved | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | | // ~ IP header + data ~ // | | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // Reserved bits. pub const REDIRECTED_RESERVED: Field = 2..8; pub const REDIR_MIN_SZ: usize = 48; // MTU Option fields // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Type | Length | Reserved | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | MTU | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // MTU pub const MTU: Field = 4..8; } /// Core getter methods relevant to any type of NDISC option. impl> NdiscOption { /// Create a raw octet buffer with an NDISC Option structure. pub const fn new_unchecked(buffer: T) -> NdiscOption { NdiscOption { buffer } } /// Shorthand for a combination of [new_unchecked] and [check_len]. /// /// [new_unchecked]: #method.new_unchecked /// [check_len]: #method.check_len pub fn new_checked(buffer: T) -> Result> { let opt = Self::new_unchecked(buffer); opt.check_len()?; // A data length field of 0 is invalid. if opt.data_len() == 0 { return Err(Error); } Ok(opt) } /// Ensure that no accessor method will panic if called. /// Returns `Err(Error)` if the buffer is too short. /// /// The result of this check is invalidated by calling [set_data_len]. /// /// [set_data_len]: #method.set_data_len pub fn check_len(&self) -> Result<()> { let data = self.buffer.as_ref(); let len = data.len(); if len < field::MIN_OPT_LEN { Err(Error) } else { let data_range = field::DATA(data[field::LENGTH]); if len < data_range.end { Err(Error) } else { match self.option_type() { Type::SourceLinkLayerAddr | Type::TargetLinkLayerAddr | Type::Mtu => Ok(()), Type::PrefixInformation if data_range.end >= field::PREFIX.end => Ok(()), Type::RedirectedHeader if data_range.end >= field::REDIR_MIN_SZ => Ok(()), Type::Unknown(_) => Ok(()), _ => Err(Error), } } } } /// Consume the NDISC option, returning the underlying buffer. pub fn into_inner(self) -> T { self.buffer } /// Return the option type. #[inline] pub fn option_type(&self) -> Type { let data = self.buffer.as_ref(); Type::from(data[field::TYPE]) } /// Return the length of the data. #[inline] pub fn data_len(&self) -> u8 { let data = self.buffer.as_ref(); data[field::LENGTH] } } /// Getter methods only relevant for Source/Target Link-layer Address options. impl> NdiscOption { /// Return the Source/Target Link-layer Address. #[inline] pub fn link_layer_addr(&self) -> RawHardwareAddress { let len = MAX_HARDWARE_ADDRESS_LEN.min(self.data_len() as usize * 8 - 2); let data = self.buffer.as_ref(); RawHardwareAddress::from_bytes(&data[2..len + 2]) } } /// Getter methods only relevant for the MTU option. impl> NdiscOption { /// Return the MTU value. #[inline] pub fn mtu(&self) -> u32 { let data = self.buffer.as_ref(); NetworkEndian::read_u32(&data[field::MTU]) } } /// Getter methods only relevant for the Prefix Information option. impl> NdiscOption { /// Return the prefix length. #[inline] pub fn prefix_len(&self) -> u8 { self.buffer.as_ref()[field::PREFIX_LEN] } /// Return the prefix information flags. #[inline] pub fn prefix_flags(&self) -> PrefixInfoFlags { PrefixInfoFlags::from_bits_truncate(self.buffer.as_ref()[field::FLAGS]) } /// Return the valid lifetime of the prefix. #[inline] pub fn valid_lifetime(&self) -> Duration { let data = self.buffer.as_ref(); Duration::from_secs(NetworkEndian::read_u32(&data[field::VALID_LT]) as u64) } /// Return the preferred lifetime of the prefix. #[inline] pub fn preferred_lifetime(&self) -> Duration { let data = self.buffer.as_ref(); Duration::from_secs(NetworkEndian::read_u32(&data[field::PREF_LT]) as u64) } /// Return the prefix. #[inline] pub fn prefix(&self) -> Ipv6Address { let data = self.buffer.as_ref(); Ipv6Address::from_bytes(&data[field::PREFIX]) } } impl<'a, T: AsRef<[u8]> + ?Sized> NdiscOption<&'a T> { /// Return the option data. #[inline] pub fn data(&self) -> &'a [u8] { let len = self.data_len(); let data = self.buffer.as_ref(); &data[field::DATA(len)] } } /// Core setter methods relevant to any type of NDISC option. impl + AsMut<[u8]>> NdiscOption { /// Set the option type. #[inline] pub fn set_option_type(&mut self, value: Type) { let data = self.buffer.as_mut(); data[field::TYPE] = value.into(); } /// Set the option data length. #[inline] pub fn set_data_len(&mut self, value: u8) { let data = self.buffer.as_mut(); data[field::LENGTH] = value; } } /// Setter methods only relevant for Source/Target Link-layer Address options. impl + AsMut<[u8]>> NdiscOption { /// Set the Source/Target Link-layer Address. #[inline] pub fn set_link_layer_addr(&mut self, addr: RawHardwareAddress) { let data = self.buffer.as_mut(); data[2..2 + addr.len()].copy_from_slice(addr.as_bytes()) } } /// Setter methods only relevant for the MTU option. impl + AsMut<[u8]>> NdiscOption { /// Set the MTU value. #[inline] pub fn set_mtu(&mut self, value: u32) { let data = self.buffer.as_mut(); NetworkEndian::write_u32(&mut data[field::MTU], value); } } /// Setter methods only relevant for the Prefix Information option. impl + AsMut<[u8]>> NdiscOption { /// Set the prefix length. #[inline] pub fn set_prefix_len(&mut self, value: u8) { self.buffer.as_mut()[field::PREFIX_LEN] = value; } /// Set the prefix information flags. #[inline] pub fn set_prefix_flags(&mut self, flags: PrefixInfoFlags) { self.buffer.as_mut()[field::FLAGS] = flags.bits(); } /// Set the valid lifetime of the prefix. #[inline] pub fn set_valid_lifetime(&mut self, time: Duration) { let data = self.buffer.as_mut(); NetworkEndian::write_u32(&mut data[field::VALID_LT], time.secs() as u32); } /// Set the preferred lifetime of the prefix. #[inline] pub fn set_preferred_lifetime(&mut self, time: Duration) { let data = self.buffer.as_mut(); NetworkEndian::write_u32(&mut data[field::PREF_LT], time.secs() as u32); } /// Clear the reserved bits. #[inline] pub fn clear_prefix_reserved(&mut self) { let data = self.buffer.as_mut(); NetworkEndian::write_u32(&mut data[field::PREF_RESERVED], 0); } /// Set the prefix. #[inline] pub fn set_prefix(&mut self, addr: Ipv6Address) { let data = self.buffer.as_mut(); data[field::PREFIX].copy_from_slice(addr.as_bytes()); } } /// Setter methods only relevant for the Redirected Header option. impl + AsMut<[u8]>> NdiscOption { /// Clear the reserved bits. #[inline] pub fn clear_redirected_reserved(&mut self) { let data = self.buffer.as_mut(); data[field::REDIRECTED_RESERVED].fill_with(|| 0); } } impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> NdiscOption<&'a mut T> { /// Return a mutable pointer to the option data. #[inline] pub fn data_mut(&mut self) -> &mut [u8] { let len = self.data_len(); let data = self.buffer.as_mut(); &mut data[field::DATA(len)] } } impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for NdiscOption<&'a T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match Repr::parse(self) { Ok(repr) => write!(f, "{repr}"), Err(err) => { write!(f, "NDISC Option ({err})")?; Ok(()) } } } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PrefixInformation { pub prefix_len: u8, pub flags: PrefixInfoFlags, pub valid_lifetime: Duration, pub preferred_lifetime: Duration, pub prefix: Ipv6Address, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct RedirectedHeader<'a> { pub header: Ipv6Repr, pub data: &'a [u8], } /// A high-level representation of an NDISC Option. #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Repr<'a> { SourceLinkLayerAddr(RawHardwareAddress), TargetLinkLayerAddr(RawHardwareAddress), PrefixInformation(PrefixInformation), RedirectedHeader(RedirectedHeader<'a>), Mtu(u32), Unknown { type_: u8, length: u8, data: &'a [u8], }, } impl<'a> Repr<'a> { /// Parse an NDISC Option and return a high-level representation. pub fn parse(opt: &NdiscOption<&'a T>) -> Result> where T: AsRef<[u8]> + ?Sized, { match opt.option_type() { Type::SourceLinkLayerAddr => { if opt.data_len() >= 1 { Ok(Repr::SourceLinkLayerAddr(opt.link_layer_addr())) } else { Err(Error) } } Type::TargetLinkLayerAddr => { if opt.data_len() >= 1 { Ok(Repr::TargetLinkLayerAddr(opt.link_layer_addr())) } else { Err(Error) } } Type::PrefixInformation => { if opt.data_len() == 4 { Ok(Repr::PrefixInformation(PrefixInformation { prefix_len: opt.prefix_len(), flags: opt.prefix_flags(), valid_lifetime: opt.valid_lifetime(), preferred_lifetime: opt.preferred_lifetime(), prefix: opt.prefix(), })) } else { Err(Error) } } Type::RedirectedHeader => { // If the options data length is less than 6, the option // does not have enough data to fill out the IP header // and common option fields. if opt.data_len() < 6 { Err(Error) } else { let redirected_packet = &opt.data()[field::REDIRECTED_RESERVED.len()..]; let ip_packet = Ipv6Packet::new_checked(redirected_packet)?; let ip_repr = Ipv6Repr::parse(&ip_packet)?; Ok(Repr::RedirectedHeader(RedirectedHeader { header: ip_repr, data: &redirected_packet[ip_repr.buffer_len()..][..ip_repr.payload_len], })) } } Type::Mtu => { if opt.data_len() == 1 { Ok(Repr::Mtu(opt.mtu())) } else { Err(Error) } } Type::Unknown(id) => { // A length of 0 is invalid. if opt.data_len() != 0 { Ok(Repr::Unknown { type_: id, length: opt.data_len(), data: opt.data(), }) } else { Err(Error) } } } } /// Return the length of a header that will be emitted from this high-level representation. pub const fn buffer_len(&self) -> usize { match self { &Repr::SourceLinkLayerAddr(addr) | &Repr::TargetLinkLayerAddr(addr) => { let len = 2 + addr.len(); // Round up to next multiple of 8 (len + 7) / 8 * 8 } &Repr::PrefixInformation(_) => field::PREFIX.end, &Repr::RedirectedHeader(RedirectedHeader { header, data }) => { (8 + header.buffer_len() + data.len() + 7) / 8 * 8 } &Repr::Mtu(_) => field::MTU.end, &Repr::Unknown { length, .. } => field::DATA(length).end, } } /// Emit a high-level representation into an NDISC Option. pub fn emit(&self, opt: &mut NdiscOption<&'a mut T>) where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized, { match *self { Repr::SourceLinkLayerAddr(addr) => { opt.set_option_type(Type::SourceLinkLayerAddr); let opt_len = addr.len() + 2; opt.set_data_len(((opt_len + 7) / 8) as u8); // round to next multiple of 8. opt.set_link_layer_addr(addr); } Repr::TargetLinkLayerAddr(addr) => { opt.set_option_type(Type::TargetLinkLayerAddr); let opt_len = addr.len() + 2; opt.set_data_len(((opt_len + 7) / 8) as u8); // round to next multiple of 8. opt.set_link_layer_addr(addr); } Repr::PrefixInformation(PrefixInformation { prefix_len, flags, valid_lifetime, preferred_lifetime, prefix, }) => { opt.clear_prefix_reserved(); opt.set_option_type(Type::PrefixInformation); opt.set_data_len(4); opt.set_prefix_len(prefix_len); opt.set_prefix_flags(flags); opt.set_valid_lifetime(valid_lifetime); opt.set_preferred_lifetime(preferred_lifetime); opt.set_prefix(prefix); } Repr::RedirectedHeader(RedirectedHeader { header, data }) => { // TODO(thvdveld): I think we need to check if the data we are sending is not // exceeding the MTU. opt.clear_redirected_reserved(); opt.set_option_type(Type::RedirectedHeader); opt.set_data_len((((8 + header.buffer_len() + data.len()) + 7) / 8) as u8); let mut packet = &mut opt.data_mut()[field::REDIRECTED_RESERVED.end - 2..]; let mut ip_packet = Ipv6Packet::new_unchecked(&mut packet); header.emit(&mut ip_packet); ip_packet.payload_mut().copy_from_slice(data); } Repr::Mtu(mtu) => { opt.set_option_type(Type::Mtu); opt.set_data_len(1); opt.set_mtu(mtu); } Repr::Unknown { type_: id, length, data, } => { opt.set_option_type(Type::Unknown(id)); opt.set_data_len(length); opt.data_mut().copy_from_slice(data); } } } } impl<'a> fmt::Display for Repr<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "NDISC Option: ")?; match *self { Repr::SourceLinkLayerAddr(addr) => { write!(f, "SourceLinkLayer addr={addr}") } Repr::TargetLinkLayerAddr(addr) => { write!(f, "TargetLinkLayer addr={addr}") } Repr::PrefixInformation(PrefixInformation { prefix, prefix_len, .. }) => { write!(f, "PrefixInformation prefix={prefix}/{prefix_len}") } Repr::RedirectedHeader(RedirectedHeader { header, .. }) => { write!(f, "RedirectedHeader header={header}") } Repr::Mtu(mtu) => { write!(f, "MTU mtu={mtu}") } Repr::Unknown { type_: id, length, .. } => { write!(f, "Unknown({id}) length={length}") } } } } use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; impl> PrettyPrint for NdiscOption { fn pretty_print( buffer: &dyn AsRef<[u8]>, f: &mut fmt::Formatter, indent: &mut PrettyIndent, ) -> fmt::Result { match NdiscOption::new_checked(buffer) { Err(err) => write!(f, "{indent}({err})"), Ok(ndisc) => match Repr::parse(&ndisc) { Err(_) => Ok(()), Ok(repr) => { write!(f, "{indent}{repr}") } }, } } } #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] #[cfg(test)] mod test { use super::Error; use super::{NdiscOption, PrefixInfoFlags, PrefixInformation, Repr, Type}; use crate::time::Duration; use crate::wire::Ipv6Address; #[cfg(feature = "medium-ethernet")] use crate::wire::EthernetAddress; #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ieee802154"))] use crate::wire::Ieee802154Address; static PREFIX_OPT_BYTES: [u8; 32] = [ 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x03, 0x84, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ]; #[test] fn test_deconstruct() { let opt = NdiscOption::new_unchecked(&PREFIX_OPT_BYTES[..]); assert_eq!(opt.option_type(), Type::PrefixInformation); assert_eq!(opt.data_len(), 4); assert_eq!(opt.prefix_len(), 64); assert_eq!( opt.prefix_flags(), PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF ); assert_eq!(opt.valid_lifetime(), Duration::from_secs(900)); assert_eq!(opt.preferred_lifetime(), Duration::from_secs(1000)); assert_eq!(opt.prefix(), Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)); } #[test] fn test_construct() { let mut bytes = [0x00; 32]; let mut opt = NdiscOption::new_unchecked(&mut bytes[..]); opt.set_option_type(Type::PrefixInformation); opt.set_data_len(4); opt.set_prefix_len(64); opt.set_prefix_flags(PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF); opt.set_valid_lifetime(Duration::from_secs(900)); opt.set_preferred_lifetime(Duration::from_secs(1000)); opt.set_prefix(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)); assert_eq!(&PREFIX_OPT_BYTES[..], &*opt.into_inner()); } #[test] fn test_short_packet() { assert_eq!(NdiscOption::new_checked(&[0x00, 0x00]), Err(Error)); let bytes = [0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; assert_eq!(NdiscOption::new_checked(&bytes), Err(Error)); } #[cfg(feature = "medium-ethernet")] #[test] fn test_repr_parse_link_layer_opt_ethernet() { let mut bytes = [0x01, 0x01, 0x54, 0x52, 0x00, 0x12, 0x23, 0x34]; let addr = EthernetAddress([0x54, 0x52, 0x00, 0x12, 0x23, 0x34]); { assert_eq!( Repr::parse(&NdiscOption::new_unchecked(&bytes)), Ok(Repr::SourceLinkLayerAddr(addr.into())) ); } bytes[0] = 0x02; { assert_eq!( Repr::parse(&NdiscOption::new_unchecked(&bytes)), Ok(Repr::TargetLinkLayerAddr(addr.into())) ); } } #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ieee802154"))] #[test] fn test_repr_parse_link_layer_opt_ieee802154() { let mut bytes = [ 0x01, 0x02, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; let addr = Ieee802154Address::Extended([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); { assert_eq!( Repr::parse(&NdiscOption::new_unchecked(&bytes)), Ok(Repr::SourceLinkLayerAddr(addr.into())) ); } bytes[0] = 0x02; { assert_eq!( Repr::parse(&NdiscOption::new_unchecked(&bytes)), Ok(Repr::TargetLinkLayerAddr(addr.into())) ); } } #[test] fn test_repr_parse_prefix_info() { let repr = Repr::PrefixInformation(PrefixInformation { prefix_len: 64, flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF, valid_lifetime: Duration::from_secs(900), preferred_lifetime: Duration::from_secs(1000), prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), }); assert_eq!( Repr::parse(&NdiscOption::new_unchecked(&PREFIX_OPT_BYTES)), Ok(repr) ); } #[test] fn test_repr_emit_prefix_info() { let mut bytes = [0x2a; 32]; let repr = Repr::PrefixInformation(PrefixInformation { prefix_len: 64, flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF, valid_lifetime: Duration::from_secs(900), preferred_lifetime: Duration::from_secs(1000), prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), }); let mut opt = NdiscOption::new_unchecked(&mut bytes); repr.emit(&mut opt); assert_eq!(&opt.into_inner()[..], &PREFIX_OPT_BYTES[..]); } #[test] fn test_repr_parse_mtu() { let bytes = [0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0xdc]; assert_eq!( Repr::parse(&NdiscOption::new_unchecked(&bytes)), Ok(Repr::Mtu(1500)) ); } }