From 5ba0b0d756d37dc1c63604e3042fdc574e05d7d4 Mon Sep 17 00:00:00 2001 From: Adam Michalik Date: Sat, 21 Feb 2026 23:01:55 -0500 Subject: [PATCH] net: add IpHeadersSlice and IpSlice::header() Introduce IpHeadersSlice as an enum over Ipv4HeaderSlice and Ipv6HeaderSlice, with convenience accessors and conversion to IpHeaders. Add IpSlice::header() to expose base IP header slices, export the new net module, and extend tests with unit and proptest coverage for conversion consistency and round-trip invariants. --- etherparse/src/net/ip_headers_slice.rs | 331 ++++++++++++++++++++++++ etherparse/src/net/ip_slice.rs | 334 +++++++++++++++++++++++++ etherparse/src/net/mod.rs | 3 + 3 files changed, 668 insertions(+) create mode 100644 etherparse/src/net/ip_headers_slice.rs diff --git a/etherparse/src/net/ip_headers_slice.rs b/etherparse/src/net/ip_headers_slice.rs new file mode 100644 index 00000000..3367fb01 --- /dev/null +++ b/etherparse/src/net/ip_headers_slice.rs @@ -0,0 +1,331 @@ +use crate::*; + +/// Slice containing an IPv4 or IPv6 base header plus extension headers. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum IpHeadersSlice<'a> { + /// IPv4 base header and extension headers. + Ipv4(Ipv4HeaderSlice<'a>, Ipv4ExtensionsSlice<'a>), + /// IPv6 base header and extension headers. + Ipv6(Ipv6HeaderSlice<'a>, Ipv6ExtensionsSlice<'a>), +} + +impl<'a> IpHeadersSlice<'a> { + /// Returns true if the slice contains an IPv4 header. + #[inline] + pub fn is_ipv4(&self) -> bool { + matches!(self, IpHeadersSlice::Ipv4(_, _)) + } + + /// Returns true if the slice contains an IPv6 header. + #[inline] + pub fn is_ipv6(&self) -> bool { + matches!(self, IpHeadersSlice::Ipv6(_, _)) + } + + /// Returns the IPv4 header slice if `self` contains one. + #[inline] + pub fn ipv4(&self) -> Option> { + if let IpHeadersSlice::Ipv4(v, _) = self { + Some(*v) + } else { + None + } + } + + /// Returns the IPv4 extension header slices if `self` contains one. + #[inline] + pub fn ipv4_exts(&self) -> Option> { + if let IpHeadersSlice::Ipv4(_, v) = self { + Some(*v) + } else { + None + } + } + + /// Returns the IPv6 header slice if `self` contains one. + #[inline] + pub fn ipv6(&self) -> Option> { + if let IpHeadersSlice::Ipv6(v, _) = self { + Some(*v) + } else { + None + } + } + + /// Returns the IPv6 extension header slices if `self` contains one. + #[inline] + pub fn ipv6_exts(&self) -> Option<&Ipv6ExtensionsSlice<'a>> { + if let IpHeadersSlice::Ipv6(_, v) = self { + Some(v) + } else { + None + } + } + + /// Returns the underlying base-header slice. + #[inline] + pub fn slice(&self) -> &'a [u8] { + match self { + IpHeadersSlice::Ipv4(v, _) => v.slice(), + IpHeadersSlice::Ipv6(v, _) => v.slice(), + } + } + + /// Returns the source IP address. + #[inline] + pub fn source_addr(&self) -> core::net::IpAddr { + match self { + IpHeadersSlice::Ipv4(v, _) => v.source_addr().into(), + IpHeadersSlice::Ipv6(v, _) => v.source_addr().into(), + } + } + + /// Returns the destination IP address. + #[inline] + pub fn destination_addr(&self) -> core::net::IpAddr { + match self { + IpHeadersSlice::Ipv4(v, _) => v.destination_addr().into(), + IpHeadersSlice::Ipv6(v, _) => v.destination_addr().into(), + } + } + + /// Returns the protocol number stored in the base header. + /// + /// For IPv4 this is the `protocol` field and for IPv6 this is + /// the `next_header` field. + #[inline] + pub fn next_header(&self) -> IpNumber { + match self { + IpHeadersSlice::Ipv4(v, _) => v.protocol(), + IpHeadersSlice::Ipv6(v, _) => v.next_header(), + } + } + + /// Returns the payload IP number after extension headers. + #[inline] + pub fn payload_ip_number(&self) -> IpNumber { + match self { + IpHeadersSlice::Ipv4(v, exts) => { + exts.auth.map(|a| a.next_header()).unwrap_or(v.protocol()) + } + IpHeadersSlice::Ipv6(v, exts) => { + let (_, payload_ip_number, _, _) = + Ipv6Extensions::from_slice_lax(v.next_header(), exts.slice()); + payload_ip_number + } + } + } + + /// Returns the IP version (4 or 6). + #[inline] + pub fn version(&self) -> u8 { + match self { + IpHeadersSlice::Ipv4(v, _) => v.version(), + IpHeadersSlice::Ipv6(v, _) => v.version(), + } + } + + /// Returns the serialized header length in bytes, including extensions. + #[inline] + pub fn header_len(&self) -> usize { + match self { + IpHeadersSlice::Ipv4(v, exts) => { + v.slice().len() + exts.auth.map(|v| v.slice().len()).unwrap_or(0) + } + IpHeadersSlice::Ipv6(v, exts) => v.slice().len() + exts.slice().len(), + } + } + + /// Converts this sliced header representation into [`IpHeaders`]. + /// + /// For IPv6 this conversion uses [`Ipv6Extensions::from_slice`]. + /// + /// Note that [`Ipv6Extensions`] can only represent a subset of valid IPv6 + /// extension chains. If more extension headers are present than can be + /// represented, only the representable subset is converted. + #[inline] + pub fn try_to_header(&self) -> Result { + match self { + IpHeadersSlice::Ipv4(v, exts) => Ok(IpHeaders::Ipv4(v.to_header(), exts.to_header())), + IpHeadersSlice::Ipv6(v, exts) => { + let (exts, _, _) = Ipv6Extensions::from_slice(v.next_header(), exts.slice())?; + Ok(IpHeaders::Ipv6(v.to_header(), exts)) + } + } + } +} + +impl<'a> From> for IpHeadersSlice<'a> { + #[inline] + fn from(value: Ipv4HeaderSlice<'a>) -> Self { + Self::Ipv4(value, Default::default()) + } +} + +impl<'a> From> for IpHeadersSlice<'a> { + #[inline] + fn from(value: Ipv6HeaderSlice<'a>) -> Self { + Self::Ipv6(value, Default::default()) + } +} + +impl<'a> From<(Ipv4HeaderSlice<'a>, Ipv4ExtensionsSlice<'a>)> for IpHeadersSlice<'a> { + #[inline] + fn from(value: (Ipv4HeaderSlice<'a>, Ipv4ExtensionsSlice<'a>)) -> Self { + Self::Ipv4(value.0, value.1) + } +} + +impl<'a> From<(Ipv6HeaderSlice<'a>, Ipv6ExtensionsSlice<'a>)> for IpHeadersSlice<'a> { + #[inline] + fn from(value: (Ipv6HeaderSlice<'a>, Ipv6ExtensionsSlice<'a>)) -> Self { + Self::Ipv6(value.0, value.1) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::vec::Vec; + use core::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + + #[test] + fn is_ipv4_ipv6_and_accessors() { + // ipv4 with auth extension + { + let h = Ipv4Header { + protocol: ip_number::AUTH, + source: [1, 2, 3, 4], + destination: [5, 6, 7, 8], + ..Default::default() + }; + let header_bytes = h.to_bytes(); + let auth = IpAuthHeader::new(ip_number::UDP, 7, 9, &[1, 2, 3, 4]).unwrap(); + let auth_bytes = auth.to_bytes(); + let exts = Ipv4ExtensionsSlice::from_slice(ip_number::AUTH, &auth_bytes) + .unwrap() + .0; + let s = IpHeadersSlice::Ipv4(Ipv4HeaderSlice::from_slice(&header_bytes).unwrap(), exts); + assert!(s.is_ipv4()); + assert_eq!(false, s.is_ipv6()); + assert!(s.ipv4().is_some()); + assert!(s.ipv6().is_none()); + assert!(s.ipv4_exts().is_some()); + assert!(s.ipv6_exts().is_none()); + assert_eq!(s.slice(), header_bytes.as_slice()); + assert_eq!( + s.ipv4_exts().unwrap().auth.unwrap().slice(), + auth_bytes.as_slice() + ); + assert_eq!(s.next_header(), ip_number::AUTH); + assert_eq!(s.payload_ip_number(), ip_number::UDP); + assert_eq!(s.version(), 4); + assert_eq!(s.header_len(), Ipv4Header::MIN_LEN + auth.header_len()); + assert_eq!(s.source_addr(), IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4))); + assert_eq!(s.destination_addr(), IpAddr::V4(Ipv4Addr::new(5, 6, 7, 8))); + } + + // ipv6 with fragment extension + { + let h = Ipv6Header { + next_header: ip_number::IPV6_FRAG, + source: [1; 16], + destination: [2; 16], + ..Default::default() + }; + let header_bytes = h.to_bytes(); + let fragment = Ipv6FragmentHeader::new(ip_number::TCP, IpFragOffset::ZERO, false, 1234); + let mut ext_bytes = Vec::new(); + ext_bytes.extend_from_slice(&fragment.to_bytes()); + let exts = Ipv6ExtensionsSlice::from_slice(ip_number::IPV6_FRAG, &ext_bytes) + .unwrap() + .0; + + let s = IpHeadersSlice::Ipv6( + Ipv6HeaderSlice::from_slice(&header_bytes).unwrap(), + exts.clone(), + ); + assert_eq!(false, s.is_ipv4()); + assert!(s.is_ipv6()); + assert!(s.ipv4().is_none()); + assert!(s.ipv6().is_some()); + assert!(s.ipv4_exts().is_none()); + assert_eq!(s.ipv6_exts(), Some(&exts)); + assert_eq!(s.slice(), &header_bytes[..]); + assert_eq!(s.ipv6_exts().unwrap().slice(), &ext_bytes[..]); + assert_eq!(s.next_header(), ip_number::IPV6_FRAG); + assert_eq!(s.payload_ip_number(), ip_number::TCP); + assert_eq!(s.version(), 6); + assert_eq!(s.header_len(), Ipv6Header::LEN + ext_bytes.len()); + assert_eq!(s.source_addr(), IpAddr::V6(Ipv6Addr::from([1; 16]))); + assert_eq!(s.destination_addr(), IpAddr::V6(Ipv6Addr::from([2; 16]))); + } + } + + #[test] + fn try_to_header() { + // ipv4 with auth extension + { + let h = Ipv4Header { + protocol: ip_number::AUTH, + source: [1, 2, 3, 4], + destination: [5, 6, 7, 8], + ..Default::default() + }; + let header_bytes = h.to_bytes(); + let auth = IpAuthHeader::new(ip_number::UDP, 7, 9, &[1, 2, 3, 4]).unwrap(); + let auth_bytes = auth.to_bytes(); + let exts = Ipv4ExtensionsSlice::from_slice(ip_number::AUTH, &auth_bytes) + .unwrap() + .0; + let s = IpHeadersSlice::Ipv4(Ipv4HeaderSlice::from_slice(&header_bytes).unwrap(), exts); + assert_eq!( + s.try_to_header().unwrap(), + IpHeaders::Ipv4(h, Ipv4Extensions { auth: Some(auth) }) + ); + } + + // ipv6 with fragment extension + { + let h = Ipv6Header { + next_header: ip_number::IPV6_FRAG, + source: [1; 16], + destination: [2; 16], + ..Default::default() + }; + let header_bytes = h.to_bytes(); + let fragment = Ipv6FragmentHeader::new(ip_number::TCP, IpFragOffset::ZERO, false, 1234); + let mut ext_bytes = Vec::new(); + ext_bytes.extend_from_slice(&fragment.to_bytes()); + let exts = Ipv6ExtensionsSlice::from_slice(ip_number::IPV6_FRAG, &ext_bytes) + .unwrap() + .0; + + let s = IpHeadersSlice::Ipv6(Ipv6HeaderSlice::from_slice(&header_bytes).unwrap(), exts); + assert_eq!( + s.try_to_header().unwrap(), + IpHeaders::Ipv6( + h, + Ipv6Extensions { + fragment: Some(fragment), + ..Default::default() + } + ) + ); + } + + // ipv6 parse error + { + let h = Ipv6Header { + next_header: ip_number::IPV6_HOP_BY_HOP, + ..Default::default() + }; + let header_bytes = h.to_bytes(); + let s = IpHeadersSlice::Ipv6( + Ipv6HeaderSlice::from_slice(&header_bytes).unwrap(), + Ipv6ExtensionsSlice::default(), + ); + assert!(s.try_to_header().is_err()); + } + } +} diff --git a/etherparse/src/net/ip_slice.rs b/etherparse/src/net/ip_slice.rs index 5c7eb5f5..4b1195dc 100644 --- a/etherparse/src/net/ip_slice.rs +++ b/etherparse/src/net/ip_slice.rs @@ -32,6 +32,30 @@ impl<'a> IpSlice<'a> { } } + /// Returns the base IPv4 or IPv6 header slice. + pub fn header(&self) -> IpHeadersSlice<'_> { + match self { + IpSlice::Ipv4(s) => IpHeadersSlice::Ipv4(s.header(), s.extensions()), + IpSlice::Ipv6(s) => IpHeadersSlice::Ipv6(s.header(), s.extensions().clone()), + } + } + + /// Converts the parsed headers into [`IpHeaders`]. + /// + /// This is infallible, as `IpSlice` is only constructible from already + /// validated IP data. + pub fn to_header(&self) -> IpHeaders { + match self { + IpSlice::Ipv4(s) => IpHeaders::Ipv4(s.header().to_header(), s.extensions().to_header()), + IpSlice::Ipv6(s) => { + let (exts, _, _) = + Ipv6Extensions::from_slice(s.header().next_header(), s.extensions().slice()) + .expect("Ipv6Slice contains validated extension headers"); + IpHeaders::Ipv6(s.header().to_header(), exts) + } + } + } + /// Returns true if the payload is fragmented. pub fn is_fragmenting_payload(&self) -> bool { match self { @@ -482,6 +506,245 @@ mod test { } } + #[test] + fn header() { + // ipv4 + { + let data = Ipv4Header::new(0, 1, ip_number::UDP, [3, 4, 5, 6], [7, 8, 9, 10]) + .unwrap() + .to_bytes(); + + assert_eq!( + IpSlice::Ipv4(Ipv4Slice::from_slice(&data[..]).unwrap()).header(), + IpHeadersSlice::Ipv4( + Ipv4HeaderSlice::from_slice(&data).unwrap(), + Ipv4ExtensionsSlice::default(), + ) + ); + } + + // ipv6 + { + let data = Ipv6Header { + traffic_class: 0, + flow_label: 1.try_into().unwrap(), + payload_length: 0, + next_header: ip_number::IGMP, + hop_limit: 4, + source: [1; 16], + destination: [2; 16], + } + .to_bytes(); + + assert_eq!( + IpSlice::Ipv6(Ipv6Slice::from_slice(&data).unwrap()).header(), + IpHeadersSlice::Ipv6( + Ipv6HeaderSlice::from_slice(&data).unwrap(), + Ipv6ExtensionsSlice::default(), + ) + ); + } + } + + #[test] + fn header_to_header_round_trip_preserves_extensions_and_base_header() { + // ipv4 with auth extension present + { + let payload = [1, 2, 3, 4]; + let auth = IpAuthHeader::new(ip_number::UDP, 7, 9, &[5, 6, 7, 8]).unwrap(); + let ipv4 = Ipv4Header::new( + (auth.header_len() + payload.len()) as u16, + 64, + ip_number::AUTH, + [1, 2, 3, 4], + [5, 6, 7, 8], + ) + .unwrap(); + + let mut data = + Vec::with_capacity(ipv4.header_len() + auth.header_len() + payload.len()); + data.extend_from_slice(&ipv4.to_bytes()); + data.extend_from_slice(&auth.to_bytes()); + data.extend_from_slice(&payload); + + let ip = IpSlice::from_slice(&data).unwrap(); + let expected = IpHeaders::Ipv4(ipv4, Ipv4Extensions { auth: Some(auth) }); + + assert_eq!(ip.to_header(), expected); + assert_eq!(ip.header().try_to_header().unwrap(), expected); + } + + // ipv6 with fragment extension present + { + let payload = [4, 3, 2, 1]; + let mut exts = Ipv6Extensions { + fragment: Some(Ipv6FragmentHeader::new( + ip_number::UDP, + 1.try_into().unwrap(), + true, + 1234, + )), + ..Default::default() + }; + let next_header = exts.set_next_headers(ip_number::UDP); + let ipv6 = Ipv6Header { + traffic_class: 0x12, + flow_label: 0x12345.try_into().unwrap(), + payload_length: (exts.header_len() + payload.len()) as u16, + next_header, + hop_limit: 64, + source: [1; 16], + destination: [2; 16], + }; + + let mut data = + Vec::with_capacity(ipv6.header_len() + exts.header_len() + payload.len()); + ipv6.write(&mut data).unwrap(); + exts.write(&mut data, ipv6.next_header).unwrap(); + data.extend_from_slice(&payload); + + let ip = IpSlice::from_slice(&data).unwrap(); + let expected = IpHeaders::Ipv6(ipv6, exts); + + assert_eq!(ip.to_header(), expected); + assert_eq!(ip.header().try_to_header().unwrap(), expected); + } + } + + #[test] + fn header_to_header_matches_manual_construction() { + // ipv4 + { + let expected_header = + Ipv4Header::new(0, 32, ip_number::UDP, [10, 0, 0, 1], [10, 0, 0, 2]).unwrap(); + let data = expected_header.to_bytes(); + let ip = IpSlice::from_slice(&data).unwrap(); + let expected = IpHeaders::Ipv4(expected_header, Default::default()); + assert_eq!(ip.to_header(), expected); + assert_eq!(ip.header().try_to_header().unwrap(), expected); + } + + // ipv6 + { + let expected_header = Ipv6Header { + traffic_class: 0, + flow_label: 1.try_into().unwrap(), + payload_length: 0, + next_header: ip_number::UDP, + hop_limit: 40, + source: [1; 16], + destination: [2; 16], + }; + let data = expected_header.to_bytes(); + let ip = IpSlice::from_slice(&data).unwrap(); + let expected = IpHeaders::Ipv6(expected_header, Default::default()); + assert_eq!(ip.to_header(), expected); + assert_eq!(ip.header().try_to_header().unwrap(), expected); + } + } + + #[test] + fn header_into_ip_headers_is_coherent() { + // ipv4 + { + let data = Ipv4Header::new(0, 20, ip_number::UDP, [1, 1, 1, 1], [2, 2, 2, 2]) + .unwrap() + .to_bytes(); + let ip = IpSlice::from_slice(&data).unwrap(); + let header = ip.header(); + assert_eq!(ip.to_header(), header.try_to_header().unwrap()); + } + + // ipv6 + { + let data = Ipv6Header { + next_header: ip_number::TCP, + ..Default::default() + } + .to_bytes(); + let ip = IpSlice::from_slice(&data).unwrap(); + let header = ip.header(); + assert_eq!(ip.to_header(), header.try_to_header().unwrap()); + } + } + + #[test] + fn header_accessors_match_underlying_header_slice() { + // ipv4 (with options) + { + let mut ipv4 = + Ipv4Header::new(0, 20, ip_number::UDP, [3, 4, 5, 6], [7, 8, 9, 10]).unwrap(); + ipv4.options = (&[11, 12, 13, 14][..]).try_into().unwrap(); + ipv4.total_len = ipv4.header_len() as u16; + ipv4.header_checksum = ipv4.calc_header_checksum(); + let data = ipv4.to_bytes(); + + let ip = IpSlice::from_slice(&data).unwrap(); + let header = ip.header(); + let v4 = ip.ipv4().unwrap().header(); + + assert!(header.is_ipv4()); + assert_eq!(false, header.is_ipv6()); + assert_eq!(header.ipv4(), Some(v4)); + assert_eq!(header.ipv6(), None); + assert_eq!(header.ipv4_exts(), Some(ip.ipv4().unwrap().extensions())); + assert_eq!(header.ipv6_exts(), None); + assert_eq!(header.version(), v4.version()); + assert_eq!( + header.header_len(), + v4.slice().len() + ip.ipv4().unwrap().extensions().to_header().header_len() + ); + assert_eq!(header.next_header(), v4.protocol()); + assert_eq!(header.payload_ip_number(), ip.payload_ip_number()); + assert_eq!(header.source_addr(), IpAddr::from(v4.source_addr())); + assert_eq!( + header.destination_addr(), + IpAddr::from(v4.destination_addr()) + ); + assert_eq!(header.slice(), v4.slice()); + assert_eq!(header.ipv4_exts(), Some(ip.ipv4().unwrap().extensions())); + } + + // ipv6 + { + let ipv6 = Ipv6Header { + traffic_class: 1, + flow_label: 2.try_into().unwrap(), + payload_length: 0, + next_header: ip_number::IGMP, + hop_limit: 3, + source: [1; 16], + destination: [2; 16], + }; + let data = ipv6.to_bytes(); + + let ip = IpSlice::from_slice(&data).unwrap(); + let header = ip.header(); + let v6 = ip.ipv6().unwrap().header(); + + assert_eq!(false, header.is_ipv4()); + assert!(header.is_ipv6()); + assert_eq!(header.ipv4(), None); + assert_eq!(header.ipv6(), Some(v6)); + assert_eq!(header.ipv4_exts(), None); + assert_eq!(header.ipv6_exts(), Some(ip.ipv6().unwrap().extensions())); + assert_eq!(header.version(), v6.version()); + assert_eq!( + header.header_len(), + v6.slice().len() + ip.ipv6().unwrap().extensions().slice().len() + ); + assert_eq!(header.next_header(), v6.next_header()); + assert_eq!(header.payload_ip_number(), ip.payload_ip_number()); + assert_eq!(header.source_addr(), IpAddr::from(v6.source_addr())); + assert_eq!( + header.destination_addr(), + IpAddr::from(v6.destination_addr()) + ); + assert_eq!(header.slice(), v6.slice()); + assert_eq!(header.ipv6_exts(), Some(ip.ipv6().unwrap().extensions())); + } + } + #[test] fn payload() { let payload: [u8; 4] = [1, 2, 3, 4]; @@ -570,6 +833,77 @@ mod test { } } + proptest! { + #[test] + fn header_round_trip_ipv4_proptest( + mut ipv4_header in ipv4_unknown(), + payload in proptest::collection::vec(any::(), 0..32) + ) { + ipv4_header.total_len = (ipv4_header.header_len() + payload.len()) as u16; + ipv4_header.header_checksum = ipv4_header.calc_header_checksum(); + + let mut data = Vec::with_capacity(ipv4_header.header_len() + payload.len()); + data.extend_from_slice(&ipv4_header.to_bytes()); + data.extend_from_slice(&payload); + + let ip = IpSlice::from_slice(&data).unwrap(); + let header = ip.header(); + let v4 = ip.ipv4().unwrap().header(); + let expected = IpHeaders::Ipv4(ipv4_header.clone(), Default::default()); + + prop_assert!(header.is_ipv4()); + prop_assert_eq!(false, header.is_ipv6()); + prop_assert_eq!(header.ipv4(), Some(v4)); + prop_assert_eq!(header.ipv6(), None); + prop_assert_eq!(header.version(), 4); + prop_assert_eq!(header.header_len(), ipv4_header.header_len()); + prop_assert_eq!(header.next_header(), ipv4_header.protocol); + prop_assert_eq!(header.source_addr(), IpAddr::from(ipv4_header.source)); + prop_assert_eq!( + header.destination_addr(), + IpAddr::from(ipv4_header.destination) + ); + prop_assert_eq!(header.slice(), &data[..ipv4_header.header_len()]); + prop_assert_eq!(header.try_to_header().unwrap(), expected.clone()); + prop_assert_eq!(ip.to_header(), expected); + } + } + + proptest! { + #[test] + fn header_round_trip_ipv6_proptest( + mut ipv6_header in ipv6_unknown(), + payload in proptest::collection::vec(any::(), 0..32) + ) { + ipv6_header.payload_length = payload.len() as u16; + + let mut data = Vec::with_capacity(ipv6_header.header_len() + payload.len()); + data.extend_from_slice(&ipv6_header.to_bytes()); + data.extend_from_slice(&payload); + + let ip = IpSlice::from_slice(&data).unwrap(); + let header = ip.header(); + let v6 = ip.ipv6().unwrap().header(); + let expected = IpHeaders::Ipv6(ipv6_header.clone(), Default::default()); + + prop_assert_eq!(false, header.is_ipv4()); + prop_assert!(header.is_ipv6()); + prop_assert_eq!(header.ipv4(), None); + prop_assert_eq!(header.ipv6(), Some(v6)); + prop_assert_eq!(header.version(), 6); + prop_assert_eq!(header.header_len(), Ipv6Header::LEN); + prop_assert_eq!(header.next_header(), ipv6_header.next_header); + prop_assert_eq!(header.source_addr(), IpAddr::from(ipv6_header.source)); + prop_assert_eq!( + header.destination_addr(), + IpAddr::from(ipv6_header.destination) + ); + prop_assert_eq!(header.slice(), &data[..Ipv6Header::LEN]); + prop_assert_eq!(header.try_to_header().unwrap(), expected.clone()); + prop_assert_eq!(ip.to_header(), expected); + } + } + proptest! { #[test] fn from_ip_slice( diff --git a/etherparse/src/net/mod.rs b/etherparse/src/net/mod.rs index fb922f64..0c7b0e82 100644 --- a/etherparse/src/net/mod.rs +++ b/etherparse/src/net/mod.rs @@ -31,6 +31,9 @@ pub use ip_frag_offset::*; mod ip_headers; pub use ip_headers::*; +mod ip_headers_slice; +pub use ip_headers_slice::*; + mod ip_number_impl; pub use ip_number_impl::*;