use crate::{
checks::internet::ipv6::{
IPV6_HEADER_LEN, validate_ipv6_header_length, validate_ipv6_payload_length,
validate_ipv6_version,
},
errors::internet::ipv6::Ipv6Error,
};
use std::convert::TryFrom;
use std::net::Ipv6Addr;
#[cfg_attr(doc, aquamarine::aquamarine)]
#[derive(Debug, PartialEq)]
pub struct Ipv6Packet<'a> {
pub version_tc_flow: [u8; 4],
pub payload_length: u16,
pub next_header: u8,
pub hop_limit: u8,
pub source_addr: Ipv6Addr,
pub dest_addr: Ipv6Addr,
pub extension_headers: &'a [u8],
pub payload: &'a [u8],
}
impl<'a> Ipv6Packet<'a> {
pub fn version(&self) -> u8 {
self.version_tc_flow[0] >> 4
}
pub fn traffic_class(&self) -> u8 {
((self.version_tc_flow[0] & 0x0F) << 4) | (self.version_tc_flow[1] >> 4)
}
pub fn flow_label(&self) -> u32 {
((self.version_tc_flow[1] as u32 & 0x0F) << 16)
| ((self.version_tc_flow[2] as u32) << 8)
| (self.version_tc_flow[3] as u32)
}
}
impl<'a> TryFrom<&'a [u8]> for Ipv6Packet<'a> {
type Error = Ipv6Error;
fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
validate_ipv6_header_length(data)?;
let version = data[0] >> 4;
validate_ipv6_version(version)?;
let version_tc_flow = [data[0], data[1], data[2], data[3]];
let payload_length = u16::from_be_bytes([data[4], data[5]]);
let next_header = data[6];
let hop_limit = data[7];
let source_addr = Ipv6Addr::new(
u16::from_be_bytes([data[8], data[9]]),
u16::from_be_bytes([data[10], data[11]]),
u16::from_be_bytes([data[12], data[13]]),
u16::from_be_bytes([data[14], data[15]]),
u16::from_be_bytes([data[16], data[17]]),
u16::from_be_bytes([data[18], data[19]]),
u16::from_be_bytes([data[20], data[21]]),
u16::from_be_bytes([data[22], data[23]]),
);
let dest_addr = Ipv6Addr::new(
u16::from_be_bytes([data[24], data[25]]),
u16::from_be_bytes([data[26], data[27]]),
u16::from_be_bytes([data[28], data[29]]),
u16::from_be_bytes([data[30], data[31]]),
u16::from_be_bytes([data[32], data[33]]),
u16::from_be_bytes([data[34], data[35]]),
u16::from_be_bytes([data[36], data[37]]),
u16::from_be_bytes([data[38], data[39]]),
);
let total_expected_len = validate_ipv6_payload_length(data.len(), payload_length)?;
let extension_headers = &[0u8];
let payload = if payload_length > 0 {
&data[IPV6_HEADER_LEN..total_expected_len]
} else {
&[]
};
Ok(Ipv6Packet {
version_tc_flow,
payload_length,
next_header,
hop_limit,
source_addr,
dest_addr,
extension_headers,
payload,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv6Addr;
#[test]
fn test_ipv6_packet_parsing() {
let data = [
0x60, 0x12, 0x34, 0x50, 0x00, 0x20, 0x11, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let packet = Ipv6Packet::try_from(&data[..]).unwrap();
assert_eq!(packet.version(), 6);
assert_eq!(packet.traffic_class(), 0x01);
assert_eq!(packet.flow_label(), 0x23450);
assert_eq!(packet.payload_length, 32);
assert_eq!(packet.next_header, 0x11); assert_eq!(packet.hop_limit, 0x40); assert_eq!(packet.source_addr, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
assert_eq!(packet.dest_addr, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
assert_eq!(packet.payload.len(), 32);
}
#[test]
fn test_invalid_version() {
let data = [
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01,
];
let result = Ipv6Packet::try_from(&data[..]);
assert!(matches!(result, Err(Ipv6Error::InvalidVersion(4))));
}
#[test]
fn test_invalid_length() {
let data = [0u8; 39];
let result = Ipv6Packet::try_from(&data[..]);
assert!(matches!(
result,
Err(Ipv6Error::InvalidLength {
expected: 40,
actual: 39
})
));
}
#[test]
fn test_invalid_payload_length() {
let mut data = [0u8; 40];
data[0] = 0x60;
data[4] = 0x00;
data[5] = 100;
let result = Ipv6Packet::try_from(&data[..]);
assert!(matches!(
result,
Err(Ipv6Error::InvalidPayloadLength {
expected: 100,
actual: 0
})
));
}
}