blob: 4ab7486dd42f77a4365ed492a994a42c99949c54 [file] [log] [blame]
use super::super::*;
/// IPv6 fragment header.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Ipv6FragmentHeader {
/// IP protocol number specifying the next header or transport layer protocol.
///
/// See [IpNumber] or [ip_number] for a definition of the known values.
pub next_header: IpNumber,
/// Offset of the current IP payload relative to the start of the fragmented
/// packet payload.
pub fragment_offset: IpFragOffset,
/// True if more fragment packets will follow. False if this is the last packet.
pub more_fragments: bool,
/// Identifcation value generated by the source.
pub identification: u32,
}
impl Ipv6FragmentHeader {
/// Length of the serialized header.
pub const LEN: usize = 8;
/// Create a new fragmentation header with the given parameters.
///
/// Note that the `fragment_offset` can only support values between 0 and 0x1fff (inclusive).
pub const fn new(
next_header: IpNumber,
fragment_offset: IpFragOffset,
more_fragments: bool,
identification: u32,
) -> Ipv6FragmentHeader {
Ipv6FragmentHeader {
next_header,
fragment_offset,
more_fragments,
identification,
}
}
/// Read an Ipv6FragmentHeader from a slice and return the header & unused parts of the slice.
pub fn from_slice(slice: &[u8]) -> Result<(Ipv6FragmentHeader, &[u8]), err::LenError> {
let s = Ipv6FragmentHeaderSlice::from_slice(slice)?;
let rest = &slice[8..];
let header = s.to_header();
Ok((header, rest))
}
/// Read an fragment header from the current reader 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<Ipv6FragmentHeader, std::io::Error> {
let buffer = {
let mut buffer: [u8; 8] = [0; 8];
reader.read_exact(&mut buffer)?;
buffer
};
Ok(Ipv6FragmentHeader {
next_header: IpNumber(buffer[0]),
fragment_offset: unsafe {
// SAFE as the resulting number is guaranteed to have at most
// 13 bits.
IpFragOffset::new_unchecked(u16::from_be_bytes([
(buffer[2] >> 3) & 0b0001_1111u8,
((buffer[2] << 5) & 0b1110_0000u8) | (buffer[3] & 0b0001_1111u8),
]))
},
more_fragments: 0 != buffer[3] & 0b1000_0000u8,
identification: u32::from_be_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]),
})
}
/// Read an fragment header from the current reader position.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn read_limited<T: std::io::Read + std::io::Seek + Sized>(
reader: &mut crate::io::LimitedReader<T>,
) -> Result<Ipv6FragmentHeader, crate::err::io::LimitedReadError> {
use err::Layer;
// set layer so errors contain the correct layer & offset
reader.start_layer(Layer::Ipv6FragHeader);
let buffer = {
let mut buffer: [u8; 8] = [0; 8];
reader.read_exact(&mut buffer)?;
buffer
};
Ok(Ipv6FragmentHeader {
next_header: IpNumber(buffer[0]),
fragment_offset: unsafe {
// SAFE as the resulting number is guaranteed to have at most
// 13 bits.
IpFragOffset::new_unchecked(u16::from_be_bytes([
(buffer[2] >> 3) & 0b0001_1111u8,
((buffer[2] << 5) & 0b1110_0000u8) | (buffer[3] & 0b0001_1111u8),
]))
},
more_fragments: 0 != buffer[3] & 0b1000_0000u8,
identification: u32::from_be_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]),
})
}
/// Writes a given IPv6 fragment 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())
}
/// Length of the header in bytes.
#[inline]
pub fn header_len(&self) -> usize {
Ipv6FragmentHeader::LEN
}
/// Checks if the fragment header actually fragments the packet.
///
/// Returns false if the fragment offset is 0 and the more flag
/// is not set. Otherwise returns true.
///
/// [RFC8200](https://datatracker.ietf.org/doc/html/rfc8200) explicitly
/// states that fragment headers that don't fragment the packet payload are
/// allowed. See the following quote from
/// RFC8200 page 32:
///
/// > Revised the text to handle the case of fragments that are whole
/// > datagrams (i.e., both the Fragment Offset field and the M flag
/// > are zero). If received, they should be processed as a
/// > reassembled packet. Any other fragments that match should be
/// > processed independently. The Fragment creation process was
/// > modified to not create whole datagram fragments (Fragment
/// > Offset field and the M flag are zero). See
/// > [RFC6946](https://datatracker.ietf.org/doc/html/6946) and
/// > [RFC8021](https://datatracker.ietf.org/doc/html/rfc8021) for more
/// > information."
///
/// ```
/// use etherparse::{Ipv6FragmentHeader, ip_number::UDP};
///
/// // offset 0 & no more fragments result in an unfragmented payload
/// {
/// let header = Ipv6FragmentHeader::new(UDP, 0.try_into().unwrap(), false, 123);
/// assert!(false == header.is_fragmenting_payload());
/// }
///
/// // offset 0 & but more fragments will come -> fragmented
/// {
/// let header = Ipv6FragmentHeader::new(UDP, 0.try_into().unwrap(), true, 123);
/// assert!(header.is_fragmenting_payload());
/// }
///
/// // offset non zero & no more fragments will come -> fragmented
/// {
/// let header = Ipv6FragmentHeader::new(UDP, 1.try_into().unwrap(), false, 123);
/// assert!(header.is_fragmenting_payload());
/// }
/// ```
#[inline]
pub fn is_fragmenting_payload(&self) -> bool {
self.more_fragments || (0 != self.fragment_offset.value())
}
/// Returns the serialized form of the header as a statically
/// sized byte array.
#[inline]
pub fn to_bytes(&self) -> [u8; 8] {
let fo_be: [u8; 2] = self.fragment_offset.value().to_be_bytes();
let id_be = self.identification.to_be_bytes();
[
self.next_header.0,
0,
(((fo_be[0] << 3) & 0b1111_1000u8) | ((fo_be[1] >> 5) & 0b0000_0111u8)),
((fo_be[1] & 0b0001_1111u8)
| if self.more_fragments {
0b1000_0000u8
} else {
0
}),
id_be[0],
id_be[1],
id_be[2],
id_be[3],
]
}
}
#[cfg(test)]
mod test {
use crate::{test_gens::*, *};
use alloc::{format, vec::Vec};
use proptest::prelude::*;
use std::io::Cursor;
proptest! {
#[test]
fn debug(input in ipv6_fragment_any()) {
assert_eq!(
&format!(
"Ipv6FragmentHeader {{ next_header: {:?}, fragment_offset: {:?}, more_fragments: {}, identification: {} }}",
input.next_header,
input.fragment_offset,
input.more_fragments,
input.identification
),
&format!("{:?}", input)
);
}
}
proptest! {
#[test]
fn clone_eq(input in ipv6_fragment_any()) {
assert_eq!(input, input.clone());
}
}
proptest! {
#[test]
fn new(
next_header in ip_number_any(),
fragment_offset in 0..IpFragOffset::MAX_U16,
more_fragments in any::<bool>(),
identification in any::<u32>(),
) {
let a = Ipv6FragmentHeader::new(
next_header,
fragment_offset.try_into().unwrap(),
more_fragments,
identification
);
assert_eq!(next_header, a.next_header);
assert_eq!(fragment_offset, a.fragment_offset.value());
assert_eq!(more_fragments, a.more_fragments);
assert_eq!(identification, a.identification);
}
}
proptest! {
#[test]
fn from_slice(
input in ipv6_fragment_any(),
dummy_data in proptest::collection::vec(any::<u8>(), 0..20)
) {
// serialize
let mut buffer: Vec<u8> = Vec::with_capacity(8 + dummy_data.len());
input.write(&mut buffer).unwrap();
buffer.extend(&dummy_data[..]);
// calls with a valid result
{
let (result, rest) = Ipv6FragmentHeader::from_slice(&buffer[..]).unwrap();
assert_eq!(input, result);
assert_eq!(&buffer[8..], rest);
}
// call with not enough data in the slice
for len in 0..Ipv6FragmentHeader::LEN {
assert_eq!(
Ipv6FragmentHeader::from_slice(&buffer[0..len]).unwrap_err(),
err::LenError{
required_len: Ipv6FragmentHeader::LEN,
len: len,
len_source: LenSource::Slice,
layer: err::Layer::Ipv6FragHeader,
layer_start_offset: 0,
}
);
}
}
}
proptest! {
#[test]
fn read(
input in ipv6_fragment_any(),
dummy_data in proptest::collection::vec(any::<u8>(), 0..20)
) {
use std::io::ErrorKind;
// serialize
let mut buffer: Vec<u8> = Vec::with_capacity(8 + dummy_data.len());
input.write(&mut buffer).unwrap();
buffer.extend(&dummy_data[..]);
// calls with a valid result
{
let mut cursor = Cursor::new(&buffer);
let result = Ipv6FragmentHeader::read(&mut cursor).unwrap();
assert_eq!(input, result);
assert_eq!(cursor.position(), 8);
}
// call with not enough data in the slice
for len in 0..Ipv6FragmentHeader::LEN {
let mut cursor = Cursor::new(&buffer[0..len]);
assert_eq!(
Ipv6FragmentHeader::read(&mut cursor)
.unwrap_err()
.kind(),
ErrorKind::UnexpectedEof
);
}
}
}
proptest! {
#[test]
fn write(input in ipv6_fragment_any()) {
// normal write
{
let mut buffer = Vec::with_capacity(8);
input.write(&mut buffer).unwrap();
assert_eq!(
&buffer,
&input.to_bytes()
);
}
// not enough memory for write
for len in 0..Ipv6FragmentHeader::LEN {
let mut buffer = [0u8;Ipv6FragmentHeader::LEN];
let mut cursor = Cursor::new(&mut buffer[..len]);
assert!(
input.write(&mut cursor).is_err()
);
}
}
}
proptest! {
#[test]
fn header_len(input in ipv6_fragment_any()) {
assert_eq!(8, input.header_len());
}
}
proptest! {
#[test]
fn is_fragmenting_payload(
non_zero_offset in 1u16..0b0001_1111_1111_1111u16,
identification in any::<u32>(),
next_header in ip_number_any(),
) {
// negative case
{
let header = Ipv6FragmentHeader {
next_header,
fragment_offset: 0.try_into().unwrap(),
more_fragments: false,
identification
};
assert!(false == header.is_fragmenting_payload());
}
// positive case (non zero offset)
{
let header = Ipv6FragmentHeader {
next_header,
fragment_offset: non_zero_offset.try_into().unwrap(),
more_fragments: false,
identification
};
assert!(header.is_fragmenting_payload());
}
// positive case (more fragments)
{
let header = Ipv6FragmentHeader {
next_header,
fragment_offset: 0.try_into().unwrap(),
more_fragments: true,
identification
};
assert!(header.is_fragmenting_payload());
}
// positive case (non zero offset & more fragments)
{
let header = Ipv6FragmentHeader {
next_header,
fragment_offset: non_zero_offset.try_into().unwrap(),
more_fragments: true,
identification
};
assert!(header.is_fragmenting_payload());
}
}
}
proptest! {
#[test]
fn to_bytes(input in ipv6_fragment_any()) {
// normal write
{
let fragment_offset_be = input.fragment_offset.value().to_be_bytes();
let id_be = input.identification.to_be_bytes();
assert_eq!(
&input.to_bytes(),
&[
input.next_header.0,
0,
(
(fragment_offset_be[0] << 3 & 0b1111_1000u8) |
(fragment_offset_be[1] >> 5 & 0b0000_0111u8)
),
(
(fragment_offset_be[1] & 0b0001_1111u8) |
if input.more_fragments {
0b1000_0000u8
} else {
0u8
}
),
id_be[0],
id_be[1],
id_be[2],
id_be[3],
]
);
}
}
}
}