use crate::*; use core::slice::from_raw_parts; /// Slice containing an IPv6 fragment header. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Ipv6FragmentHeaderSlice<'a> { /// Slice containing the packet data. slice: &'a [u8], } impl<'a> Ipv6FragmentHeaderSlice<'a> { /// Creates a hop by hop header slice from a slice. pub fn from_slice(slice: &'a [u8]) -> Result, err::LenError> { // the fragmentation header has the exact size of 8 bytes if slice.len() < 8 { Err(err::LenError { required_len: 8, len: slice.len(), len_source: LenSource::Slice, layer: err::Layer::Ipv6FragHeader, layer_start_offset: 0, }) } else { Ok(Ipv6FragmentHeaderSlice { // SAFETY: // Safe as slice length is checked to be at least 8 before this // code can be reached. slice: unsafe { from_raw_parts(slice.as_ptr(), 8) }, }) } } /// Creates a hop by hop header slice from a slice (assumes slice size & content was validated before). /// /// # Safety /// /// This function assumes that the passed slice has at least the length /// of 8. If a slice with length less then 8 is passed to this function /// the behavior will be undefined. pub unsafe fn from_slice_unchecked(slice: &'a [u8]) -> Ipv6FragmentHeaderSlice<'a> { debug_assert!(slice.len() >= Ipv6FragmentHeader::LEN); // the fragmentation header has the exact size of 8 bytes Ipv6FragmentHeaderSlice { slice: from_raw_parts(slice.as_ptr(), Ipv6FragmentHeader::LEN), } } /// Returns the slice containing the ipv6 fragment header. #[inline] pub fn slice(&self) -> &'a [u8] { self.slice } /// Returns the IP protocol number of the next header. /// /// See [IpNumber] or [ip_number] for a definition of the known values. #[inline] pub fn next_header(&self) -> IpNumber { // SAFETY: // Slice size checked to be at least 8 bytes in constructor. IpNumber(unsafe { *self.slice.get_unchecked(0) }) } /// Fragment offset #[inline] pub fn fragment_offset(&self) -> IpFragOffset { unsafe { // SAFETY: Safe as the resulting number is guaranteed to be only // 13 bit long. IpFragOffset::new_unchecked(u16::from_be_bytes([ // SAFETY: // Slice size checked to be at least 8 bytes in constructor. (*self.slice.get_unchecked(2) >> 3) & 0b0001_1111u8, ((*self.slice.get_unchecked(2) << 5) & 0b1110_0000u8) | (*self.slice.get_unchecked(3) & 0b0001_1111u8), ])) } } /// True if more fragment packets will follow. False if this is the last packet. #[inline] pub fn more_fragments(&self) -> bool { // SAFETY: // Slice size checked to be at least 8 bytes in constructor. unsafe { 0 != *self.slice.get_unchecked(3) & 0b1000_0000u8 } } /// Identifcation value generated by the source pub fn identification(&self) -> u32 { // SAFETY: // Slice size checked to be at least 8 bytes in constructor. unsafe { get_unchecked_be_u32(self.slice.as_ptr().add(4)) } } /// 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::Ipv6FragmentHeaderSlice; /// /// { /// let slice = Ipv6FragmentHeaderSlice::from_slice(&[ /// 0, 0, 0, 0, // offset 0 & more_fragments not set /// 1, 2, 3, 4, /// ]).unwrap(); /// assert!(false == slice.is_fragmenting_payload()); /// } /// /// { /// let slice = Ipv6FragmentHeaderSlice::from_slice(&[ /// 0, 0, 0, 0b1000_0000u8, // more_fragments set /// 1, 2, 3, 4, /// ]).unwrap(); /// assert!(slice.is_fragmenting_payload()); /// } /// /// { /// let slice = Ipv6FragmentHeaderSlice::from_slice(&[ /// 0, 0, 1, 0, // non zero offset /// 1, 2, 3, 4, /// ]).unwrap(); /// assert!(slice.is_fragmenting_payload()); /// } /// ``` #[inline] pub fn is_fragmenting_payload(&self) -> bool { // SAFETY: // Slice size checked to be at least 8 bytes in constructor. unsafe { 0 != *self.slice.get_unchecked(2) || 0 != (*self.slice.get_unchecked(3) & 0b1001_1111u8) // exclude the reserved bytes } } /// Decode some of the fields and copy the results to a /// Ipv6FragmentHeader struct. pub fn to_header(&self) -> Ipv6FragmentHeader { Ipv6FragmentHeader { next_header: self.next_header(), fragment_offset: self.fragment_offset(), more_fragments: self.more_fragments(), identification: self.identification(), } } } #[cfg(test)] mod test { use crate::{test_gens::*, *}; use alloc::{format, vec::Vec}; use proptest::prelude::*; proptest! { #[test] fn debug(input in ipv6_fragment_any()) { let bytes = input.to_bytes(); let slice = Ipv6FragmentHeaderSlice::from_slice( &bytes ).unwrap(); assert_eq!( &format!( "Ipv6FragmentHeaderSlice {{ slice: {:?} }}", slice.slice() ), &format!("{:?}", slice) ); } } proptest! { #[test] fn clone_eq(input in ipv6_fragment_any()) { let bytes = input.to_bytes(); let slice = Ipv6FragmentHeaderSlice::from_slice( &bytes ).unwrap(); assert_eq!(slice, slice.clone()); } } 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 slice = Ipv6FragmentHeaderSlice::from_slice(&buffer[..]).unwrap(); assert_eq!(slice.slice(), &buffer[..8]); } // call with not enough data in the slice for len in 0..Ipv6FragmentHeader::LEN { assert_eq!( Ipv6FragmentHeaderSlice::from_slice(&buffer[0..len]).unwrap_err(), err::LenError{ required_len: 8, len: len, len_source: LenSource::Slice, layer: err::Layer::Ipv6FragHeader, layer_start_offset: 0, } ); } } } proptest! { #[test] fn from_slice_unchecked( 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 unsafe { let slice = Ipv6FragmentHeaderSlice::from_slice_unchecked(&buffer[..]); assert_eq!(slice.slice(), &buffer[..8]); } } } proptest! { #[test] fn getters(input in ipv6_fragment_any()) { let buffer = input.to_bytes(); let slice = Ipv6FragmentHeaderSlice::from_slice(&buffer[..]).unwrap(); assert_eq!(input.next_header, slice.next_header()); assert_eq!(input.fragment_offset, slice.fragment_offset()); assert_eq!(input.more_fragments, slice.more_fragments()); assert_eq!(input.identification, slice.identification()); } } 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 }; // slice let buffer = header.to_bytes(); let slice = Ipv6FragmentHeaderSlice::from_slice(&buffer).unwrap(); assert!(false == slice.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 }; // slice let buffer = header.to_bytes(); let slice = Ipv6FragmentHeaderSlice::from_slice(&buffer).unwrap(); assert!(slice.is_fragmenting_payload()); } // positive case (more fragments) { let header = Ipv6FragmentHeader { next_header, fragment_offset: 0.try_into().unwrap(), more_fragments: true, identification }; // slice let buffer = header.to_bytes(); let slice = Ipv6FragmentHeaderSlice::from_slice(&buffer).unwrap(); assert!(slice.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 }; // slice let buffer = header.to_bytes(); let slice = Ipv6FragmentHeaderSlice::from_slice(&buffer).unwrap(); assert!(slice.is_fragmenting_payload()); } } } proptest! { #[test] fn to_header(input in ipv6_fragment_any()) { let buffer = input.to_bytes(); let slice = Ipv6FragmentHeaderSlice::from_slice(&buffer).unwrap(); assert_eq!(input, slice.to_header()); } } }