blob: 7676466c427c4c96db246d47c81a0ebd9239d0d5 [file] [log] [blame]
use crate::{err::ValueTooBigError, *};
/// IPv6 header according to rfc8200.
#[derive(Clone, Debug, Eq, PartialEq, Default)]
pub struct Ipv6Header {
pub traffic_class: u8,
/// If non 0 serves as a hint to router and switches with multiple outbound paths that these packets should stay on the same path, so that they will not be reordered.
pub flow_label: Ipv6FlowLabel,
///The length of the payload and extension headers in bytes (0 in case of jumbo payloads).
pub payload_length: u16,
/// IP protocol number specifying the next header or transport layer protocol.
///
/// See [IpNumber] or [ip_number] for a definitions of ids.
pub next_header: IpNumber,
/// The number of hops the packet can take before it is discarded.
pub hop_limit: u8,
/// IPv6 source address
pub source: [u8; 16],
/// IPv6 destination address
pub destination: [u8; 16],
}
impl Ipv6Header {
/// Serialized size of an IPv6 header in bytes/octets.
pub const LEN: usize = 40;
#[deprecated(since = "0.14.0", note = "Use `Ipv6Header::LEN` instead")]
pub const SERIALIZED_SIZE: usize = Ipv6Header::LEN;
/// Renamed to `Ipv6Header::from_slice`
#[deprecated(since = "0.10.1", note = "Renamed to `Ipv6Header::from_slice`")]
#[inline]
pub fn read_from_slice(
slice: &[u8],
) -> Result<(Ipv6Header, &[u8]), err::ipv6::HeaderSliceError> {
Ipv6Header::from_slice(slice)
}
/// Read an Ipv6Header from a slice and return the header & unused parts of the slice.
///
/// Note that this function DOES NOT seperate the payload based on the length
/// payload_length present in the IPv6 header. It just returns the left over slice
/// after the header.
///
/// If you want to have correctly seperated payload including the IP extension
/// headers use
///
/// * [`crate::IpHeaders::from_ipv6_slice`] (decodes all the fields of the IP headers)
/// * [`crate::Ipv6Slice::from_slice`] (just identifies the ranges in the slice where
/// the headers and payload are present)
///
/// or
///
/// * [`crate::IpHeaders::from_ipv6_slice_lax`]
/// * [`crate::Ipv6Slice::from_slice_lax`]
///
/// for a laxer version which falls back to slice length when the `payload_length`
/// contains an inconsistent value.
#[inline]
pub fn from_slice(slice: &[u8]) -> Result<(Ipv6Header, &[u8]), err::ipv6::HeaderSliceError> {
Ok((
Ipv6HeaderSlice::from_slice(slice)?.to_header(),
&slice[Ipv6Header::LEN..],
))
}
///Reads an IPv6 header from the current position.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn read<T: std::io::Read + std::io::Seek + Sized>(
reader: &mut T,
) -> Result<Ipv6Header, err::ipv6::HeaderReadError> {
use err::ipv6::{HeaderError::*, HeaderReadError::*};
let mut value: [u8; 1] = [0; 1];
reader.read_exact(&mut value).map_err(Io)?;
let version_number = value[0] >> 4;
if 6 != version_number {
return Err(Content(UnexpectedVersion { version_number }));
}
Ipv6Header::read_without_version(reader, value[0] & 0xf).map_err(Io)
}
///Reads an IPv6 header assuming the version & flow_label field have already been read.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn read_without_version<T: std::io::Read + std::io::Seek + Sized>(
reader: &mut T,
version_rest: u8,
) -> Result<Ipv6Header, std::io::Error> {
let mut buffer: [u8; 8 + 32 - 1] = [0; 8 + 32 - 1];
reader.read_exact(&mut buffer[..])?;
Ok(Ipv6Header {
traffic_class: (version_rest << 4) | (buffer[0] >> 4),
flow_label: unsafe {
// SAFETY: Safe as the bitmask & 0 contant guarantee that the value
// does not exceed 20 bytes.
Ipv6FlowLabel::new_unchecked(u32::from_be_bytes([
0,
buffer[0] & 0b0000_1111,
buffer[1],
buffer[2],
]))
},
payload_length: u16::from_be_bytes([buffer[3], buffer[4]]),
next_header: IpNumber(buffer[5]),
hop_limit: buffer[6],
#[rustfmt::skip]
source: [
buffer[7], buffer[8], buffer[9], buffer[10],
buffer[11], buffer[12], buffer[13], buffer[14],
buffer[15], buffer[16], buffer[17], buffer[18],
buffer[19], buffer[20], buffer[21], buffer[22],
],
#[rustfmt::skip]
destination: [
buffer[23], buffer[24], buffer[25], buffer[26],
buffer[27], buffer[28], buffer[29], buffer[30],
buffer[31], buffer[32], buffer[33], buffer[34],
buffer[35], buffer[36], buffer[37], buffer[38],
],
})
}
///Takes a slice and skips an ipv6 header extensions and returns the next_header ip number & the slice past the header.
pub fn skip_header_extension_in_slice(
slice: &[u8],
next_header: IpNumber,
) -> Result<(IpNumber, &[u8]), err::LenError> {
use crate::ip_number::*;
// verify that a ipv6 extension is present (before
// validating the slice length)
match next_header {
IPV6_FRAG | AUTH | IPV6_HOP_BY_HOP | IPV6_ROUTE | IPV6_DEST_OPTIONS | MOBILITY
| HIP | SHIM6 => {}
_ => {
return Ok((next_header, slice));
}
}
if slice.len() >= 2 {
//determine the length
let len = match next_header {
IPV6_FRAG => 8,
AUTH => (usize::from(slice[1]) + 2) * 4,
IPV6_HOP_BY_HOP | IPV6_ROUTE | IPV6_DEST_OPTIONS | MOBILITY | HIP | SHIM6 => {
(usize::from(slice[1]) + 1) * 8
}
// not a ipv6 header extension that can be skipped
_ => unreachable!(),
};
if slice.len() < len {
Err(err::LenError {
required_len: len,
len: slice.len(),
len_source: LenSource::Slice,
layer: err::Layer::Ipv6ExtHeader,
layer_start_offset: 0,
})
} else {
Ok((IpNumber(slice[0]), &slice[len..]))
}
} else {
Err(err::LenError {
required_len: 2,
len: slice.len(),
len_source: LenSource::Slice,
layer: err::Layer::Ipv6ExtHeader,
layer_start_offset: 0,
})
}
}
/// Returns true if the given ip protocol number is a skippable header extension.
///
/// A skippable header extension is an extension header for which it is known how
/// to determine the protocol number of the following header as well as how many
/// octets have to be skipped to reach the start of the following header.
pub fn is_skippable_header_extension(ip_protocol_number: IpNumber) -> bool {
use crate::ip_number::*;
//Note: EncapsulatingSecurityPayload & ExperimentalAndTesting0 can not be skipped
matches!(
ip_protocol_number,
IPV6_HOP_BY_HOP
| IPV6_ROUTE
| IPV6_FRAG
| AUTH
| IPV6_DEST_OPTIONS
| MOBILITY
| HIP
| SHIM6
)
}
///Takes a slice & ip protocol number (identifying the first header type) and returns next_header id & the slice past after all ipv6 header extensions.
pub fn skip_all_header_extensions_in_slice(
slice: &[u8],
next_header: IpNumber,
) -> Result<(IpNumber, &[u8]), err::LenError> {
let mut next_header = next_header;
let mut rest = slice;
let mut offset = 0;
loop {
let (n_id, n_rest) = Ipv6Header::skip_header_extension_in_slice(rest, next_header)
.map_err(|err| err.add_offset(offset))?;
offset = slice.len() - n_rest.len();
if n_rest.len() == rest.len() {
return Ok((next_header, rest));
} else {
next_header = n_id;
rest = n_rest;
}
}
}
///Skips the ipv6 header extension and returns the next ip protocol number
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn skip_header_extension<T: std::io::Read + std::io::Seek + Sized>(
reader: &mut T,
next_header: IpNumber,
) -> Result<IpNumber, std::io::Error> {
use crate::ip_number::*;
let (next_header, rest_length) = match next_header {
IPV6_FRAG => {
let mut buf = [0; 1];
reader.read_exact(&mut buf)?;
(IpNumber(buf[0]), 7)
}
AUTH => {
let mut buf = [0; 2];
reader.read_exact(&mut buf)?;
(IpNumber(buf[0]), i64::from(buf[1]) * 4 + 6)
}
IPV6_HOP_BY_HOP | IPV6_ROUTE | IPV6_DEST_OPTIONS | MOBILITY | HIP | SHIM6 => {
let mut buf = [0; 2];
reader.read_exact(&mut buf)?;
(IpNumber(buf[0]), i64::from(buf[1]) * 8 + 6)
}
// not a ipv6 header extension that can be skipped
_ => return Ok(next_header),
};
//Sadly seek does not return an error if the seek could not be fulfilled.
//Some implementations do not even truncate the returned position to the
//last valid one. std::io::Cursor for example just moves the position
//over the border of the given slice (e.g. returns position 15 even when
//the given slice contains only 1 element).
//The only option, to detect that we are in an invalid state, is to move the
//seek offset to one byte before the end and then execute a normal read to
//trigger an error.
reader.seek(std::io::SeekFrom::Current(rest_length - 1))?;
{
let mut buf = [0; 1];
reader.read_exact(&mut buf)?;
}
Ok(next_header)
}
///Skips all ipv6 header extensions and returns the next ip protocol number
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn skip_all_header_extensions<T: std::io::Read + std::io::Seek + Sized>(
reader: &mut T,
next_header: IpNumber,
) -> Result<IpNumber, std::io::Error> {
let mut next_header = next_header;
loop {
if Ipv6Header::is_skippable_header_extension(next_header) {
next_header = Ipv6Header::skip_header_extension(reader, next_header)?;
} else {
return Ok(next_header);
}
}
}
///Writes a given IPv6 header to the current position.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn write<T: std::io::Write + Sized>(&self, writer: &mut T) -> Result<(), std::io::Error> {
writer.write_all(&self.to_bytes())
}
/// Return the ipv6 source address as an std::net::Ipv6Addr
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[inline]
pub fn source_addr(&self) -> std::net::Ipv6Addr {
std::net::Ipv6Addr::from(self.source)
}
/// Return the ipv6 destination address as an std::net::Ipv6Addr
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[inline]
pub fn destination_addr(&self) -> std::net::Ipv6Addr {
std::net::Ipv6Addr::from(self.destination)
}
/// Length of the serialized header in bytes.
///
/// The function always returns the constant Ipv6Header::LEN
/// and exists to keep the methods consistent with other headers.
#[inline]
pub fn header_len(&self) -> usize {
Ipv6Header::LEN
}
/// Sets the field total_length based on the size of the payload and the options. Returns an error if the payload is too big to fit.
pub fn set_payload_length(&mut self, size: usize) -> Result<(), ValueTooBigError<usize>> {
use crate::err::ValueType;
// check that the total length fits into the field
const MAX_PAYLOAD_LENGTH: usize = u16::MAX as usize;
if MAX_PAYLOAD_LENGTH < size {
return Err(ValueTooBigError {
actual: size,
max_allowed: MAX_PAYLOAD_LENGTH,
value_type: ValueType::Ipv6PayloadLength,
});
}
self.payload_length = size as u16;
Ok(())
}
/// Returns the serialized form of the header as a statically
/// sized byte array.
#[rustfmt::skip]
pub fn to_bytes(&self) -> [u8;Ipv6Header::LEN] {
// serialize header
let flow_label_be = self.flow_label.value().to_be_bytes();
let payload_len_be = self.payload_length.to_be_bytes();
[
(6 << 4) | (self.traffic_class >> 4),
(self.traffic_class << 4) | flow_label_be[1],
flow_label_be[2],
flow_label_be[3],
payload_len_be[0],
payload_len_be[1],
self.next_header.0,
self.hop_limit,
self.source[0], self.source[1], self.source[2], self.source[3],
self.source[4], self.source[5], self.source[6], self.source[7],
self.source[8], self.source[9], self.source[10], self.source[11],
self.source[12], self.source[13], self.source[14], self.source[15],
self.destination[0], self.destination[1], self.destination[2], self.destination[3],
self.destination[4], self.destination[5], self.destination[6], self.destination[7],
self.destination[8], self.destination[9], self.destination[10], self.destination[11],
self.destination[12], self.destination[13], self.destination[14], self.destination[15],
]
}
}
#[cfg(test)]
mod test {
use crate::{
err::ipv6::HeaderError::*, err::ipv6::HeaderSliceError::*, ip_number::*, test_gens::*, *,
};
use alloc::format;
use arrayvec::ArrayVec;
use proptest::*;
use std::io::Cursor;
#[test]
fn default() {
let header: Ipv6Header = Default::default();
assert_eq!(0, header.traffic_class);
assert_eq!(0, header.flow_label.value());
assert_eq!(0, header.payload_length);
assert_eq!(255, header.next_header.0);
assert_eq!(0, header.hop_limit);
assert_eq!([0u8; 16], header.source);
assert_eq!([0u8; 16], header.destination);
}
#[test]
fn debug() {
let header: Ipv6Header = Default::default();
assert_eq!(
format!("{:?}", header),
format!(
"Ipv6Header {{ traffic_class: {}, flow_label: {:?}, payload_length: {}, next_header: {:?}, hop_limit: {}, source: {:?}, destination: {:?} }}",
header.traffic_class,
header.flow_label,
header.payload_length,
header.next_header,
header.hop_limit,
header.source,
header.destination
)
);
}
proptest! {
#[test]
fn clone_eq(header in ipv6_any()) {
assert_eq!(header.clone(), header);
}
}
proptest! {
#[test]
#[allow(deprecated)]
fn read_from_slice(
header in ipv6_any(),
bad_version in 0..=0b1111u8
) {
// ok read
{
let bytes = header.to_bytes();
let (actual, rest) = Ipv6Header::read_from_slice(&bytes).unwrap();
assert_eq!(header, actual);
assert_eq!(rest, &[]);
}
// version error
if bad_version != 6 {
let mut bytes = header.to_bytes();
// inject a bad version number
bytes[0] = (0b1111 & bytes[0]) | (bad_version << 4);
assert_eq!(
Ipv6Header::read_from_slice(&bytes).unwrap_err(),
Content(UnexpectedVersion{ version_number: bad_version })
);
}
// length error
{
let bytes = header.to_bytes();
for len in 0..bytes.len() {
assert_eq!(
Ipv6Header::read_from_slice(&bytes[..len])
.unwrap_err(),
Len(err::LenError{
required_len: Ipv6Header::LEN,
len: len,
len_source: LenSource::Slice,
layer: err::Layer::Ipv6Header,
layer_start_offset: 0,
})
);
}
}
}
}
proptest! {
#[test]
fn from_slice(
header in ipv6_any(),
bad_version in 0..=0b1111u8
) {
// ok read
{
let bytes = header.to_bytes();
let (actual, rest) = Ipv6Header::from_slice(&bytes).unwrap();
assert_eq!(header, actual);
assert_eq!(rest, &[]);
}
// version error
if bad_version != 6 {
let mut bytes = header.to_bytes();
// inject a bad version number
bytes[0] = (0b1111 & bytes[0]) | (bad_version << 4);
assert_eq!(
Ipv6Header::from_slice(&bytes).unwrap_err(),
Content(UnexpectedVersion{ version_number: bad_version })
);
}
// length error
{
let bytes = header.to_bytes();
for len in 0..bytes.len() {
assert_eq!(
Ipv6Header::from_slice(&bytes[..len])
.unwrap_err(),
Len(err::LenError{
required_len: Ipv6Header::LEN,
len: len,
len_source: LenSource::Slice,
layer: err::Layer::Ipv6Header,
layer_start_offset: 0,
})
);
}
}
}
}
proptest! {
#[test]
fn read(
header in ipv6_any(),
bad_version in 0..=0b1111u8
) {
use err::ipv6::HeaderError::*;
// ok read
{
let bytes = header.to_bytes();
let mut cursor = Cursor::new(&bytes[..]);
let actual = Ipv6Header::read(&mut cursor).unwrap();
assert_eq!(header, actual);
assert_eq!(cursor.position(), bytes.len() as u64);
}
// version error
if bad_version != 6 {
let mut bytes = header.to_bytes();
// inject a bad version number
bytes[0] = (0b1111 & bytes[0]) | (bad_version << 4);
let mut cursor = Cursor::new(&bytes[..]);
assert_eq!(
Ipv6Header::read(&mut cursor)
.unwrap_err()
.content_error()
.unwrap(),
UnexpectedVersion {
version_number: bad_version,
}
);
}
// io error
{
let bytes = header.to_bytes();
for len in 0..bytes.len() {
let mut cursor = Cursor::new(&bytes[..len]);
assert!(Ipv6Header::read(&mut cursor).is_err());
}
}
}
}
proptest! {
#[test]
fn read_without_version(header in ipv6_any()) {
// ok read
{
let bytes = header.to_bytes();
let mut cursor = Cursor::new(&bytes[1..]);
let actual = Ipv6Header::read_without_version(&mut cursor, bytes[0] & 0xf).unwrap();
assert_eq!(header, actual);
assert_eq!(cursor.position(), bytes.len() as u64 - 1);
}
// io error
{
let bytes = header.to_bytes();
for len in 1..bytes.len() {
let mut cursor = Cursor::new(&bytes[1..len]);
assert!(Ipv6Header::read_without_version(&mut cursor, bytes[0] & 0xf).is_err());
}
}
}
}
proptest! {
#[test]
fn skip_header_extension_in_slice(
generic in ipv6_raw_ext_any(),
frag in ipv6_fragment_any(),
auth in ip_auth_any()
) {
const GENERICS: [IpNumber;7] = [
IPV6_HOP_BY_HOP,
IPV6_DEST_OPTIONS,
IPV6_ROUTE,
IPV6_DEST_OPTIONS,
MOBILITY,
HIP,
SHIM6,
];
// generic headers
for g in GENERICS {
let bytes = generic.to_bytes();
// ok case
{
let (next, rest) = Ipv6Header::skip_header_extension_in_slice(&bytes, g).unwrap();
assert_eq!(next, generic.next_header);
assert_eq!(rest, &[]);
}
// length error
for len in 0..bytes.len() {
assert_eq!(
Ipv6Header::skip_header_extension_in_slice(&bytes[..len], g).unwrap_err(),
err::LenError {
required_len: if len < 2 {
2
} else {
bytes.len()
},
len: len,
len_source: LenSource::Slice,
layer: err::Layer::Ipv6ExtHeader,
layer_start_offset: 0,
}
);
}
}
// frag header
{
let bytes = frag.to_bytes();
// ok case
{
let (next, rest) = Ipv6Header::skip_header_extension_in_slice(&bytes, IPV6_FRAG).unwrap();
assert_eq!(next, frag.next_header);
assert_eq!(rest, &[]);
}
// length error
for len in 0..bytes.len() {
assert_eq!(
Ipv6Header::skip_header_extension_in_slice(&bytes[..len], IPV6_FRAG).unwrap_err(),
err::LenError {
required_len: if len < 2 {
2
} else {
bytes.len()
},
len: len,
len_source: LenSource::Slice,
layer: err::Layer::Ipv6ExtHeader,
layer_start_offset: 0,
}
);
}
}
// auth header
{
let bytes = auth.to_bytes();
// ok case
{
let (next, rest) = Ipv6Header::skip_header_extension_in_slice(&bytes, AUTH).unwrap();
assert_eq!(next, auth.next_header);
assert_eq!(rest, &[]);
}
// length error
for len in 0..bytes.len() {
assert_eq!(
Ipv6Header::skip_header_extension_in_slice(&bytes[..len], AUTH).unwrap_err(),
err::LenError {
required_len: if len < 2 {
2
} else {
bytes.len()
},
len: len,
len_source: LenSource::Slice,
layer: err::Layer::Ipv6ExtHeader,
layer_start_offset: 0,
}
);
}
}
}
}
#[test]
fn is_skippable_header_extension() {
for i in 0..0xffu8 {
let expected = match IpNumber(i) {
IPV6_HOP_BY_HOP | IPV6_ROUTE | IPV6_FRAG | AUTH | IPV6_DEST_OPTIONS | MOBILITY
| HIP | SHIM6 => true,
_ => false,
};
assert_eq!(
expected,
Ipv6Header::is_skippable_header_extension(IpNumber(i))
);
}
}
proptest! {
#[test]
fn skip_all_header_extensions_in_slice(
hop_by_hop in ipv6_raw_ext_any(),
dst_opt1 in ipv6_raw_ext_any(),
route in ipv6_raw_ext_any(),
frag in ipv6_fragment_any(),
auth in ip_auth_any(),
dst_opt2 in ipv6_raw_ext_any(),
mobility in ipv6_raw_ext_any(),
hip in ipv6_raw_ext_any(),
shim6 in ipv6_raw_ext_any()
) {
// no extension header
{
let (next, rest) = Ipv6Header::skip_all_header_extensions_in_slice(&[], UDP).unwrap();
assert_eq!(UDP, next);
assert_eq!(rest, &[]);
}
// setup a buffer with all extension headers present
let buffer = {
let mut buffer = ArrayVec::<u8, {
Ipv6RawExtHeader::MAX_LEN * 8 + IpAuthHeader::MAX_LEN
}>::new();
// based on RFC 8200 4.1. Extension Header Order
// & IANA https://www.iana.org/assignments/ipv6-parameters/ipv6-parameters.xhtml
//
// IPV6_HOP_BY_HOP,
// IPV6_DEST_OPTIONS,
// IPV6_ROUTE,
// IPV6_FRAG,
// AUTH,
// IPV6_DEST_OPTIONS,
// MOBILITY,
// HIP,
// SHIM6,
let mut hop_by_hop = hop_by_hop.clone();
hop_by_hop.next_header = IPV6_DEST_OPTIONS;
buffer.extend(hop_by_hop.to_bytes());
let mut dst_opt1 = dst_opt1.clone();
dst_opt1.next_header = IPV6_ROUTE;
buffer.extend(dst_opt1.to_bytes());
let mut route = route.clone();
route.next_header = IPV6_FRAG;
buffer.extend(route.to_bytes());
let mut frag = frag.clone();
frag.next_header = AUTH;
buffer.extend(frag.to_bytes());
let mut auth = auth.clone();
auth.next_header = IPV6_DEST_OPTIONS;
buffer.extend(auth.to_bytes());
let mut dst_opt2 = dst_opt2.clone();
dst_opt2.next_header = MOBILITY;
buffer.extend(dst_opt2.to_bytes());
let mut mobility = mobility.clone();
mobility.next_header = HIP;
buffer.extend(mobility.to_bytes());
let mut hip = hip.clone();
hip.next_header = SHIM6;
buffer.extend(hip.to_bytes());
let mut shim6 = shim6.clone();
shim6.next_header = TCP;
buffer.extend(shim6.to_bytes());
buffer
};
// ok skip case with all extension headers
{
let (next, rest) = Ipv6Header::skip_all_header_extensions_in_slice(&buffer, IPV6_HOP_BY_HOP).unwrap();
assert_eq!(next, TCP);
assert_eq!(rest, &[]);
}
// length error
{
let len_ranges: [usize;9] = [
hop_by_hop.header_len(),
dst_opt1.header_len(),
route.header_len(),
frag.header_len(),
auth.header_len(),
dst_opt2.header_len(),
mobility.header_len(),
hip.header_len(),
shim6.header_len()
];
let get_expected = |len: usize| -> usize{
let mut curr = 0;
for next in &len_ranges {
if len < curr {
break;
}
if len < curr + 2 {
curr += 2;
break;
}
curr += next;
}
curr
};
let get_offset = |len: usize| -> usize{
let mut curr = 0;
for next in &len_ranges {
if len < curr + next {
break;
}
curr += next;
}
curr
};
for len in 0..buffer.len() {
assert_eq!(
Ipv6Header::skip_all_header_extensions_in_slice(&buffer[..len], IPV6_HOP_BY_HOP)
.unwrap_err(),
err::LenError {
required_len: get_expected(len) - get_offset(len),
len: len - get_offset(len),
len_source: LenSource::Slice,
layer: err::Layer::Ipv6ExtHeader,
layer_start_offset: get_offset(len),
}
);
}
}
}
}
#[test]
fn skip_header_extension() {
use crate::ip_number::*;
{
let buffer: [u8; 8] = [0; 8];
let mut cursor = Cursor::new(&buffer);
assert_eq!(
Ipv6Header::skip_header_extension(&mut cursor, ICMP).unwrap(),
ICMP
);
assert_eq!(0, cursor.position());
}
{
let buffer: [u8; 8] = [0; 8];
let mut cursor = Cursor::new(&buffer);
assert_eq!(
Ipv6Header::skip_header_extension(&mut cursor, IPV6_HOP_BY_HOP).unwrap(),
IpNumber(0)
);
assert_eq!(8, cursor.position());
}
{
#[rustfmt::skip]
let buffer: [u8; 8 * 3] = [
4, 2, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];
let mut cursor = Cursor::new(&buffer);
assert_eq!(
Ipv6Header::skip_header_extension(&mut cursor, IPV6_ROUTE).unwrap(),
IpNumber(4)
);
assert_eq!(8 * 3, cursor.position());
}
{
//fragmentation header has a fixed size -> the 2 should be ignored
#[rustfmt::skip]
let buffer: [u8; 8 * 3] = [
4, 2, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];
let mut cursor = Cursor::new(&buffer);
assert_eq!(
Ipv6Header::skip_header_extension(&mut cursor, IPV6_FRAG).unwrap(),
IpNumber(4)
);
assert_eq!(8, cursor.position());
}
}
proptest! {
#[test]
fn skip_all_header_extensions(
hop_by_hop in ipv6_raw_ext_any(),
dst_opt1 in ipv6_raw_ext_any(),
route in ipv6_raw_ext_any(),
frag in ipv6_fragment_any(),
auth in ip_auth_any(),
dst_opt2 in ipv6_raw_ext_any(),
mobility in ipv6_raw_ext_any(),
hip in ipv6_raw_ext_any(),
shim6 in ipv6_raw_ext_any()
) {
// no extension header
{
let mut cursor = Cursor::new(&[]);
let next = Ipv6Header::skip_all_header_extensions(&mut cursor, UDP).unwrap();
assert_eq!(UDP, next);
assert_eq!(0, cursor.position());
}
// setup a buffer with all extension headers present
let buffer = {
let mut buffer = ArrayVec::<u8, {
Ipv6RawExtHeader::MAX_LEN * 8 + IpAuthHeader::MAX_LEN
}>::new();
// based on RFC 8200 4.1. Extension Header Order
// & IANA https://www.iana.org/assignments/ipv6-parameters/ipv6-parameters.xhtml
//
// IPV6_HOP_BY_HOP,
// IPV6_DEST_OPTIONS,
// IPV6_ROUTE,
// IPV6_FRAG,
// AUTH,
// IPV6_DEST_OPTIONS,
// MOBILITY,
// HIP,
// SHIM6,
let mut hop_by_hop = hop_by_hop.clone();
hop_by_hop.next_header = IPV6_DEST_OPTIONS;
buffer.extend(hop_by_hop.to_bytes());
let mut dst_opt1 = dst_opt1.clone();
dst_opt1.next_header = IPV6_ROUTE;
buffer.extend(dst_opt1.to_bytes());
let mut route = route.clone();
route.next_header = IPV6_FRAG;
buffer.extend(route.to_bytes());
let mut frag = frag.clone();
frag.next_header = AUTH;
buffer.extend(frag.to_bytes());
let mut auth = auth.clone();
auth.next_header = IPV6_DEST_OPTIONS;
buffer.extend(auth.to_bytes());
let mut dst_opt2 = dst_opt2.clone();
dst_opt2.next_header = MOBILITY;
buffer.extend(dst_opt2.to_bytes());
let mut mobility = mobility.clone();
mobility.next_header = HIP;
buffer.extend(mobility.to_bytes());
let mut hip = hip.clone();
hip.next_header = SHIM6;
buffer.extend(hip.to_bytes());
let mut shim6 = shim6.clone();
shim6.next_header = TCP;
buffer.extend(shim6.to_bytes());
buffer
};
// ok skip case with all extension headers
{
let mut cursor = Cursor::new(&buffer);
let last = Ipv6Header::skip_all_header_extensions(&mut cursor, IPV6_HOP_BY_HOP).unwrap();
assert_eq!(last, TCP);
assert_eq!(cursor.position(), buffer.len() as u64);
}
// length error
for len in 0..buffer.len() {
let mut cursor = Cursor::new(&buffer[..len]);
assert!(
Ipv6Header::skip_all_header_extensions(&mut cursor, IPV6_HOP_BY_HOP)
.is_err()
);
}
}
}
proptest! {
#[test]
fn write(header in ipv6_any()) {
let mut buffer = [0u8;Ipv6Header::LEN];
let len = {
let mut cursor = Cursor::new(&mut buffer[..]);
header.write(&mut cursor).unwrap();
cursor.position() as usize
};
assert_eq!(len, header.header_len());
assert_eq!(
Ipv6Header::from_slice(&buffer[..len]).unwrap().0,
header
);
}
}
proptest! {
#[test]
fn source_addr(header in ipv6_any()) {
assert_eq!(
header.source_addr().octets(),
header.source
);
}
}
proptest! {
#[test]
fn destination_addr(header in ipv6_any()) {
assert_eq!(
header.destination_addr().octets(),
header.destination
);
}
}
proptest! {
#[test]
fn to_bytes(header in ipv6_any()) {
let bytes = header.to_bytes();
assert_eq!(
Ipv6Header::from_slice(&bytes).unwrap().0,
header
);
}
}
}