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( reader: &mut T, ) -> Result { 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( reader: &mut crate::io::LimitedReader, ) -> Result { 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(&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::(), identification in any::(), ) { 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::(), 0..20) ) { // serialize let mut buffer: Vec = 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::(), 0..20) ) { use std::io::ErrorKind; // serialize let mut buffer: Vec = 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::(), 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], ] ); } } } }