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! {
pub enum Type(u8) {
SourceLinkLayerAddr = 0x1,
TargetLinkLayerAddr = 0x2,
PrefixInformation = 0x3,
RedirectedHeader = 0x4,
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;
}
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct NdiscOption<T: AsRef<[u8]>> {
buffer: T,
}
mod field {
#![allow(non_snake_case)]
use crate::wire::field::*;
pub const TYPE: usize = 0;
pub const LENGTH: usize = 1;
pub const MIN_OPT_LEN: usize = 8;
pub const fn DATA(length: u8) -> Field {
2..length as usize * 8
}
pub const PREFIX_LEN: usize = 2;
pub const FLAGS: usize = 3;
pub const VALID_LT: Field = 4..8;
pub const PREF_LT: Field = 8..12;
pub const PREF_RESERVED: Field = 12..16;
pub const PREFIX: Field = 16..32;
pub const REDIRECTED_RESERVED: Field = 2..8;
pub const REDIR_MIN_SZ: usize = 48;
pub const MTU: Field = 4..8;
}
impl<T: AsRef<[u8]>> NdiscOption<T> {
pub const fn new_unchecked(buffer: T) -> NdiscOption<T> {
NdiscOption { buffer }
}
pub fn new_checked(buffer: T) -> Result<NdiscOption<T>> {
let opt = Self::new_unchecked(buffer);
opt.check_len()?;
if opt.data_len() == 0 {
return Err(Error);
}
Ok(opt)
}
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),
}
}
}
}
pub fn into_inner(self) -> T {
self.buffer
}
#[inline]
pub fn option_type(&self) -> Type {
let data = self.buffer.as_ref();
Type::from(data[field::TYPE])
}
#[inline]
pub fn data_len(&self) -> u8 {
let data = self.buffer.as_ref();
data[field::LENGTH]
}
}
impl<T: AsRef<[u8]>> NdiscOption<T> {
#[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])
}
}
impl<T: AsRef<[u8]>> NdiscOption<T> {
#[inline]
pub fn mtu(&self) -> u32 {
let data = self.buffer.as_ref();
NetworkEndian::read_u32(&data[field::MTU])
}
}
impl<T: AsRef<[u8]>> NdiscOption<T> {
#[inline]
pub fn prefix_len(&self) -> u8 {
self.buffer.as_ref()[field::PREFIX_LEN]
}
#[inline]
pub fn prefix_flags(&self) -> PrefixInfoFlags {
PrefixInfoFlags::from_bits_truncate(self.buffer.as_ref()[field::FLAGS])
}
#[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)
}
#[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)
}
#[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> {
#[inline]
pub fn data(&self) -> &'a [u8] {
let len = self.data_len();
let data = self.buffer.as_ref();
&data[field::DATA(len)]
}
}
impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
#[inline]
pub fn set_option_type(&mut self, value: Type) {
let data = self.buffer.as_mut();
data[field::TYPE] = value.into();
}
#[inline]
pub fn set_data_len(&mut self, value: u8) {
let data = self.buffer.as_mut();
data[field::LENGTH] = value;
}
}
impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
#[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())
}
}
impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
#[inline]
pub fn set_mtu(&mut self, value: u32) {
let data = self.buffer.as_mut();
NetworkEndian::write_u32(&mut data[field::MTU], value);
}
}
impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
#[inline]
pub fn set_prefix_len(&mut self, value: u8) {
self.buffer.as_mut()[field::PREFIX_LEN] = value;
}
#[inline]
pub fn set_prefix_flags(&mut self, flags: PrefixInfoFlags) {
self.buffer.as_mut()[field::FLAGS] = flags.bits();
}
#[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);
}
#[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);
}
#[inline]
pub fn clear_prefix_reserved(&mut self) {
let data = self.buffer.as_mut();
NetworkEndian::write_u32(&mut data[field::PREF_RESERVED], 0);
}
#[inline]
pub fn set_prefix(&mut self, addr: Ipv6Address) {
let data = self.buffer.as_mut();
data[field::PREFIX].copy_from_slice(addr.as_bytes());
}
}
impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
#[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> {
#[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],
}
#[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> {
pub fn parse<T>(opt: &NdiscOption<&'a T>) -> Result<Repr<'a>>
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 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) => {
if opt.data_len() != 0 {
Ok(Repr::Unknown {
type_: id,
length: opt.data_len(),
data: opt.data(),
})
} else {
Err(Error)
}
}
}
}
pub const fn buffer_len(&self) -> usize {
match self {
&Repr::SourceLinkLayerAddr(addr) | &Repr::TargetLinkLayerAddr(addr) => {
let len = 2 + addr.len();
(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,
}
}
pub fn emit<T>(&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); 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); 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 }) => {
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<T: AsRef<[u8]>> PrettyPrint for NdiscOption<T> {
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))
);
}
}