use crate::ip::IpNextProtocol;
use crate::packet::{MutablePacket, Packet};
use crate::parse::ParseError;
use bytes::{BufMut, Bytes, BytesMut};
use std::net::Ipv6Addr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub const IPV6_HEADER_LEN: usize = 40;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Ipv6Header {
pub version: u8, pub traffic_class: u8, pub flow_label: u32, pub payload_length: u16,
pub next_header: IpNextProtocol,
pub hop_limit: u8,
pub source: Ipv6Addr,
pub destination: Ipv6Addr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ipv6Packet {
pub header: Ipv6Header,
pub extensions: Vec<Ipv6ExtensionHeader>,
pub payload: Bytes,
}
impl Packet for Ipv6Packet {
type Header = Ipv6Header;
fn from_buf(bytes: &[u8]) -> Option<Self> {
Self::try_from_buf(bytes).ok()
}
fn from_bytes(bytes: Bytes) -> Option<Self> {
Self::try_from_bytes(bytes).ok()
}
fn to_bytes(&self) -> Bytes {
let mut buf = BytesMut::with_capacity(self.total_len());
let vtf_1 = (self.header.version << 4) | (self.header.traffic_class >> 4);
let vtf_2 =
((self.header.traffic_class & 0x0F) << 4) | ((self.header.flow_label >> 16) as u8);
let vtf_3 = (self.header.flow_label >> 8) as u8;
let vtf_4 = self.header.flow_label as u8;
buf.put_u8(vtf_1);
buf.put_u8(vtf_2);
buf.put_u8(vtf_3);
buf.put_u8(vtf_4);
buf.put_u16(self.header.payload_length);
let first_next_header = self
.extensions
.first()
.map(|ext| ext.next_protocol())
.unwrap_or(self.header.next_header);
buf.put_u8(first_next_header.value());
buf.put_u8(self.header.hop_limit);
buf.extend_from_slice(&self.header.source.octets());
buf.extend_from_slice(&self.header.destination.octets());
for ext in &self.extensions {
match ext {
Ipv6ExtensionHeader::HopByHop { next, data }
| Ipv6ExtensionHeader::Destination { next, data } => {
let hdr_ext_len = ((data.len() + 6) / 8) as u8 - 1;
buf.put_u8(next.value());
buf.put_u8(hdr_ext_len);
buf.extend_from_slice(data);
while (2 + data.len()) % 8 != 0 {
buf.put_u8(0);
}
}
Ipv6ExtensionHeader::Routing {
next,
routing_type,
segments_left,
data,
} => {
let hdr_ext_len = ((data.len() + 4 + 6) / 8) as u8 - 1;
buf.put_u8(next.value());
buf.put_u8(hdr_ext_len);
buf.put_u8(*routing_type);
buf.put_u8(*segments_left);
buf.extend_from_slice(data);
while (4 + data.len()) % 8 != 0 {
buf.put_u8(0);
}
}
Ipv6ExtensionHeader::Fragment {
next,
offset,
more,
id,
} => {
buf.put_u8(next.value());
buf.put_u8(0); let offset_flags = (offset << 3) | if *more { 1 } else { 0 };
buf.put_u16(offset_flags);
buf.put_u32(*id);
}
Ipv6ExtensionHeader::Raw { next: _, raw } => {
buf.extend_from_slice(&raw[..]);
}
}
}
buf.extend_from_slice(&self.payload);
buf.freeze()
}
fn header(&self) -> Bytes {
self.to_bytes().slice(..IPV6_HEADER_LEN)
}
fn payload(&self) -> Bytes {
self.payload.clone()
}
fn header_len(&self) -> usize {
IPV6_HEADER_LEN
}
fn payload_len(&self) -> usize {
self.payload.len()
}
fn total_len(&self) -> usize {
self.header_len() + self.payload_len()
}
fn into_parts(self) -> (Self::Header, Bytes) {
(self.header, self.payload)
}
}
impl Ipv6Packet {
pub fn try_from_buf(bytes: &[u8]) -> Result<Self, ParseError> {
parse_ipv6_from_slice(bytes, false)
}
pub fn try_from_bytes(bytes: Bytes) -> Result<Self, ParseError> {
parse_ipv6_from_bytes(bytes, false)
}
pub fn try_from_buf_strict(bytes: &[u8]) -> Result<Self, ParseError> {
parse_ipv6_from_slice(bytes, true)
}
pub fn try_from_bytes_strict(bytes: Bytes) -> Result<Self, ParseError> {
parse_ipv6_from_bytes(bytes, true)
}
pub fn total_len(&self) -> usize {
IPV6_HEADER_LEN
+ self.extensions.iter().map(|ext| ext.len()).sum::<usize>()
+ self.payload.len()
}
pub fn get_extension(&self, kind: ExtensionHeaderType) -> Option<&Ipv6ExtensionHeader> {
self.extensions.iter().find(|ext| ext.kind() == kind)
}
}
fn parse_ipv6_from_slice(bytes: &[u8], strict: bool) -> Result<Ipv6Packet, ParseError> {
parse_ipv6_parts(bytes, strict, |range| Bytes::copy_from_slice(&bytes[range]))
}
fn parse_ipv6_from_bytes(bytes: Bytes, strict: bool) -> Result<Ipv6Packet, ParseError> {
parse_ipv6_parts(&bytes, strict, |range| bytes.slice(range))
}
fn parse_ipv6_parts<F>(
bytes: &[u8],
strict: bool,
mut slice_bytes: F,
) -> Result<Ipv6Packet, ParseError>
where
F: FnMut(std::ops::Range<usize>) -> Bytes,
{
if bytes.len() < IPV6_HEADER_LEN {
return Err(ParseError::BufferTooShort {
context: "IPv6 packet",
minimum: IPV6_HEADER_LEN,
actual: bytes.len(),
});
}
let version_traffic_flow = &bytes[..4];
let version = version_traffic_flow[0] >> 4;
if version != 6 {
return Err(ParseError::Malformed {
context: "IPv6 packet version",
});
}
let traffic_class = ((version_traffic_flow[0] & 0x0F) << 4) | (version_traffic_flow[1] >> 4);
let flow_label = u32::from(version_traffic_flow[1] & 0x0F) << 16
| u32::from(version_traffic_flow[2]) << 8
| u32::from(version_traffic_flow[3]);
let payload_length = u16::from_be_bytes([bytes[4], bytes[5]]);
let mut next_header = IpNextProtocol::new(bytes[6]);
let hop_limit = bytes[7];
let source =
Ipv6Addr::from(
<[u8; 16]>::try_from(&bytes[8..24]).map_err(|_| ParseError::Malformed {
context: "IPv6 source address",
})?,
);
let destination = Ipv6Addr::from(<[u8; 16]>::try_from(&bytes[24..40]).map_err(|_| {
ParseError::Malformed {
context: "IPv6 destination address",
}
})?);
let header = Ipv6Header {
version,
traffic_class,
flow_label,
payload_length,
next_header,
hop_limit,
source,
destination,
};
let declared_total_len = IPV6_HEADER_LEN + payload_length as usize;
if strict && declared_total_len > bytes.len() {
return Err(ParseError::Truncated {
context: "IPv6 payload",
expected: declared_total_len,
actual: bytes.len(),
});
}
let available_end = declared_total_len.min(bytes.len());
let mut offset = IPV6_HEADER_LEN;
let mut extensions = Vec::new();
loop {
match next_header {
IpNextProtocol::Hopopt
| IpNextProtocol::Ipv6Route
| IpNextProtocol::Ipv6Frag
| IpNextProtocol::Ipv6Opts => {
if offset + 2 > available_end {
return Err(ParseError::Truncated {
context: "IPv6 extension header",
expected: offset + 2,
actual: available_end,
});
}
let nh = IpNextProtocol::new(bytes[offset]);
let ext_len = bytes[offset + 1] as usize;
match next_header {
IpNextProtocol::Hopopt | IpNextProtocol::Ipv6Opts => {
let total_len = 8 + ext_len * 8;
if offset + total_len > available_end {
return Err(ParseError::Truncated {
context: "IPv6 extension header",
expected: offset + total_len,
actual: available_end,
});
}
let data = slice_bytes(offset + 2..offset + total_len);
let ext = match next_header {
IpNextProtocol::Hopopt => {
Ipv6ExtensionHeader::HopByHop { next: nh, data }
}
IpNextProtocol::Ipv6Opts => {
Ipv6ExtensionHeader::Destination { next: nh, data }
}
_ => unreachable!(),
};
extensions.push(ext);
next_header = nh;
offset += total_len;
}
IpNextProtocol::Ipv6Route => {
if offset + 4 > available_end {
return Err(ParseError::Truncated {
context: "IPv6 routing header",
expected: offset + 4,
actual: available_end,
});
}
let routing_type = bytes[offset + 2];
let segments_left = bytes[offset + 3];
let total_len = 8 + ext_len * 8;
if offset + total_len > available_end {
return Err(ParseError::Truncated {
context: "IPv6 routing header",
expected: offset + total_len,
actual: available_end,
});
}
extensions.push(Ipv6ExtensionHeader::Routing {
next: nh,
routing_type,
segments_left,
data: slice_bytes(offset + 4..offset + total_len),
});
next_header = nh;
offset += total_len;
}
IpNextProtocol::Ipv6Frag => {
if offset + 8 > available_end {
return Err(ParseError::Truncated {
context: "IPv6 fragment header",
expected: offset + 8,
actual: available_end,
});
}
let frag_off_flags =
u16::from_be_bytes([bytes[offset + 2], bytes[offset + 3]]);
let offset_val = frag_off_flags >> 3;
let more = (frag_off_flags & 0x1) != 0;
let id = u32::from_be_bytes([
bytes[offset + 4],
bytes[offset + 5],
bytes[offset + 6],
bytes[offset + 7],
]);
extensions.push(Ipv6ExtensionHeader::Fragment {
next: nh,
offset: offset_val,
more,
id,
});
next_header = nh;
offset += 8;
}
_ => unreachable!(),
}
}
_ => break,
}
}
Ok(Ipv6Packet {
header,
extensions,
payload: slice_bytes(offset..available_end),
})
}
pub struct MutableIpv6Packet<'a> {
buffer: &'a mut [u8],
}
impl<'a> MutablePacket<'a> for MutableIpv6Packet<'a> {
type Packet = Ipv6Packet;
fn new(buffer: &'a mut [u8]) -> Option<Self> {
if buffer.len() < IPV6_HEADER_LEN {
None
} else {
Some(Self { buffer })
}
}
fn packet(&self) -> &[u8] {
&*self.buffer
}
fn packet_mut(&mut self) -> &mut [u8] {
&mut *self.buffer
}
fn header(&self) -> &[u8] {
&self.packet()[..IPV6_HEADER_LEN]
}
fn header_mut(&mut self) -> &mut [u8] {
let (header, _) = (&mut *self.buffer).split_at_mut(IPV6_HEADER_LEN);
header
}
fn payload(&self) -> &[u8] {
&self.packet()[IPV6_HEADER_LEN..]
}
fn payload_mut(&mut self) -> &mut [u8] {
let (_, payload) = (&mut *self.buffer).split_at_mut(IPV6_HEADER_LEN);
payload
}
}
impl<'a> MutableIpv6Packet<'a> {
pub fn new_unchecked(buffer: &'a mut [u8]) -> Self {
Self { buffer }
}
fn raw(&self) -> &[u8] {
&*self.buffer
}
fn raw_mut(&mut self) -> &mut [u8] {
&mut *self.buffer
}
pub fn payload_len(&self) -> usize {
self.raw().len().saturating_sub(IPV6_HEADER_LEN)
}
pub fn get_version(&self) -> u8 {
self.raw()[0] >> 4
}
pub fn set_version(&mut self, version: u8) {
let buf = self.raw_mut();
buf[0] = (buf[0] & 0x0F) | ((version & 0x0F) << 4);
}
pub fn get_traffic_class(&self) -> u8 {
((self.raw()[0] & 0x0F) << 4) | (self.raw()[1] >> 4)
}
pub fn set_traffic_class(&mut self, class: u8) {
let buf = self.raw_mut();
buf[0] = (buf[0] & 0xF0) | ((class >> 4) & 0x0F);
buf[1] = (buf[1] & 0x0F) | ((class & 0x0F) << 4);
}
pub fn get_flow_label(&self) -> u32 {
let buf = self.raw();
let high = (buf[1] as u32 & 0x0F) << 16;
let mid = (buf[2] as u32) << 8;
let low = buf[3] as u32;
high | mid | low
}
pub fn set_flow_label(&mut self, label: u32) {
let buf = self.raw_mut();
buf[1] = (buf[1] & 0xF0) | (((label >> 16) as u8) & 0x0F);
buf[2] = (label >> 8) as u8;
buf[3] = label as u8;
}
pub fn get_payload_length(&self) -> u16 {
u16::from_be_bytes([self.raw()[4], self.raw()[5]])
}
pub fn set_payload_length(&mut self, length: u16) {
self.raw_mut()[4..6].copy_from_slice(&length.to_be_bytes());
}
pub fn get_next_header(&self) -> IpNextProtocol {
IpNextProtocol::new(self.raw()[6])
}
pub fn set_next_header(&mut self, proto: IpNextProtocol) {
self.raw_mut()[6] = proto.value();
}
pub fn get_hop_limit(&self) -> u8 {
self.raw()[7]
}
pub fn set_hop_limit(&mut self, value: u8) {
self.raw_mut()[7] = value;
}
pub fn get_source(&self) -> Ipv6Addr {
let raw = self.raw();
Ipv6Addr::from([
raw[8], raw[9], raw[10], raw[11], raw[12], raw[13], raw[14], raw[15], raw[16], raw[17],
raw[18], raw[19], raw[20], raw[21], raw[22], raw[23],
])
}
pub fn set_source(&mut self, addr: Ipv6Addr) {
self.raw_mut()[8..24].copy_from_slice(&addr.octets());
}
pub fn get_destination(&self) -> Ipv6Addr {
let raw = self.raw();
Ipv6Addr::from([
raw[24], raw[25], raw[26], raw[27], raw[28], raw[29], raw[30], raw[31], raw[32],
raw[33], raw[34], raw[35], raw[36], raw[37], raw[38], raw[39],
])
}
pub fn set_destination(&mut self, addr: Ipv6Addr) {
self.raw_mut()[24..40].copy_from_slice(&addr.octets());
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExtensionHeaderType {
HopByHop,
Destination,
Routing,
Fragment,
Unknown(u8),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Ipv6ExtensionHeader {
HopByHop {
next: IpNextProtocol,
data: Bytes,
},
Destination {
next: IpNextProtocol,
data: Bytes,
},
Routing {
next: IpNextProtocol,
routing_type: u8,
segments_left: u8,
data: Bytes,
},
Fragment {
next: IpNextProtocol,
offset: u16,
more: bool,
id: u32,
},
Raw {
next: IpNextProtocol,
raw: Bytes,
},
}
impl Ipv6ExtensionHeader {
pub fn next_protocol(&self) -> IpNextProtocol {
match self {
Ipv6ExtensionHeader::HopByHop { next, .. } => *next,
Ipv6ExtensionHeader::Destination { next, .. } => *next,
Ipv6ExtensionHeader::Routing { next, .. } => *next,
Ipv6ExtensionHeader::Fragment { next, .. } => *next,
Ipv6ExtensionHeader::Raw { next, .. } => *next,
}
}
pub fn len(&self) -> usize {
match self {
Ipv6ExtensionHeader::HopByHop { data, .. }
| Ipv6ExtensionHeader::Destination { data, .. } => {
let base = 2 + data.len();
(base + 7) / 8 * 8 }
Ipv6ExtensionHeader::Routing { data, .. } => {
let base = 4 + data.len();
(base + 7) / 8 * 8
}
Ipv6ExtensionHeader::Fragment { .. } => 8,
Ipv6ExtensionHeader::Raw { raw, .. } => raw.len(),
}
}
pub fn kind(&self) -> ExtensionHeaderType {
match self {
Ipv6ExtensionHeader::HopByHop { .. } => ExtensionHeaderType::HopByHop,
Ipv6ExtensionHeader::Destination { .. } => ExtensionHeaderType::Destination,
Ipv6ExtensionHeader::Routing { .. } => ExtensionHeaderType::Routing,
Ipv6ExtensionHeader::Fragment { .. } => ExtensionHeaderType::Fragment,
Ipv6ExtensionHeader::Raw { raw, .. } => {
let kind = raw.get(0).copied().unwrap_or(0xff);
match kind {
0 => ExtensionHeaderType::HopByHop,
43 => ExtensionHeaderType::Routing,
44 => ExtensionHeaderType::Fragment,
60 => ExtensionHeaderType::Destination,
other => ExtensionHeaderType::Unknown(other),
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ip::IpNextProtocol;
use std::net::Ipv6Addr;
#[test]
fn test_ipv6_basic_header_fields() {
let header = Ipv6Header {
version: 6,
traffic_class: 0xaa,
flow_label: 0x12345,
payload_length: 0,
next_header: IpNextProtocol::Udp,
hop_limit: 64,
source: Ipv6Addr::LOCALHOST,
destination: Ipv6Addr::UNSPECIFIED,
};
let packet = Ipv6Packet {
header: header.clone(),
extensions: vec![],
payload: Bytes::new(),
};
assert_eq!(packet.header.version, 6);
assert_eq!(packet.header.traffic_class, 0xaa);
assert_eq!(packet.header.flow_label, 0x12345);
assert_eq!(packet.header.payload_length, 0);
assert_eq!(packet.header.next_header, IpNextProtocol::Udp);
assert_eq!(packet.header.hop_limit, 64);
assert_eq!(packet.header.source, Ipv6Addr::LOCALHOST);
assert_eq!(packet.header.destination, Ipv6Addr::UNSPECIFIED);
let raw = packet.to_bytes();
assert_eq!(raw.len(), IPV6_HEADER_LEN);
let reparsed = Ipv6Packet::from_bytes(raw.clone()).unwrap();
assert_eq!(reparsed.header, packet.header);
}
#[test]
fn test_ipv6_from_bytes_parsing() {
use bytes::Bytes;
let raw_bytes = Bytes::from_static(&[
0x60, 0xA1, 0x23, 0x45, 0x00, 0x08, 0x06, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1a, 0x2b, 0xff, 0xfe, 0x1a,
0x2b, 0x3c, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x02, b'H', b'e', b'l', b'l', b'o', b'!', b'!', b'\n',
]);
let parsed = Ipv6Packet::from_bytes(raw_bytes.clone()).expect("should parse successfully");
assert_eq!(parsed.header.version, 6);
assert_eq!(parsed.header.traffic_class, 0xa);
assert_eq!(parsed.header.flow_label, 0x12345);
assert_eq!(parsed.header.payload_length, 8);
assert_eq!(parsed.header.next_header, IpNextProtocol::Tcp);
assert_eq!(parsed.header.hop_limit, 0x40);
assert_eq!(
parsed.header.source,
"fe80::21a:2bff:fe1a:2b3c".parse::<Ipv6Addr>().unwrap()
);
assert_eq!(
parsed.header.destination,
"ff02::2".parse::<Ipv6Addr>().unwrap()
);
assert_eq!(&parsed.payload[..], b"Hello!!\n");
assert_eq!(parsed.extensions.len(), 0);
assert_eq!(parsed.to_bytes(), raw_bytes);
}
#[test]
fn test_ipv6_payload_roundtrip() {
use bytes::Bytes;
let payload = Bytes::from_static(b"HELLO_WORLDS");
let packet = Ipv6Packet {
header: super::Ipv6Header {
version: 6,
traffic_class: 0,
flow_label: 0,
payload_length: payload.len() as u16,
next_header: IpNextProtocol::Tcp,
hop_limit: 32,
source: Ipv6Addr::LOCALHOST,
destination: Ipv6Addr::LOCALHOST,
},
extensions: vec![],
payload: payload.clone(),
};
let raw = packet.to_bytes();
let parsed = Ipv6Packet::from_bytes(raw.clone()).unwrap();
assert_eq!(parsed.header, packet.header);
assert_eq!(parsed.payload, payload);
assert_eq!(raw.len(), packet.total_len());
}
#[test]
fn test_ipv6_truncated_packet_rejected() {
use bytes::Bytes;
let short = Bytes::from_static(&[0u8; 20]); assert!(Ipv6Packet::from_bytes(short).is_none());
}
#[test]
fn test_ipv6_total_len_computation() {
use bytes::Bytes;
let ext = Ipv6ExtensionHeader::Fragment {
next: IpNextProtocol::Tcp,
offset: 1,
more: true,
id: 42,
};
let packet = Ipv6Packet {
header: Ipv6Header {
version: 6,
traffic_class: 0,
flow_label: 0,
payload_length: 8,
next_header: IpNextProtocol::Tcp,
hop_limit: 1,
source: Ipv6Addr::LOCALHOST,
destination: Ipv6Addr::LOCALHOST,
},
extensions: vec![ext],
payload: Bytes::from_static(b"ABCDEFGH"),
};
let expected_len = IPV6_HEADER_LEN + 8 + 8; assert_eq!(packet.total_len(), expected_len);
assert_eq!(packet.to_bytes().len(), expected_len);
}
#[test]
fn test_extension_kind_known_variants() {
let hop = Ipv6ExtensionHeader::HopByHop {
next: IpNextProtocol::Tcp,
data: Bytes::from_static(&[1, 2, 3, 4]),
};
assert_eq!(hop.kind(), ExtensionHeaderType::HopByHop);
let dst = Ipv6ExtensionHeader::Destination {
next: IpNextProtocol::Udp,
data: Bytes::from_static(&[9, 8, 7]),
};
assert_eq!(dst.kind(), ExtensionHeaderType::Destination);
let route = Ipv6ExtensionHeader::Routing {
next: IpNextProtocol::Tcp,
routing_type: 0,
segments_left: 0,
data: Bytes::from_static(&[1, 2, 3]),
};
assert_eq!(route.kind(), ExtensionHeaderType::Routing);
let frag = Ipv6ExtensionHeader::Fragment {
next: IpNextProtocol::Udp,
offset: 0,
more: false,
id: 12345,
};
assert_eq!(frag.kind(), ExtensionHeaderType::Fragment);
}
#[test]
fn test_extension_kind_raw_known() {
let raw_routing = Ipv6ExtensionHeader::Raw {
next: IpNextProtocol::new(43),
raw: Bytes::from_static(&[43, 1, 2, 3]),
};
assert_eq!(raw_routing.kind(), ExtensionHeaderType::Routing);
let raw_frag = Ipv6ExtensionHeader::Raw {
next: IpNextProtocol::new(44),
raw: Bytes::from_static(&[44, 0, 0, 0]),
};
assert_eq!(raw_frag.kind(), ExtensionHeaderType::Fragment);
}
#[test]
fn test_extension_kind_raw_unknown() {
let raw_unknown = Ipv6ExtensionHeader::Raw {
next: IpNextProtocol::new(250),
raw: Bytes::from_static(&[250, 0, 1, 2]),
};
assert_eq!(raw_unknown.kind(), ExtensionHeaderType::Unknown(250));
}
#[test]
fn test_mutable_ipv6_packet_mutations() {
let mut raw = [
0x60, 0x00, 0x00, 0x00, 0x00, 0x04, 0x11, 0x40, 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0xde, 0xad, 0xbe, 0xef,
];
let mut packet = MutableIpv6Packet::new(&mut raw).expect("mutable ipv6");
assert_eq!(packet.get_version(), 6);
packet.set_hop_limit(0x7f);
packet.set_next_header(IpNextProtocol::Tcp);
packet.set_flow_label(0x12345);
packet.set_source(Ipv6Addr::LOCALHOST);
packet.payload_mut()[0] = 0xaa;
let frozen = packet.freeze().expect("freeze");
assert_eq!(frozen.header.hop_limit, 0x7f);
assert_eq!(frozen.header.next_header, IpNextProtocol::Tcp);
assert_eq!(frozen.header.flow_label, 0x12345);
assert_eq!(frozen.header.source, Ipv6Addr::LOCALHOST);
assert_eq!(frozen.payload[0], 0xaa);
}
#[test]
fn ipv6_try_from_buf_reports_strict_truncation() {
let raw = Bytes::from_static(&[
0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4,
]);
let err = Ipv6Packet::try_from_buf_strict(&raw).expect_err("strict parse should fail");
assert!(matches!(err, ParseError::Truncated { .. }));
assert!(Ipv6Packet::from_buf(&raw).is_some());
}
}