use crate::icmpv6::{NdpOptionReadError, NdpOptionType};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct PrefixInformation {
pub prefix_length: u8,
pub on_link: bool,
pub autonomous_address_configuration: bool,
pub valid_lifetime: u32,
pub preferred_lifetime: u32,
pub prefix: [u8; 16],
}
impl PrefixInformation {
const LENGTH_UNITS: u8 = 4;
pub const LEN: usize = 32;
pub const ON_LINK_MASK: u8 = 0b1000_0000;
pub const AUTONOMOUS_ADDRESS_CONFIGURATION_MASK: u8 = 0b0100_0000;
pub fn from_bytes(bytes: [u8; Self::LEN]) -> Result<PrefixInformation, NdpOptionReadError> {
let bytes = bytes.as_slice();
let (type_and_len, rest) = bytes.split_first_chunk::<2>().unwrap();
if *type_and_len != [NdpOptionType::PREFIX_INFORMATION.0, Self::LENGTH_UNITS] {
return Err(NdpOptionReadError::UnexpectedHeader {
expected_option_id: NdpOptionType::PREFIX_INFORMATION,
actual_option_id: NdpOptionType(type_and_len[0]),
expected_length_units: Self::LENGTH_UNITS,
actual_length_units: type_and_len[1],
});
}
let (prefix_length_and_flags, rest) = rest.split_first_chunk::<2>().unwrap();
let (valid_lifetime, rest) = rest.split_first_chunk::<4>().unwrap();
let (preferred_lifetime, rest) = rest.split_first_chunk::<4>().unwrap();
let (_reserved2, prefix) = rest.split_first_chunk::<4>().unwrap();
let prefix = *prefix.first_chunk::<16>().unwrap();
Ok(PrefixInformation {
prefix_length: prefix_length_and_flags[0],
on_link: 0 != prefix_length_and_flags[1] & Self::ON_LINK_MASK,
autonomous_address_configuration: 0
!= prefix_length_and_flags[1] & Self::AUTONOMOUS_ADDRESS_CONFIGURATION_MASK,
valid_lifetime: u32::from_be_bytes(*valid_lifetime),
preferred_lifetime: u32::from_be_bytes(*preferred_lifetime),
prefix,
})
}
pub fn from_slice(bytes: &[u8]) -> Result<PrefixInformation, NdpOptionReadError> {
let bytes: &[u8; Self::LEN] =
bytes
.try_into()
.map_err(|_| NdpOptionReadError::UnexpectedSize {
option_id: NdpOptionType::PREFIX_INFORMATION,
expected_size: Self::LEN,
actual_size: bytes.len(),
})?;
PrefixInformation::from_bytes(*bytes)
}
pub fn to_bytes(&self) -> [u8; Self::LEN] {
let mut bytes = [0u8; Self::LEN];
let (type_and_len, rest) = bytes.as_mut_slice().split_first_chunk_mut::<2>().unwrap();
let (prefix_length_and_flags, rest) = rest.split_first_chunk_mut::<2>().unwrap();
let (valid_lifetime, rest) = rest.split_first_chunk_mut::<4>().unwrap();
let (preferred_lifetime, rest) = rest.split_first_chunk_mut::<4>().unwrap();
let (_reserved2, prefix) = rest.split_first_chunk_mut::<4>().unwrap();
let prefix = prefix.first_chunk_mut::<16>().unwrap();
*type_and_len = [NdpOptionType::PREFIX_INFORMATION.into(), Self::LENGTH_UNITS];
*prefix_length_and_flags = [
self.prefix_length,
(if self.on_link { Self::ON_LINK_MASK } else { 0 })
| if self.autonomous_address_configuration {
Self::AUTONOMOUS_ADDRESS_CONFIGURATION_MASK
} else {
0
},
];
*valid_lifetime = self.valid_lifetime.to_be_bytes();
*preferred_lifetime = self.preferred_lifetime.to_be_bytes();
*prefix = self.prefix;
bytes
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn to_and_from_bytes(
prefix_length in any::<u8>(),
on_link in any::<bool>(),
autonomous_address_configuration in any::<bool>(),
valid_lifetime in any::<u32>(),
preferred_lifetime in any::<u32>(),
prefix in any::<[u8;16]>()
) {
let value = PrefixInformation {
prefix_length,
on_link,
autonomous_address_configuration,
valid_lifetime,
preferred_lifetime,
prefix,
};
assert_eq!(PrefixInformation::from_bytes(value.to_bytes()), Ok(value));
assert_eq!(PrefixInformation::from_slice(&value.to_bytes()), Ok(value));
}
}
#[test]
fn from_slice_len_error() {
assert_eq!(
PrefixInformation::from_slice(&[0u8; PrefixInformation::LEN - 1]),
Err(NdpOptionReadError::UnexpectedSize {
option_id: NdpOptionType::PREFIX_INFORMATION,
expected_size: PrefixInformation::LEN,
actual_size: PrefixInformation::LEN - 1,
})
);
assert_eq!(
PrefixInformation::from_slice(&[0u8; PrefixInformation::LEN + 1]),
Err(NdpOptionReadError::UnexpectedSize {
option_id: NdpOptionType::PREFIX_INFORMATION,
expected_size: PrefixInformation::LEN,
actual_size: PrefixInformation::LEN + 1,
})
);
}
#[test]
fn from_bytes_invalid_header() {
let mut bytes = PrefixInformation {
prefix_length: 64,
on_link: true,
autonomous_address_configuration: true,
valid_lifetime: 1,
preferred_lifetime: 2,
prefix: [3; 16],
}
.to_bytes();
bytes[0] = NdpOptionType::MTU.0;
assert_eq!(
PrefixInformation::from_bytes(bytes),
Err(NdpOptionReadError::UnexpectedHeader {
expected_option_id: NdpOptionType::PREFIX_INFORMATION,
actual_option_id: NdpOptionType::MTU,
expected_length_units: 4,
actual_length_units: 4,
})
);
let mut bytes = PrefixInformation {
prefix_length: 64,
on_link: true,
autonomous_address_configuration: true,
valid_lifetime: 1,
preferred_lifetime: 2,
prefix: [3; 16],
}
.to_bytes();
bytes[1] = 1;
assert_eq!(
PrefixInformation::from_bytes(bytes),
Err(NdpOptionReadError::UnexpectedHeader {
expected_option_id: NdpOptionType::PREFIX_INFORMATION,
actual_option_id: NdpOptionType::PREFIX_INFORMATION,
expected_length_units: 4,
actual_length_units: 1,
})
);
}
}