use core::net::Ipv6Addr;
use core::str::FromStr;
use crate::error::{CrafterError, Result};
use super::constants::{
IPV6_HOME_ADDRESS_DATA_LEN, IPV6_JUMBO_PAYLOAD_DATA_LEN, IPV6_OPTION_ACTION_SHIFT,
IPV6_OPTION_CHANGE_EN_ROUTE_MASK, IPV6_OPTION_DATA_MAX_LEN, IPV6_OPTION_HEADER_LEN,
IPV6_OPTION_HOME_ADDRESS, IPV6_OPTION_JUMBO_PAYLOAD, IPV6_OPTION_NUMBER_MASK, IPV6_OPTION_PAD1,
IPV6_OPTION_PADN, IPV6_OPTION_ROUTER_ALERT, IPV6_ROUTER_ALERT_ACTIVE_NETWORKS,
IPV6_ROUTER_ALERT_DATA_LEN, IPV6_ROUTER_ALERT_MLD, IPV6_ROUTER_ALERT_MPLS_OAM,
IPV6_ROUTER_ALERT_RESERVED, IPV6_ROUTER_ALERT_RSVP,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Ipv6OptionAction {
Skip,
Discard,
DiscardSendIcmp,
DiscardSendIcmpIfNotMulticast,
}
impl Ipv6OptionAction {
pub const fn bits(self) -> u8 {
match self {
Self::Skip => 0,
Self::Discard => 1,
Self::DiscardSendIcmp => 2,
Self::DiscardSendIcmpIfNotMulticast => 3,
}
}
pub const fn from_option_type(option_type: u8) -> Self {
match option_type >> IPV6_OPTION_ACTION_SHIFT {
0 => Self::Skip,
1 => Self::Discard,
2 => Self::DiscardSendIcmp,
_ => Self::DiscardSendIcmpIfNotMulticast,
}
}
pub fn from_bits(bits: u8) -> Result<Self> {
match bits {
0 => Ok(Self::Skip),
1 => Ok(Self::Discard),
2 => Ok(Self::DiscardSendIcmp),
3 => Ok(Self::DiscardSendIcmpIfNotMulticast),
_ => Err(CrafterError::invalid_field_value(
"ipv6.option.action",
"IPv6 option action must fit in two bits",
)),
}
}
}
impl TryFrom<u8> for Ipv6OptionAction {
type Error = CrafterError;
fn try_from(value: u8) -> Result<Self> {
Self::from_bits(value)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Ipv6Option {
Pad1,
PadN {
data: Vec<u8>,
},
RouterAlert {
value_bytes: [u8; 2],
},
JumboPayload {
length_bytes: [u8; 4],
},
HomeAddress {
address_bytes: [u8; 16],
},
Generic {
option_type: u8,
data: Vec<u8>,
},
}
impl Ipv6Option {
pub const fn pad1() -> Self {
Self::Pad1
}
pub fn padn(total_len: usize) -> Result<Self> {
if total_len < IPV6_OPTION_HEADER_LEN {
return Err(CrafterError::invalid_field_value(
"ipv6.option.padn.length",
"PadN total length must be at least 2 bytes",
));
}
Self::padn_data(vec![0; total_len - IPV6_OPTION_HEADER_LEN])
}
pub fn pad_n(total_len: usize) -> Result<Self> {
Self::padn(total_len)
}
pub fn padn_data(data: impl Into<Vec<u8>>) -> Result<Self> {
let data = data.into();
validate_ipv6_option_data_len("ipv6.option.padn.length", data.len())?;
Ok(Self::PadN { data })
}
pub fn pad_n_data(data: impl Into<Vec<u8>>) -> Result<Self> {
Self::padn_data(data)
}
pub const fn router_alert(value: u16) -> Self {
Self::RouterAlert {
value_bytes: [(value >> 8) as u8, value as u8],
}
}
pub const fn jumbo_payload(length: u32) -> Self {
Self::JumboPayload {
length_bytes: [
(length >> 24) as u8,
(length >> 16) as u8,
(length >> 8) as u8,
length as u8,
],
}
}
pub fn home_address(address: Ipv6Addr) -> Self {
Self::HomeAddress {
address_bytes: address.octets(),
}
}
pub fn home_address_str(address: &str) -> Result<Self> {
let address = Ipv6Addr::from_str(address).map_err(|_| {
CrafterError::invalid_field_value("ipv6_address", "expected textual IPv6 address")
})?;
Ok(Self::home_address(address))
}
pub fn generic(option_type: u8, data: impl Into<Vec<u8>>) -> Result<Self> {
validate_ipv6_generic_option_type(option_type)?;
let data = data.into();
validate_ipv6_option_data_len("ipv6.option.length", data.len())?;
Ok(Self::Generic { option_type, data })
}
pub fn unknown(option_type: u8, data: impl Into<Vec<u8>>) -> Result<Self> {
Self::generic(option_type, data)
}
pub const fn option_type(&self) -> u8 {
match self {
Self::Pad1 => IPV6_OPTION_PAD1,
Self::PadN { .. } => IPV6_OPTION_PADN,
Self::RouterAlert { .. } => IPV6_OPTION_ROUTER_ALERT,
Self::JumboPayload { .. } => IPV6_OPTION_JUMBO_PAYLOAD,
Self::HomeAddress { .. } => IPV6_OPTION_HOME_ADDRESS,
Self::Generic { option_type, .. } => *option_type,
}
}
pub const fn kind(&self) -> u8 {
self.option_type()
}
pub fn data(&self) -> &[u8] {
match self {
Self::Pad1 => &[],
Self::PadN { data } | Self::Generic { data, .. } => data,
Self::RouterAlert { value_bytes } => value_bytes,
Self::JumboPayload { length_bytes } => length_bytes,
Self::HomeAddress { address_bytes } => address_bytes,
}
}
pub const fn router_alert_value(&self) -> Option<u16> {
match self {
Self::RouterAlert { value_bytes } => {
Some(((value_bytes[0] as u16) << 8) | value_bytes[1] as u16)
}
_ => None,
}
}
pub const fn router_alert_value_label(&self) -> Option<&'static str> {
match self.router_alert_value() {
Some(value) => Some(ipv6_router_alert_value_label(value)),
None => None,
}
}
pub const fn jumbo_payload_length(&self) -> Option<u32> {
match self {
Self::JumboPayload { length_bytes } => Some(
((length_bytes[0] as u32) << 24)
| ((length_bytes[1] as u32) << 16)
| ((length_bytes[2] as u32) << 8)
| length_bytes[3] as u32,
),
_ => None,
}
}
pub fn home_address_value(&self) -> Option<Ipv6Addr> {
match self {
Self::HomeAddress { address_bytes } => Some(Ipv6Addr::from(*address_bytes)),
_ => None,
}
}
pub const fn action_bits(&self) -> u8 {
self.option_type() >> IPV6_OPTION_ACTION_SHIFT
}
pub const fn action(&self) -> Ipv6OptionAction {
Ipv6OptionAction::from_option_type(self.option_type())
}
pub const fn change_en_route(&self) -> bool {
self.option_type() & IPV6_OPTION_CHANGE_EN_ROUTE_MASK != 0
}
pub const fn may_change_en_route(&self) -> bool {
self.change_en_route()
}
pub const fn rest(&self) -> u8 {
self.option_type() & IPV6_OPTION_NUMBER_MASK
}
pub const fn option_number(&self) -> u8 {
self.rest()
}
pub fn encoded_len(&self) -> usize {
match self {
Self::Pad1 => 1,
Self::RouterAlert { .. } => IPV6_OPTION_HEADER_LEN + IPV6_ROUTER_ALERT_DATA_LEN,
Self::JumboPayload { .. } => IPV6_OPTION_HEADER_LEN + IPV6_JUMBO_PAYLOAD_DATA_LEN,
Self::HomeAddress { .. } => IPV6_OPTION_HEADER_LEN + IPV6_HOME_ADDRESS_DATA_LEN,
Self::PadN { data } | Self::Generic { data, .. } => IPV6_OPTION_HEADER_LEN + data.len(),
}
}
pub fn encode(&self) -> Result<Vec<u8>> {
let mut bytes = Vec::with_capacity(self.encoded_len());
match self {
Self::Pad1 => bytes.push(IPV6_OPTION_PAD1),
Self::PadN { data } => {
validate_ipv6_option_data_len("ipv6.option.padn.length", data.len())?;
bytes.extend_from_slice(&[IPV6_OPTION_PADN, data.len() as u8]);
bytes.extend_from_slice(data);
}
Self::RouterAlert { value_bytes } => {
bytes.extend_from_slice(&[
IPV6_OPTION_ROUTER_ALERT,
IPV6_ROUTER_ALERT_DATA_LEN as u8,
]);
bytes.extend_from_slice(value_bytes);
}
Self::JumboPayload { length_bytes } => {
bytes.extend_from_slice(&[
IPV6_OPTION_JUMBO_PAYLOAD,
IPV6_JUMBO_PAYLOAD_DATA_LEN as u8,
]);
bytes.extend_from_slice(length_bytes);
}
Self::HomeAddress { address_bytes } => {
bytes.extend_from_slice(&[
IPV6_OPTION_HOME_ADDRESS,
IPV6_HOME_ADDRESS_DATA_LEN as u8,
]);
bytes.extend_from_slice(address_bytes);
}
Self::Generic { option_type, data } => {
validate_ipv6_generic_option_type(*option_type)?;
validate_ipv6_option_data_len("ipv6.option.length", data.len())?;
bytes.extend_from_slice(&[*option_type, data.len() as u8]);
bytes.extend_from_slice(data);
}
}
Ok(bytes)
}
pub fn decode_all(bytes: &[u8]) -> Result<Vec<Self>> {
Ipv6OptionIter::new(bytes).collect()
}
}
#[derive(Debug, Clone)]
pub struct Ipv6OptionIter<'a> {
bytes: &'a [u8],
offset: usize,
done: bool,
}
impl<'a> Ipv6OptionIter<'a> {
pub const fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
offset: 0,
done: false,
}
}
}
impl Iterator for Ipv6OptionIter<'_> {
type Item = Result<Ipv6Option>;
fn next(&mut self) -> Option<Self::Item> {
if self.done || self.offset >= self.bytes.len() {
return None;
}
let start = self.offset;
let option_type = self.bytes[start];
if option_type == IPV6_OPTION_PAD1 {
self.offset += 1;
return Some(Ok(Ipv6Option::Pad1));
}
if start + IPV6_OPTION_HEADER_LEN > self.bytes.len() {
self.done = true;
return Some(Err(CrafterError::buffer_too_short(
"ipv6.option.header",
start + IPV6_OPTION_HEADER_LEN,
self.bytes.len(),
)));
}
let data_len = self.bytes[start + 1] as usize;
let Some(end) = start
.checked_add(IPV6_OPTION_HEADER_LEN)
.and_then(|header_end| header_end.checked_add(data_len))
else {
self.done = true;
return Some(Err(CrafterError::invalid_field_value(
"ipv6.option.length",
"IPv6 option length overflows the option area",
)));
};
if end > self.bytes.len() {
self.done = true;
return Some(Err(CrafterError::buffer_too_short(
"ipv6.option.data",
end,
self.bytes.len(),
)));
}
self.offset = end;
let data = self.bytes[start + IPV6_OPTION_HEADER_LEN..end].to_vec();
if option_type == IPV6_OPTION_PADN {
Some(Ok(Ipv6Option::PadN { data }))
} else if option_type == IPV6_OPTION_JUMBO_PAYLOAD
&& data_len == IPV6_JUMBO_PAYLOAD_DATA_LEN
{
let mut length_bytes = [0; 4];
length_bytes.copy_from_slice(&data);
Some(Ok(Ipv6Option::JumboPayload { length_bytes }))
} else if option_type == IPV6_OPTION_ROUTER_ALERT && data_len == IPV6_ROUTER_ALERT_DATA_LEN
{
let mut value_bytes = [0; 2];
value_bytes.copy_from_slice(&data);
Some(Ok(Ipv6Option::RouterAlert { value_bytes }))
} else if option_type == IPV6_OPTION_HOME_ADDRESS && data_len == IPV6_HOME_ADDRESS_DATA_LEN
{
let mut address_bytes = [0; 16];
address_bytes.copy_from_slice(&data);
Some(Ok(Ipv6Option::HomeAddress { address_bytes }))
} else {
Some(Ok(Ipv6Option::Generic { option_type, data }))
}
}
}
pub const fn ipv6_router_alert_value_label(value: u16) -> &'static str {
match value {
IPV6_ROUTER_ALERT_MLD => "MLD",
IPV6_ROUTER_ALERT_RSVP => "RSVP",
IPV6_ROUTER_ALERT_ACTIVE_NETWORKS => "Active Networks",
IPV6_ROUTER_ALERT_RESERVED => "Reserved",
4..=35 => "Aggregated Reservation Nesting Level",
36..=67 => "QoS NSLP Aggregation Levels",
68 => "NSIS NATFW NSLP",
IPV6_ROUTER_ALERT_MPLS_OAM => "MPLS OAM (DEPRECATED)",
70..=65502 => "Unassigned",
65503..=65535 => "Reserved",
}
}
fn validate_ipv6_option_data_len(field: &'static str, data_len: usize) -> Result<()> {
if data_len > IPV6_OPTION_DATA_MAX_LEN {
return Err(CrafterError::invalid_field_value(
field,
"IPv6 option data length must fit in one byte",
));
}
Ok(())
}
fn validate_ipv6_generic_option_type(option_type: u8) -> Result<()> {
match option_type {
IPV6_OPTION_PAD1 => Err(CrafterError::invalid_field_value(
"ipv6.option.type",
"Pad1 option does not carry length or data fields",
)),
IPV6_OPTION_PADN => Err(CrafterError::invalid_field_value(
"ipv6.option.type",
"PadN option should use the PadN option model",
)),
_ => Ok(()),
}
}