diff --git a/etherparse/proptest-regressions/net/ipv4_header.txt b/etherparse/proptest-regressions/net/ipv4_header.txt new file mode 100644 index 00000000..7ee59512 --- /dev/null +++ b/etherparse/proptest-regressions/net/ipv4_header.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc fc12b54abbe610c4344c9f792ab4c501830e658c699e926cebafc50e5c156975 # shrinks to source_ip = [0, 0, 0, 0], dest_ip = [0, 0, 0, 0], ttl = 0, ok_payload_len = 0, err_payload_len = 65516 diff --git a/etherparse/src/compositions_tests.rs b/etherparse/src/compositions_tests.rs index 96645825..42a8c010 100644 --- a/etherparse/src/compositions_tests.rs +++ b/etherparse/src/compositions_tests.rs @@ -713,7 +713,7 @@ impl<'a> ComponentTest<'a> { proptest! { ///Test that all known packet compositions are parsed correctly. #[test] - // #[cfg_attr(miri, ignore)] // vec allocation reduces miri runspeed too much + // #[cfg_attr(miri, ignore)] // vec allocation reduces miri run-speed too much fn test_compositions(ref eth in ethernet_2_unknown(), ref vlan0 in vlan_single_unknown(), ref vlan1 in vlan_single_unknown(), diff --git a/etherparse/src/err/ip/ip_dscp_unknown_value_error.rs b/etherparse/src/err/ip/ip_dscp_unknown_value_error.rs new file mode 100644 index 00000000..9067ca8f --- /dev/null +++ b/etherparse/src/err/ip/ip_dscp_unknown_value_error.rs @@ -0,0 +1,67 @@ +use core::{cmp::Eq, cmp::PartialEq, fmt::Debug, hash::Hash}; + +/// Error if an unknown value is passed to [`crate::IpDscpKnown::try_from_ip_dscp`]. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct IpDscpUnknownValueError { + /// Unknown DSCP value that caused the error. + pub value: u8, +} + +impl core::fmt::Display for IpDscpUnknownValueError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Error DSCP value '{}' is not known.", self.value) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for IpDscpUnknownValueError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::{collections::hash_map::DefaultHasher, error::Error, format, hash::Hasher}; + + #[test] + fn fmt() { + assert_eq!( + format!("{}", IpDscpUnknownValueError { value: 3 }), + "Error DSCP value '3' is not known." + ); + } + + #[test] + fn dbg() { + assert_eq!( + format!("{:?}", IpDscpUnknownValueError { value: 3 }), + format!("IpDscpUnknownValueError {{ value: {} }}", 3) + ); + } + + #[test] + fn clone_eq_hash() { + let err = IpDscpUnknownValueError { value: 3 }; + assert_eq!(err, err.clone()); + let hash_a = { + let mut hasher = DefaultHasher::new(); + err.hash(&mut hasher); + hasher.finish() + }; + let hash_b = { + let mut hasher = DefaultHasher::new(); + err.clone().hash(&mut hasher); + hasher.finish() + }; + assert_eq!(hash_a, hash_b); + } + + #[cfg(feature = "std")] + #[test] + fn source() { + assert!(IpDscpUnknownValueError { value: 3 }.source().is_none()); + } +} diff --git a/etherparse/src/err/ip/mod.rs b/etherparse/src/err/ip/mod.rs index 600dfeed..e90ad98c 100644 --- a/etherparse/src/err/ip/mod.rs +++ b/etherparse/src/err/ip/mod.rs @@ -17,6 +17,9 @@ mod headers_write_error; #[cfg(feature = "std")] pub use headers_write_error::*; +pub mod ip_dscp_unknown_value_error; +pub use ip_dscp_unknown_value_error::*; + mod lax_header_slice_error; pub use lax_header_slice_error::*; diff --git a/etherparse/src/err/value_type.rs b/etherparse/src/err/value_type.rs index 046703dd..15bfb372 100644 --- a/etherparse/src/err/value_type.rs +++ b/etherparse/src/err/value_type.rs @@ -6,19 +6,19 @@ pub enum ValueType { VlanId, /// VLAN PCP (Priority Code Point) field in a [`crate::SingleVlanHeader`]. VlanPcp, - /// MACsec association number (present in the [`crate::MacSecHeader`]). + /// MACsec association number (present in the [`crate::MacsecHeader`]). MacsecAn, - /// MACsec short length (present in the [`crate::MacSecHeader`]). + /// MACsec short length (present in the [`crate::MacsecHeader`]). MacsecShortLen, /// IP Fragment offset present in the IPv4 header and /// IPv6 fragmentation header. IpFragmentOffset, - /// IPv4 Header DSCP (Differentiated Services Code Point) field - /// present in an [`crate::Ipv4Header`]. - Ipv4Dscp, - /// IPv4 Header ECN (Explicit Congestion Notification) field - /// present in an [`crate::Ipv4Header`]. - Ipv4Ecn, + /// IPv4 & IPv6 Header DSCP (Differentiated Services Code Point) field + /// present in an [`crate::Ipv4Header`] or [`crate::Ipv6Header`]. + IpDscp, + /// IPv6 & IPv6 Header ECN (Explicit Congestion Notification) field + /// present in an [`crate::Ipv4Header`] or [`crate::Ipv6Header`]. + IpEcn, /// IPv6 Header Flow Label field present in [`crate::Ipv6Header`]. Ipv6FlowLabel, /// IPv4 Header "total length" field based on the payload @@ -54,8 +54,8 @@ impl core::fmt::Display for ValueType { MacsecAn => write!(f, "MACsec AN (Association Number)"), MacsecShortLen => write!(f, "MACsec SL (Short Length)"), IpFragmentOffset => write!(f, "IP Fragment Offset"), - Ipv4Dscp => write!(f, "IPv4 DSCP (Differentiated Services Code Point)"), - Ipv4Ecn => write!(f, "IPv4 ECN (Explicit Congestion Notification)"), + IpDscp => write!(f, "IPv4 DSCP (Differentiated Services Code Point)"), + IpEcn => write!(f, "IPv4 ECN (Explicit Congestion Notification)"), Ipv6FlowLabel => write!(f, "IPv6 Flow Label"), Ipv4PayloadLength => write!(f, "IPv4 Header 'Payload Length' (sets 'Total Length')"), Ipv6PayloadLength => write!(f, "IPv6 Header 'Payload Length'"), @@ -102,11 +102,11 @@ mod test { assert_eq!("IP Fragment Offset", &format!("{}", IpFragmentOffset)); assert_eq!( "IPv4 DSCP (Differentiated Services Code Point)", - &format!("{}", Ipv4Dscp) + &format!("{}", IpDscp) ); assert_eq!( "IPv4 ECN (Explicit Congestion Notification)", - &format!("{}", Ipv4Ecn) + &format!("{}", IpEcn) ); assert_eq!("IPv6 Flow Label", &format!("{}", Ipv6FlowLabel)); assert_eq!( diff --git a/etherparse/src/lax_sliced_packet_cursor.rs b/etherparse/src/lax_sliced_packet_cursor.rs index b70fcb42..60cb9e10 100644 --- a/etherparse/src/lax_sliced_packet_cursor.rs +++ b/etherparse/src/lax_sliced_packet_cursor.rs @@ -135,7 +135,7 @@ impl<'a> LaxSlicedPacketCursor<'a> { len_source: self.len_source, payload: vlan_payload.payload, }; - // SAFETY: Safe, as the if at the startt verifies that there is still + // SAFETY: Safe, as the if at the start verifies that there is still // space in link_exts. unsafe { self.result @@ -165,7 +165,7 @@ impl<'a> LaxSlicedPacketCursor<'a> { self.offset += macsec.header.header_len(); let macsec_payload = macsec.payload.clone(); - // SAFETY: Safe, as the if at the startt verifies that there is still + // SAFETY: Safe, as the if at the start verifies that there is still // space in link_exts. unsafe { self.result diff --git a/etherparse/src/link/macsec_an.rs b/etherparse/src/link/macsec_an.rs index 639982e9..4e6ea38f 100644 --- a/etherparse/src/link/macsec_an.rs +++ b/etherparse/src/link/macsec_an.rs @@ -1,7 +1,7 @@ use crate::err::ValueTooBigError; /// 2 bit unsigned integer containing the "MACsec association number". -/// (present in the [`crate::MacSecHeader`]). +/// (present in the [`crate::MacsecHeader`]). /// /// Identifies up to four SAs within the context of an SC. #[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] diff --git a/etherparse/src/link/macsec_header.rs b/etherparse/src/link/macsec_header.rs index 268c3adf..ee87f552 100644 --- a/etherparse/src/link/macsec_header.rs +++ b/etherparse/src/link/macsec_header.rs @@ -58,7 +58,7 @@ impl MacsecHeader { } } - /// Try creating a [`MacSecHeaderSlice`] from a slice containing the + /// Try creating a [`MacsecHeaderSlice`] from a slice containing the /// MACsec header & next ether type. pub fn from_slice(slice: &[u8]) -> Result { MacsecHeaderSlice::from_slice(slice).map(|v| v.to_header()) diff --git a/etherparse/src/link/macsec_header_slice.rs b/etherparse/src/link/macsec_header_slice.rs index 4c0b9af3..128756e9 100644 --- a/etherparse/src/link/macsec_header_slice.rs +++ b/etherparse/src/link/macsec_header_slice.rs @@ -10,7 +10,7 @@ pub struct MacsecHeaderSlice<'a> { } impl<'a> MacsecHeaderSlice<'a> { - /// Try creating a [`MacSecHeaderSlice`] from a slice containing the + /// Try creating a [`MacsecHeaderSlice`] from a slice containing the /// MACsec header & next ether type. pub fn from_slice( slice: &'a [u8], @@ -270,7 +270,7 @@ impl<'a> MacsecHeaderSlice<'a> { } /// Decodes all MacSecHeader values and returns them as a - /// [`crate::MacSecHeader`]. + /// [`crate::MacsecHeader`]. #[inline] pub fn to_header(&self) -> MacsecHeader { MacsecHeader { diff --git a/etherparse/src/link/macsec_short_len.rs b/etherparse/src/link/macsec_short_len.rs index ea549454..287a9e45 100644 --- a/etherparse/src/link/macsec_short_len.rs +++ b/etherparse/src/link/macsec_short_len.rs @@ -1,7 +1,7 @@ use crate::err::ValueTooBigError; /// 6 bit unsigned integer containing the "MACsec short length". -/// (present in the [`crate::MacSecHeader`]). +/// (present in the [`crate::MacsecHeader`]). #[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct MacsecShortLen(u8); @@ -70,7 +70,7 @@ impl MacsecShortLen { /// Creates an [`MacsecShortLen`] from a length and automatically /// defaults to zero if too big. This mirrors the expected behavior - /// of the `short_len` field in the [`MacsecHeader`]. + /// of the `short_len` field in the [`crate::MacsecHeader`]. /// /// # Example /// ``` diff --git a/etherparse/src/net/ip_dscp.rs b/etherparse/src/net/ip_dscp.rs new file mode 100644 index 00000000..738ba2bf --- /dev/null +++ b/etherparse/src/net/ip_dscp.rs @@ -0,0 +1,333 @@ +use crate::err::ValueTooBigError; + +/// Deprecated, use [`IpDscp`] instead. +#[deprecated(since = "0.18.0", note = "Use `IpDscp` instead of `Ipv4Dscp`")] +pub type Ipv4Dscp = IpDscp; + +/// 6 bit unsigned integer containing the "Differentiated Services +/// Code Point" (present in the [`crate::Ipv4Header`] and in the +/// in [`crate::Ipv6Header`] as part of `traffic_class`). +/// +/// Established in +/// [RFC-2472](https://datatracker.ietf.org/doc/html/rfc2474) and defined/maintained in the +/// [IANA dscp-registry](https://www.iana.org/assignments/dscp-registry/dscp-registry.xhtml) +#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct IpDscp(u8); + +impl IpDscp { + /// IpDscp with value 0. + pub const ZERO: IpDscp = IpDscp(0); + + /// Maximum value of an IPv4 header DSCP. + pub const MAX_U8: u8 = 0b0011_1111; + + /// Maximum value of DSCP field (6 bits). + pub const MAX: IpDscp = IpDscp(Self::MAX_U8); + + /// Class Selector 0 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + pub const CS0: IpDscp = IpDscp(0b00_0000); + + /// Class Selector 1 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + pub const CS1: IpDscp = IpDscp(0b00_1000); + + /// Class Selector 2 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + pub const CS2: IpDscp = IpDscp(0b01_0000); + + /// Class Selector 3 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + pub const CS3: IpDscp = IpDscp(0b01_1000); + + /// Class Selector 4 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + pub const CS4: IpDscp = IpDscp(0b10_0000); + + /// Class Selector 5 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + pub const CS5: IpDscp = IpDscp(0b10_1000); + + /// Class Selector 6 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + pub const CS6: IpDscp = IpDscp(0b11_0000); + + /// Class Selector 7 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + pub const CS7: IpDscp = IpDscp(0b11_1000); + + /// Assured Forwarding PHB Group 11 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + pub const AF11: IpDscp = IpDscp(0b00_1010); + + /// Assured Forwarding PHB Group 12 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + pub const AF12: IpDscp = IpDscp(0b00_1100); + + /// Assured Forwarding PHB Group 13 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + pub const AF13: IpDscp = IpDscp(0b00_1110); + + /// Assured Forwarding PHB Group 21 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + pub const AF21: IpDscp = IpDscp(0b01_0010); + + /// Assured Forwarding PHB Group 22 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + pub const AF22: IpDscp = IpDscp(0b01_0100); + + /// Assured Forwarding PHB Group 23 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + pub const AF23: IpDscp = IpDscp(0b01_0110); + + /// Assured Forwarding PHB Group 31 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + pub const AF31: IpDscp = IpDscp(0b01_1010); + + /// Assured Forwarding PHB Group 32 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + pub const AF32: IpDscp = IpDscp(0b01_1100); + + /// Assured Forwarding PHB Group 11 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + pub const AF33: IpDscp = IpDscp(0b01_1110); + + /// Assured Forwarding PHB Group 11 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + pub const AF41: IpDscp = IpDscp(0b10_0010); + + /// Assured Forwarding PHB Group 11 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + pub const AF42: IpDscp = IpDscp(0b10_0100); + + /// Assured Forwarding PHB Group 11 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + pub const AF43: IpDscp = IpDscp(0b10_0110); + + /// Expedited Forwarding (Pool 1) [RFC-3246](https://datatracker.ietf.org/doc/html/rfc3246) + pub const EF: IpDscp = IpDscp(0b10_1110); + + /// Voice admit (Pool 1) [RFC-5865](https://datatracker.ietf.org/doc/html/rfc5865) + pub const VOICE_ADMIT: IpDscp = IpDscp(0b10_1100); + + /// Lower Effort PHB (Pool 3) [RFC-8622](https://datatracker.ietf.org/doc/html/rfc8622) + pub const LOWER_EFFORT: IpDscp = IpDscp(0b00_0001); + + /// Tries to create an [`IpDscp`] and checks that the passed value + /// is smaller or equal than [`IpDscp::MAX_U8`] (6 bit unsigned integer). + /// + /// In case the passed value is bigger then what can be represented in an 6 bit + /// integer an error is returned. Otherwise an `Ok` containing the [`IpDscp`]. + /// + /// ``` + /// use etherparse::IpDscp; + /// + /// let dscp = IpDscp::try_new(32).unwrap(); + /// assert_eq!(dscp.value(), 32); + /// + /// // if a number that can not be represented in an 6 bit integer + /// // gets passed in an error is returned + /// use etherparse::err::{ValueTooBigError, ValueType}; + /// assert_eq!( + /// IpDscp::try_new(IpDscp::MAX_U8 + 1), + /// Err(ValueTooBigError{ + /// actual: IpDscp::MAX_U8 + 1, + /// max_allowed: IpDscp::MAX_U8, + /// value_type: ValueType::IpDscp, + /// }) + /// ); + /// ``` + #[inline] + pub const fn try_new(value: u8) -> Result> { + use crate::err::ValueType; + if value <= IpDscp::MAX_U8 { + Ok(IpDscp(value)) + } else { + Err(ValueTooBigError { + actual: value, + max_allowed: IpDscp::MAX_U8, + value_type: ValueType::IpDscp, + }) + } + } + + /// Creates an [`IpDscp`] without checking that the value + /// is smaller or equal than [`IpDscp::MAX_U8`] (6 bit unsigned integer). + /// The caller must guarantee that `value <= IpDscp::MAX_U8`. + /// + /// # Safety + /// + /// `value` must be smaller or equal than [`IpDscp::MAX_U8`] + /// otherwise the behavior of functions or data structures relying + /// on this pre-requirement is undefined. + #[inline] + pub const unsafe fn new_unchecked(value: u8) -> IpDscp { + debug_assert!(value <= IpDscp::MAX_U8); + IpDscp(value) + } + + /// Returns the underlying unsigned 6 bit value as an `u8` value. + #[inline] + pub const fn value(self) -> u8 { + self.0 + } +} + +impl core::fmt::Display for IpDscp { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.fmt(f) + } +} + +impl From for u8 { + #[inline] + fn from(value: IpDscp) -> Self { + value.0 + } +} + +impl TryFrom for IpDscp { + type Error = ValueTooBigError; + + #[inline] + fn try_from(value: u8) -> Result { + use crate::err::ValueType; + if value <= IpDscp::MAX_U8 { + Ok(IpDscp(value)) + } else { + Err(Self::Error { + actual: value, + max_allowed: IpDscp::MAX_U8, + value_type: ValueType::IpDscp, + }) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use core::hash::{Hash, Hasher}; + use proptest::prelude::*; + use std::format; + + #[test] + fn derived_traits() { + // copy & clone + { + let a = IpDscp(32); + let b = a; + assert_eq!(a, b); + assert_eq!(a.clone(), a); + } + + // default + { + let actual: IpDscp = Default::default(); + assert_eq!(actual.value(), 0); + } + + // debug + { + let a = IpDscp(32); + assert_eq!(format!("{:?}", a), format!("IpDscp(32)")); + } + + // ord & partial ord + { + use core::cmp::Ordering; + let a = IpDscp(32); + let b = a; + assert_eq!(a.cmp(&b), Ordering::Equal); + assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal)); + } + + // hash + { + use std::collections::hash_map::DefaultHasher; + let a = { + let mut hasher = DefaultHasher::new(); + IpDscp(64).hash(&mut hasher); + hasher.finish() + }; + let b = { + let mut hasher = DefaultHasher::new(); + IpDscp(64).hash(&mut hasher); + hasher.finish() + }; + assert_eq!(a, b); + } + } + + proptest! { + #[test] + fn try_new( + valid_value in 0..=0b0011_1111u8, + invalid_value in 0b0100_0000u8..=u8::MAX + ) { + use crate::err::{ValueType, ValueTooBigError}; + assert_eq!( + valid_value, + IpDscp::try_new(valid_value).unwrap().value() + ); + assert_eq!( + IpDscp::try_new(invalid_value).unwrap_err(), + ValueTooBigError{ + actual: invalid_value, + max_allowed: 0b0011_1111, + value_type: ValueType::IpDscp + } + ); + } + } + + proptest! { + #[test] + fn try_from( + valid_value in 0..=0b0011_1111u8, + invalid_value in 0b0100_0000u8..=u8::MAX + ) { + use crate::err::{ValueType, ValueTooBigError}; + // try_into + { + let actual: IpDscp = valid_value.try_into().unwrap(); + assert_eq!(actual.value(), valid_value); + + let err: Result> = invalid_value.try_into(); + assert_eq!( + err.unwrap_err(), + ValueTooBigError{ + actual: invalid_value, + max_allowed: 0b0011_1111, + value_type: ValueType::IpDscp + } + ); + } + // try_from + { + assert_eq!( + IpDscp::try_from(valid_value).unwrap().value(), + valid_value + ); + + assert_eq!( + IpDscp::try_from(invalid_value).unwrap_err(), + ValueTooBigError{ + actual: invalid_value, + max_allowed: 0b0011_1111, + value_type: ValueType::IpDscp + } + ); + } + } + } + + proptest! { + #[test] + fn new_unchecked(valid_value in 0..=0b0011_1111u8) { + assert_eq!( + valid_value, + unsafe { + IpDscp::new_unchecked(valid_value).value() + } + ); + } + } + + proptest! { + #[test] + fn fmt(valid_value in 0..=0b0011_1111u8) { + assert_eq!(format!("{}", IpDscp(valid_value)), format!("{}", valid_value)); + } + } + + proptest! { + #[test] + fn from(valid_value in 0..=0b0011_1111u8,) { + let dscp = IpDscp::try_new(valid_value).unwrap(); + let actual: u8 = dscp.into(); + assert_eq!(actual, valid_value); + } + } +} diff --git a/etherparse/src/net/ip_dscp_known.rs b/etherparse/src/net/ip_dscp_known.rs new file mode 100644 index 00000000..64b3f0ad --- /dev/null +++ b/etherparse/src/net/ip_dscp_known.rs @@ -0,0 +1,261 @@ +use crate::err::ip::IpDscpUnknownValueError; + +use super::IpDscp; + +/// Known "Differentiated Services Field Codepoints" (DSCP) values according to +/// the [IANA registry](https://www.iana.org/assignments/dscp-registry/dscp-registry.xhtml) +/// (exported on 2025-04-24). +/// +/// DSCP was established in +/// [RFC-2472](https://datatracker.ietf.org/doc/html/rfc2474) and defined/maintained in the +/// [IANA dscp-registry](https://www.iana.org/assignments/dscp-registry/dscp-registry.xhtml) +#[derive(Copy, Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub enum IpDscpKnown { + /// Class Selector 0 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + ClassSelector0 = 0b0000_0000, + + /// Class Selector 1 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + ClassSelector1 = 0b0000_1000, + + /// Class Selector 2 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + ClassSelector2 = 0b0001_0000, + + /// Class Selector 3 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + ClassSelector3 = 0b0001_1000, + + /// Class Selector 4 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + ClassSelector4 = 0b0010_0000, + + /// Class Selector 5 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + ClassSelector5 = 0b0010_1000, + + /// Class Selector 6 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + ClassSelector6 = 0b0011_0000, + + /// Class Selector 7 (Pool 1) [RFC-2474](https://datatracker.ietf.org/doc/html/rfc2474) + ClassSelector7 = 0b0011_1000, + + /// Assured Forwarding PHB Group 11 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + AfGroup11 = 0b0000_1010, + + /// Assured Forwarding PHB Group 12 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + AfGroup12 = 0b0000_1100, + + /// Assured Forwarding PHB Group 13 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + AfGroup13 = 0b0000_1110, + + /// Assured Forwarding PHB Group 21 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + AfGroup21 = 0b0001_0010, + + /// Assured Forwarding PHB Group 22 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + AfGroup22 = 0b0001_0100, + + /// Assured Forwarding PHB Group 23 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + AfGroup23 = 0b0001_0110, + + /// Assured Forwarding PHB Group 31 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + AfGroup31 = 0b0001_1010, + + /// Assured Forwarding PHB Group 32 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + AfGroup32 = 0b0001_1100, + + /// Assured Forwarding PHB Group 33 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + AfGroup33 = 0b0001_1110, + + /// Assured Forwarding PHB Group 41 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + AfGroup41 = 0b0010_0010, + + /// Assured Forwarding PHB Group 42 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + AfGroup42 = 0b0010_0100, + + /// Assured Forwarding PHB Group 43 (Pool 1) [RFC-2597](https://datatracker.ietf.org/doc/html/rfc2597) + AfGroup43 = 0b0010_0110, + + /// Expedited Forwarding (Pool 1) [RFC-3246](https://datatracker.ietf.org/doc/html/rfc3246) + ExpeditedForwarding = 0b_0010_1110, + + /// Voice admit (Pool 1) [RFC-5865](https://datatracker.ietf.org/doc/html/rfc5865) + VoiceAdmit = 0b0010_1100, + + /// Lower Effort PHB (Pool 3) [RFC-8622](https://datatracker.ietf.org/doc/html/rfc8622) + LowerEffort = 0b0000_0001, + // NOTE: NQB was omitted here because it has an expiration in the IANA registry. +} + +impl IpDscpKnown { + /// Try converting an [`IpDscp`] into a [`IpDscpKnown`] value. + /// + /// Returns an error if the value is not a known DSCP value. + pub const fn try_from_ip_dscp(value: IpDscp) -> Result { + match value { + IpDscp::CS0 => Ok(IpDscpKnown::ClassSelector0), + IpDscp::CS1 => Ok(IpDscpKnown::ClassSelector1), + IpDscp::CS2 => Ok(IpDscpKnown::ClassSelector2), + IpDscp::CS3 => Ok(IpDscpKnown::ClassSelector3), + IpDscp::CS4 => Ok(IpDscpKnown::ClassSelector4), + IpDscp::CS5 => Ok(IpDscpKnown::ClassSelector5), + IpDscp::CS6 => Ok(IpDscpKnown::ClassSelector6), + IpDscp::CS7 => Ok(IpDscpKnown::ClassSelector7), + IpDscp::AF11 => Ok(IpDscpKnown::AfGroup11), + IpDscp::AF12 => Ok(IpDscpKnown::AfGroup12), + IpDscp::AF13 => Ok(IpDscpKnown::AfGroup13), + IpDscp::AF21 => Ok(IpDscpKnown::AfGroup21), + IpDscp::AF22 => Ok(IpDscpKnown::AfGroup22), + IpDscp::AF23 => Ok(IpDscpKnown::AfGroup23), + IpDscp::AF31 => Ok(IpDscpKnown::AfGroup31), + IpDscp::AF32 => Ok(IpDscpKnown::AfGroup32), + IpDscp::AF33 => Ok(IpDscpKnown::AfGroup33), + IpDscp::AF41 => Ok(IpDscpKnown::AfGroup41), + IpDscp::AF42 => Ok(IpDscpKnown::AfGroup42), + IpDscp::AF43 => Ok(IpDscpKnown::AfGroup43), + IpDscp::EF => Ok(IpDscpKnown::ExpeditedForwarding), + IpDscp::VOICE_ADMIT => Ok(IpDscpKnown::VoiceAdmit), + IpDscp::LOWER_EFFORT => Ok(IpDscpKnown::LowerEffort), + value => Err(IpDscpUnknownValueError { + value: value.value(), + }), + } + } +} + +impl TryFrom for IpDscpKnown { + type Error = crate::err::ip::IpDscpUnknownValueError; + + fn try_from(value: IpDscp) -> Result { + Self::try_from_ip_dscp(value) + } +} + +impl From for u8 { + fn from(value: IpDscpKnown) -> Self { + value as u8 + } +} + +impl From for IpDscp { + fn from(value: IpDscpKnown) -> Self { + // SAFE: As all IpDscpKnown values are bellow the maximum + // value of IpDscp::MAX_U8. + unsafe { IpDscp::new_unchecked(value as u8) } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{err::ip::IpDscpUnknownValueError, IpDscp}; + + #[test] + fn try_from_ip_dscp() { + // ok value conversions + { + use IpDscpKnown::*; + let tests = [ + (ClassSelector0, 0b0000_0000u8), + (ClassSelector1, 0b0000_1000u8), + (ClassSelector2, 0b0001_0000u8), + (ClassSelector3, 0b0001_1000u8), + (ClassSelector4, 0b0010_0000u8), + (ClassSelector5, 0b0010_1000u8), + (ClassSelector6, 0b0011_0000u8), + (ClassSelector7, 0b0011_1000u8), + (AfGroup11, 0b0000_1010u8), + (AfGroup12, 0b0000_1100u8), + (AfGroup13, 0b0000_1110u8), + (AfGroup21, 0b0001_0010u8), + (AfGroup22, 0b0001_0100u8), + (AfGroup23, 0b0001_0110u8), + (AfGroup31, 0b0001_1010u8), + (AfGroup32, 0b0001_1100u8), + (AfGroup33, 0b0001_1110u8), + (AfGroup41, 0b0010_0010u8), + (AfGroup42, 0b0010_0100u8), + (AfGroup43, 0b0010_0110u8), + (ExpeditedForwarding, 0b_0010_1110u8), + (VoiceAdmit, 0b0010_1100u8), + (LowerEffort, 0b0000_0001u8), + ]; + for (expected, value) in tests { + let ip_dscp = IpDscp::try_new(value).unwrap(); + assert_eq!(Ok(expected), IpDscpKnown::try_from_ip_dscp(ip_dscp)); + assert_eq!(Ok(expected), IpDscpKnown::try_from(ip_dscp)); + } + } + + // unknown conversions (experimental range) + { + // defined based on IANA registry + let unknown_ranges = [ + 2..=7u8, + 9..=9u8, + 11..=11u8, + 13..=13u8, + 15..=15u8, + 17..=17u8, + 19..=19u8, + 21..=21u8, + 23..=23u8, + 25..=25u8, + 27..=27u8, + 29..=29u8, + 31..=31u8, + 33..=33u8, + 35..=35u8, + 37..=37u8, + 39..=39u8, + 41..=43u8, + 45..=45u8, + 47..=47u8, + 49..=55u8, + 57..=0b0011_1111u8, + ]; + for range in unknown_ranges { + for value in range { + let ip_dscp = IpDscp::try_new(value).unwrap(); + assert_eq!( + Err(IpDscpUnknownValueError { value }), + IpDscpKnown::try_from_ip_dscp(ip_dscp) + ); + assert_eq!( + Err(IpDscpUnknownValueError { value }), + IpDscpKnown::try_from(ip_dscp) + ); + } + } + } + } + + #[test] + fn into_u8_and_ip_dscp() { + use IpDscpKnown::*; + let tests = [ + (ClassSelector0, 0b0000_0000u8), + (ClassSelector1, 0b0000_1000u8), + (ClassSelector2, 0b0001_0000u8), + (ClassSelector3, 0b0001_1000u8), + (ClassSelector4, 0b0010_0000u8), + (ClassSelector5, 0b0010_1000u8), + (ClassSelector6, 0b0011_0000u8), + (ClassSelector7, 0b0011_1000u8), + (AfGroup11, 0b0000_1010u8), + (AfGroup12, 0b0000_1100u8), + (AfGroup13, 0b0000_1110u8), + (AfGroup21, 0b0001_0010u8), + (AfGroup22, 0b0001_0100u8), + (AfGroup23, 0b0001_0110u8), + (AfGroup31, 0b0001_1010u8), + (AfGroup32, 0b0001_1100u8), + (AfGroup33, 0b0001_1110u8), + (AfGroup41, 0b0010_0010u8), + (AfGroup42, 0b0010_0100u8), + (AfGroup43, 0b0010_0110u8), + (ExpeditedForwarding, 0b_0010_1110u8), + (VoiceAdmit, 0b0010_1100u8), + (LowerEffort, 0b0000_0001u8), + ]; + for (input, expected) in tests { + assert_eq!(expected, u8::from(input)); + assert_eq!(IpDscp::try_new(expected).unwrap(), IpDscp::from(input)); + } + } +} diff --git a/etherparse/src/net/ipv4_ecn.rs b/etherparse/src/net/ip_ecn.rs similarity index 51% rename from etherparse/src/net/ipv4_ecn.rs rename to etherparse/src/net/ip_ecn.rs index f227a490..cc48eb7d 100644 --- a/etherparse/src/net/ipv4_ecn.rs +++ b/etherparse/src/net/ip_ecn.rs @@ -1,119 +1,136 @@ -use crate::err::ValueTooBigError; +#[deprecated(since = "0.18.0", note = "Please use `IpEcn` instead.")] +pub type Ipv4Ecn = IpEcn; -/// 2 bit unsigned integer containing the "Explicit Congestion -/// Notification" (present in the [`crate::Ipv4Header`]). -#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Ipv4Ecn(u8); +use crate::err::ValueTooBigError; -impl Ipv4Ecn { - /// Ipv4Ecn with value 0. - pub const ZERO: Ipv4Ecn = Ipv4Ecn(0); +/// Code points for "Explicit Congestion Notification" (ECN) present in the +/// [`crate::Ipv4Header`] and [`crate::Ipv6Header`]. +/// +/// Code points are defined in [RFC-3168](https://datatracker.ietf.org/doc/html/rfc3168) +/// +/// For reasoning to why there are two code points with the exact same meaning, +/// see [RFC-3168 Section 20.2](https://datatracker.ietf.org/doc/html/rfc3168#section-20.2) +#[repr(u8)] +#[derive(Copy, Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub enum IpEcn { + /// End node is not an ECN capable transport. + NotEct = 0b00, + /// End node is an ECN capable transport (experimental). + Ect1 = 0b01, + /// End node is an ECN capable transport. + Ect0 = 0b10, + /// Congestion is experienced by the router. + CongestionExperienced = 0b11, +} - /// Ipv4Ecn with value 0. - pub const ONE: Ipv4Ecn = Ipv4Ecn(1); +impl IpEcn { + /// IpEcn with value 0. + pub const ZERO: IpEcn = IpEcn::NotEct; - /// Ipv4Ecn with value 0. - pub const TWO: Ipv4Ecn = Ipv4Ecn(2); + /// IpEcn with value 1. + pub const ONE: IpEcn = IpEcn::Ect1; - /// Ipv4Ecn with value 0. - pub const THREE: Ipv4Ecn = Ipv4Ecn(3); + /// IpEcn with value 2. + pub const TWO: IpEcn = IpEcn::Ect0; - #[deprecated(since = "0.18.0", note = "Please use Ipv4Ecn::THREE instead.")] - /// Deprecated, use [`crate::Ipv4Ecn::THREE`] instead. - pub const TRHEE: Ipv4Ecn = Ipv4Ecn::THREE; + /// IpEcn with value 3. + pub const THREE: IpEcn = IpEcn::CongestionExperienced; - /// Maximum value of an IPv4 header ECN. + /// Maximum value of an IPv4 or IPv6 header ECN. pub const MAX_U8: u8 = 0b0000_0011; - /// Tries to create an [`Ipv4Ecn`] and checks that the passed value - /// is smaller or equal than [`Ipv4Ecn::MAX_U8`] (2 bit unsigned integer). + #[deprecated(since = "0.18.0", note = "Please use IpEcn::THREE instead.")] + /// Deprecated, use [`crate::IpEcn::THREE`] instead. + pub const TRHEE: IpEcn = IpEcn::THREE; + + /// Tries to create an [`IpEcn`] and checks that the passed value + /// is smaller or equal than [`IpEcn::MAX_U8`] (2 bit unsigned integer). /// /// In case the passed value is bigger then what can be represented in an 2 bit - /// integer an error is returned. Otherwise an `Ok` containing the [`Ipv4Ecn`]. + /// integer an error is returned. Otherwise an `Ok` containing the [`IpEcn`]. /// /// ``` - /// use etherparse::Ipv4Ecn; + /// use etherparse::IpEcn; /// - /// let ecn = Ipv4Ecn::try_new(2).unwrap(); + /// let ecn = IpEcn::try_new(2).unwrap(); /// assert_eq!(ecn.value(), 2); /// /// // if a number that can not be represented in an 2 bit integer /// // gets passed in an error is returned /// use etherparse::err::{ValueTooBigError, ValueType}; /// assert_eq!( - /// Ipv4Ecn::try_new(Ipv4Ecn::MAX_U8 + 1), + /// IpEcn::try_new(IpEcn::MAX_U8 + 1), /// Err(ValueTooBigError{ - /// actual: Ipv4Ecn::MAX_U8 + 1, - /// max_allowed: Ipv4Ecn::MAX_U8, - /// value_type: ValueType::Ipv4Ecn, + /// actual: IpEcn::MAX_U8 + 1, + /// max_allowed: IpEcn::MAX_U8, + /// value_type: ValueType::IpEcn, /// }) /// ); /// ``` #[inline] - pub const fn try_new(value: u8) -> Result> { + pub const fn try_new(value: u8) -> Result> { use crate::err::ValueType; - if value <= Ipv4Ecn::MAX_U8 { - Ok(Ipv4Ecn(value)) + if value <= IpEcn::MAX_U8 { + // SAFETY: Safe as value has been verified to be + // <= IpEcn::MAX_U8. + unsafe { Ok(Self::new_unchecked(value)) } } else { Err(ValueTooBigError { actual: value, - max_allowed: Ipv4Ecn::MAX_U8, - value_type: ValueType::Ipv4Ecn, + max_allowed: IpEcn::MAX_U8, + value_type: ValueType::IpEcn, }) } } - /// Creates an [`Ipv4Ecn`] without checking that the value - /// is smaller or equal than [`Ipv4Ecn::MAX_U8`] (2 bit unsigned integer). - /// The caller must guarantee that `value <= Ipv4Ecn::MAX_U8`. + /// Creates an [`IpEcn`] without checking that the value + /// is smaller or equal than [`IpEcn::MAX_U8`] (2 bit unsigned integer). + /// The caller must guarantee that `value <= IpEcn::MAX_U8`. /// /// # Safety /// - /// `value` must be smaller or equal than [`Ipv4Ecn::MAX_U8`] + /// `value` must be smaller or equal than [`IpEcn::MAX_U8`] /// otherwise the behavior of functions or data structures relying /// on this pre-requirement is undefined. #[inline] - pub const unsafe fn new_unchecked(value: u8) -> Ipv4Ecn { - debug_assert!(value <= Ipv4Ecn::MAX_U8); - Ipv4Ecn(value) + pub const unsafe fn new_unchecked(value: u8) -> IpEcn { + debug_assert!(value <= IpEcn::MAX_U8); + core::mem::transmute::(value) } /// Returns the underlying unsigned 2 bit value as an `u8` value. #[inline] pub const fn value(self) -> u8 { - self.0 + self as u8 + } +} + +impl core::default::Default for IpEcn { + fn default() -> Self { + IpEcn::ZERO } } -impl core::fmt::Display for Ipv4Ecn { +impl core::fmt::Display for IpEcn { #[inline] fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.0.fmt(f) + self.value().fmt(f) } } -impl From for u8 { +impl From for u8 { #[inline] - fn from(value: Ipv4Ecn) -> Self { - value.0 + fn from(value: IpEcn) -> Self { + value as u8 } } -impl TryFrom for Ipv4Ecn { +impl TryFrom for IpEcn { type Error = ValueTooBigError; #[inline] fn try_from(value: u8) -> Result { - use crate::err::ValueType; - if value <= Ipv4Ecn::MAX_U8 { - Ok(Ipv4Ecn(value)) - } else { - Err(Self::Error { - actual: value, - max_allowed: Ipv4Ecn::MAX_U8, - value_type: ValueType::Ipv4Ecn, - }) - } + Self::try_new(value) } } @@ -128,7 +145,7 @@ mod test { fn derived_traits() { // copy & clone { - let a = Ipv4Ecn(2); + let a = IpEcn::TWO; let b = a; assert_eq!(a, b); assert_eq!(a.clone(), a); @@ -136,20 +153,20 @@ mod test { // default { - let actual: Ipv4Ecn = Default::default(); + let actual: IpEcn = Default::default(); assert_eq!(actual.value(), 0); } // debug { - let a = Ipv4Ecn(2); - assert_eq!(format!("{:?}", a), format!("Ipv4Ecn(2)")); + let a = IpEcn::Ect0; + assert_eq!(format!("{:?}", a), format!("Ect0")); } // ord & partial ord { use core::cmp::Ordering; - let a = Ipv4Ecn(2); + let a = IpEcn::TWO; let b = a; assert_eq!(a.cmp(&b), Ordering::Equal); assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal)); @@ -160,12 +177,12 @@ mod test { use std::collections::hash_map::DefaultHasher; let a = { let mut hasher = DefaultHasher::new(); - Ipv4Ecn(2).hash(&mut hasher); + IpEcn::TWO.hash(&mut hasher); hasher.finish() }; let b = { let mut hasher = DefaultHasher::new(); - Ipv4Ecn(2).hash(&mut hasher); + IpEcn::TWO.hash(&mut hasher); hasher.finish() }; assert_eq!(a, b); @@ -181,14 +198,14 @@ mod test { use crate::err::{ValueType, ValueTooBigError}; assert_eq!( valid_value, - Ipv4Ecn::try_new(valid_value).unwrap().value() + IpEcn::try_new(valid_value).unwrap().value() ); assert_eq!( - Ipv4Ecn::try_new(invalid_value).unwrap_err(), + IpEcn::try_new(invalid_value).unwrap_err(), ValueTooBigError{ actual: invalid_value, max_allowed: 0b0000_0011, - value_type: ValueType::Ipv4Ecn + value_type: ValueType::IpEcn } ); } @@ -203,32 +220,32 @@ mod test { use crate::err::{ValueType, ValueTooBigError}; // try_into { - let actual: Ipv4Ecn = valid_value.try_into().unwrap(); + let actual: IpEcn = valid_value.try_into().unwrap(); assert_eq!(actual.value(), valid_value); - let err: Result> = invalid_value.try_into(); + let err: Result> = invalid_value.try_into(); assert_eq!( err.unwrap_err(), ValueTooBigError{ actual: invalid_value, max_allowed: 0b0000_0011, - value_type: ValueType::Ipv4Ecn + value_type: ValueType::IpEcn } ); } // try_from { assert_eq!( - Ipv4Ecn::try_from(valid_value).unwrap().value(), + IpEcn::try_from(valid_value).unwrap().value(), valid_value ); assert_eq!( - Ipv4Ecn::try_from(invalid_value).unwrap_err(), + IpEcn::try_from(invalid_value).unwrap_err(), ValueTooBigError{ actual: invalid_value, max_allowed: 0b0000_0011, - value_type: ValueType::Ipv4Ecn + value_type: ValueType::IpEcn } ); } @@ -241,7 +258,7 @@ mod test { assert_eq!( valid_value, unsafe { - Ipv4Ecn::new_unchecked(valid_value).value() + IpEcn::new_unchecked(valid_value).value() } ); } @@ -250,14 +267,14 @@ mod test { proptest! { #[test] fn fmt(valid_value in 0..=0b0000_0011u8) { - assert_eq!(format!("{}", Ipv4Ecn(valid_value)), format!("{}", valid_value)); + assert_eq!(format!("{}", IpEcn::try_new(valid_value).unwrap()), format!("{}", valid_value)); } } proptest! { #[test] fn from(valid_value in 0..=0b0000_0011u8,) { - let ecn = Ipv4Ecn::try_new(valid_value).unwrap(); + let ecn = IpEcn::try_new(valid_value).unwrap(); let actual: u8 = ecn.into(); assert_eq!(actual, valid_value); } diff --git a/etherparse/src/net/ip_payload_slice.rs b/etherparse/src/net/ip_payload_slice.rs index 91456e9d..07cd88d9 100644 --- a/etherparse/src/net/ip_payload_slice.rs +++ b/etherparse/src/net/ip_payload_slice.rs @@ -8,7 +8,7 @@ pub struct IpPayloadSlice<'a> { /// True if the payload is not complete and has been fragmented. /// - /// This can occur if the IPv4 incdicates that the payload + /// This can occur if the IPv4 indicates that the payload /// has been fragmented or if there is an IPv6 fragmentation /// header indicating that the payload has been fragmented. pub fragmented: bool, diff --git a/etherparse/src/net/ipv4_dscp.rs b/etherparse/src/net/ipv4_dscp.rs deleted file mode 100644 index 9498b342..00000000 --- a/etherparse/src/net/ipv4_dscp.rs +++ /dev/null @@ -1,252 +0,0 @@ -use crate::err::ValueTooBigError; - -/// 6 bit unsigned integer containing the "Differentiated Services -/// Code Point" (present in the [`crate::Ipv4Header`]). -#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Ipv4Dscp(u8); - -impl Ipv4Dscp { - /// Ipv4Dscp with value 0. - pub const ZERO: Ipv4Dscp = Ipv4Dscp(0); - - /// Maximum value of an IPv4 header DSCP. - pub const MAX_U8: u8 = 0b0011_1111; - - /// Tries to create an [`Ipv4Dscp`] and checks that the passed value - /// is smaller or equal than [`Ipv4Dscp::MAX_U8`] (6 bit unsigned integer). - /// - /// In case the passed value is bigger then what can be represented in an 6 bit - /// integer an error is returned. Otherwise an `Ok` containing the [`Ipv4Dscp`]. - /// - /// ``` - /// use etherparse::Ipv4Dscp; - /// - /// let dscp = Ipv4Dscp::try_new(32).unwrap(); - /// assert_eq!(dscp.value(), 32); - /// - /// // if a number that can not be represented in an 6 bit integer - /// // gets passed in an error is returned - /// use etherparse::err::{ValueTooBigError, ValueType}; - /// assert_eq!( - /// Ipv4Dscp::try_new(Ipv4Dscp::MAX_U8 + 1), - /// Err(ValueTooBigError{ - /// actual: Ipv4Dscp::MAX_U8 + 1, - /// max_allowed: Ipv4Dscp::MAX_U8, - /// value_type: ValueType::Ipv4Dscp, - /// }) - /// ); - /// ``` - #[inline] - pub const fn try_new(value: u8) -> Result> { - use crate::err::ValueType; - if value <= Ipv4Dscp::MAX_U8 { - Ok(Ipv4Dscp(value)) - } else { - Err(ValueTooBigError { - actual: value, - max_allowed: Ipv4Dscp::MAX_U8, - value_type: ValueType::Ipv4Dscp, - }) - } - } - - /// Creates an [`Ipv4Dscp`] without checking that the value - /// is smaller or equal than [`Ipv4Dscp::MAX_U8`] (6 bit unsigned integer). - /// The caller must guarantee that `value <= Ipv4Dscp::MAX_U8`. - /// - /// # Safety - /// - /// `value` must be smaller or equal than [`Ipv4Dscp::MAX_U8`] - /// otherwise the behavior of functions or data structures relying - /// on this pre-requirement is undefined. - #[inline] - pub const unsafe fn new_unchecked(value: u8) -> Ipv4Dscp { - debug_assert!(value <= Ipv4Dscp::MAX_U8); - Ipv4Dscp(value) - } - - /// Returns the underlying unsigned 6 bit value as an `u8` value. - #[inline] - pub const fn value(self) -> u8 { - self.0 - } -} - -impl core::fmt::Display for Ipv4Dscp { - #[inline] - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.0.fmt(f) - } -} - -impl From for u8 { - #[inline] - fn from(value: Ipv4Dscp) -> Self { - value.0 - } -} - -impl TryFrom for Ipv4Dscp { - type Error = ValueTooBigError; - - #[inline] - fn try_from(value: u8) -> Result { - use crate::err::ValueType; - if value <= Ipv4Dscp::MAX_U8 { - Ok(Ipv4Dscp(value)) - } else { - Err(Self::Error { - actual: value, - max_allowed: Ipv4Dscp::MAX_U8, - value_type: ValueType::Ipv4Dscp, - }) - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use core::hash::{Hash, Hasher}; - use proptest::prelude::*; - use std::format; - - #[test] - fn derived_traits() { - // copy & clone - { - let a = Ipv4Dscp(32); - let b = a; - assert_eq!(a, b); - assert_eq!(a.clone(), a); - } - - // default - { - let actual: Ipv4Dscp = Default::default(); - assert_eq!(actual.value(), 0); - } - - // debug - { - let a = Ipv4Dscp(32); - assert_eq!(format!("{:?}", a), format!("Ipv4Dscp(32)")); - } - - // ord & partial ord - { - use core::cmp::Ordering; - let a = Ipv4Dscp(32); - let b = a; - assert_eq!(a.cmp(&b), Ordering::Equal); - assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal)); - } - - // hash - { - use std::collections::hash_map::DefaultHasher; - let a = { - let mut hasher = DefaultHasher::new(); - Ipv4Dscp(64).hash(&mut hasher); - hasher.finish() - }; - let b = { - let mut hasher = DefaultHasher::new(); - Ipv4Dscp(64).hash(&mut hasher); - hasher.finish() - }; - assert_eq!(a, b); - } - } - - proptest! { - #[test] - fn try_new( - valid_value in 0..=0b0011_1111u8, - invalid_value in 0b0100_0000u8..=u8::MAX - ) { - use crate::err::{ValueType, ValueTooBigError}; - assert_eq!( - valid_value, - Ipv4Dscp::try_new(valid_value).unwrap().value() - ); - assert_eq!( - Ipv4Dscp::try_new(invalid_value).unwrap_err(), - ValueTooBigError{ - actual: invalid_value, - max_allowed: 0b0011_1111, - value_type: ValueType::Ipv4Dscp - } - ); - } - } - - proptest! { - #[test] - fn try_from( - valid_value in 0..=0b0011_1111u8, - invalid_value in 0b0100_0000u8..=u8::MAX - ) { - use crate::err::{ValueType, ValueTooBigError}; - // try_into - { - let actual: Ipv4Dscp = valid_value.try_into().unwrap(); - assert_eq!(actual.value(), valid_value); - - let err: Result> = invalid_value.try_into(); - assert_eq!( - err.unwrap_err(), - ValueTooBigError{ - actual: invalid_value, - max_allowed: 0b0011_1111, - value_type: ValueType::Ipv4Dscp - } - ); - } - // try_from - { - assert_eq!( - Ipv4Dscp::try_from(valid_value).unwrap().value(), - valid_value - ); - - assert_eq!( - Ipv4Dscp::try_from(invalid_value).unwrap_err(), - ValueTooBigError{ - actual: invalid_value, - max_allowed: 0b0011_1111, - value_type: ValueType::Ipv4Dscp - } - ); - } - } - } - - proptest! { - #[test] - fn new_unchecked(valid_value in 0..=0b0011_1111u8) { - assert_eq!( - valid_value, - unsafe { - Ipv4Dscp::new_unchecked(valid_value).value() - } - ); - } - } - - proptest! { - #[test] - fn fmt(valid_value in 0..=0b0011_1111u8) { - assert_eq!(format!("{}", Ipv4Dscp(valid_value)), format!("{}", valid_value)); - } - } - - proptest! { - #[test] - fn from(valid_value in 0..=0b0011_1111u8,) { - let dscp = Ipv4Dscp::try_new(valid_value).unwrap(); - let actual: u8 = dscp.into(); - assert_eq!(actual, valid_value); - } - } -} diff --git a/etherparse/src/net/ipv4_header.rs b/etherparse/src/net/ipv4_header.rs index 9854b5b3..3a332b77 100644 --- a/etherparse/src/net/ipv4_header.rs +++ b/etherparse/src/net/ipv4_header.rs @@ -20,7 +20,7 @@ use arrayvec::ArrayVec; /// ..Default::default() /// }; /// -/// // depending on your usecase you might want to set the correct checksum +/// // depending on your use case you might want to set the correct checksum /// header.header_checksum = header.calc_header_checksum(); /// /// // header can be serialized into the "on the wire" format @@ -35,9 +35,9 @@ use arrayvec::ArrayVec; #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct Ipv4Header { /// Differentiated Services Code Point - pub dscp: Ipv4Dscp, + pub dscp: IpDscp, /// Explicit Congestion Notification - pub ecn: Ipv4Ecn, + pub ecn: IpEcn, /// Total length of the IPv4 header (including extension headers) and the payload after it. pub total_len: u16, /// Number used to identify packets that contain an originally fragmented packet. @@ -463,12 +463,12 @@ impl Ipv4Header { dscp: unsafe { // Safe as only 6 bits were used to decode the // dscp value - Ipv4Dscp::new_unchecked(dscp) + IpDscp::new_unchecked(dscp) }, ecn: unsafe { // Safe as only 2 bits were used to decode the // ecn value - Ipv4Ecn::new_unchecked(ecn) + IpEcn::new_unchecked(ecn) }, total_len, identification, diff --git a/etherparse/src/net/ipv4_header_slice.rs b/etherparse/src/net/ipv4_header_slice.rs index 01c4a351..dfbbc15a 100644 --- a/etherparse/src/net/ipv4_header_slice.rs +++ b/etherparse/src/net/ipv4_header_slice.rs @@ -119,23 +119,23 @@ impl<'a> Ipv4HeaderSlice<'a> { /// Read the "differentiated_services_code_point" from the slice. #[inline] - pub fn dcp(&self) -> Ipv4Dscp { + pub fn dcp(&self) -> IpDscp { // SAFETY: // get_unchecked: Safe as the slice length is checked to be at least // Ipv4Header::MIN_LEN (20) in the constructor. // new_unchecked: Safe as the bit-shift by 2 guarantees that the passed // value is not bigger then 6 bits. - unsafe { Ipv4Dscp::new_unchecked(*self.slice.get_unchecked(1) >> 2) } + unsafe { IpDscp::new_unchecked(*self.slice.get_unchecked(1) >> 2) } } /// Read the "explicit_congestion_notification" from the slice. #[inline] - pub fn ecn(&self) -> Ipv4Ecn { + pub fn ecn(&self) -> IpEcn { // SAFETY: // get_unchecked: Safe as the slice length is checked to be at least // Ipv4Header::MIN_LEN (20) in the constructor. // new_unchecked: Safe as value has been bit-masked to two bits. - unsafe { Ipv4Ecn::new_unchecked(*self.slice.get_unchecked(1) & 0b0000_0011) } + unsafe { IpEcn::new_unchecked(*self.slice.get_unchecked(1) & 0b0000_0011) } } /// Read the "total length" from the slice (total length of ip header + payload). diff --git a/etherparse/src/net/ipv6_header.rs b/etherparse/src/net/ipv6_header.rs index 32bb0111..454d99aa 100644 --- a/etherparse/src/net/ipv6_header.rs +++ b/etherparse/src/net/ipv6_header.rs @@ -38,11 +38,11 @@ impl Ipv6Header { /// 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 + /// Note that this function DOES NOT separate 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 + /// If you want to have correctly separated payload including the IP extension /// headers use /// /// * [`crate::IpHeaders::from_ipv6_slice`] (decodes all the fields of the IP headers) @@ -326,6 +326,30 @@ impl Ipv6Header { Ok(()) } + /// Sets the ECN field in the `traffic_class` octet. + pub fn set_ecn(&mut self, ecn: IpEcn) { + self.traffic_class = (self.traffic_class & 0b1111_1100) | (ecn.value() & 0b11); + } + + /// Returns the [`IpEcn`] decoded from the `traffic_class` octet. + pub fn ecn(&self) -> IpEcn { + // SAFETY: Safe as value can only be at most 0b11 as it is bit-and-ed with 0b11. + unsafe { IpEcn::new_unchecked(self.traffic_class & 0b0000_0011) } + } + + /// Set the DSCP field in the `traffic_class` octet. + pub fn set_dscp(&mut self, dscp: IpDscp) { + self.traffic_class = + (self.traffic_class & 0b0000_0011) | ((dscp.value() << 2) & 0b1111_1100); + } + + /// Returns the [`IpDscp`] decoded from the `traffic_class` octet. + pub fn dscp(&self) -> IpDscp { + // SAFETY: Safe as value can not be bigger than IpDscp::MAX_U8 as it + // is bit masked with IpDscp::MAX_U8 (0b0011_1111). + unsafe { IpDscp::new_unchecked((self.traffic_class >> 2) & 0b0011_1111) } + } + /// Returns the serialized form of the header as a statically /// sized byte array. #[rustfmt::skip] @@ -449,6 +473,34 @@ mod test { } } + proptest! { + #[test] + fn set_dscp( + header in ipv6_any(), + dscp in ip_dscp_any() + ) { + let mut header = header; + assert_eq!(header.dscp(), IpDscp::try_new(header.traffic_class >> 2).unwrap()); + header.set_dscp(dscp); + assert_eq!(dscp, IpDscp::try_new(header.traffic_class >> 2).unwrap()); + assert_eq!(header.dscp(), dscp); + } + } + + proptest! { + #[test] + fn set_ecn( + header in ipv6_any(), + ecn in ip_ecn_any() + ) { + let mut header = header; + assert_eq!(header.ecn(), IpEcn::try_new(header.traffic_class & 0b11).unwrap()); + header.set_ecn(ecn); + assert_eq!(ecn, IpEcn::try_new(header.traffic_class & 0b11).unwrap()); + assert_eq!(header.ecn(), ecn); + } + } + proptest! { #[test] fn from_slice( diff --git a/etherparse/src/net/mod.rs b/etherparse/src/net/mod.rs index 676f9655..fb922f64 100644 --- a/etherparse/src/net/mod.rs +++ b/etherparse/src/net/mod.rs @@ -16,6 +16,15 @@ pub use ip_auth_header::*; mod ip_auth_header_slice; pub use ip_auth_header_slice::*; +mod ip_dscp; +pub use ip_dscp::*; + +mod ip_dscp_known; +pub use ip_dscp_known::*; + +mod ip_ecn; +pub use ip_ecn::*; + mod ip_frag_offset; pub use ip_frag_offset::*; @@ -31,12 +40,6 @@ pub use ip_payload_slice::*; mod ip_slice; pub use ip_slice::*; -mod ipv4_dscp; -pub use ipv4_dscp::*; - -mod ipv4_ecn; -pub use ipv4_ecn::*; - mod ipv4_exts; pub use ipv4_exts::*; diff --git a/etherparse/src/test_gens/mod.rs b/etherparse/src/test_gens/mod.rs index 82bbf5e3..ad5871ea 100644 --- a/etherparse/src/test_gens/mod.rs +++ b/etherparse/src/test_gens/mod.rs @@ -65,6 +65,24 @@ prop_compose! { } } +prop_compose! { + pub fn ip_ecn_any() + (value in 0u8..=0b0000_0011) + -> IpEcn + { + IpEcn::try_new(value).unwrap() + } +} + +prop_compose! { + pub fn ip_dscp_any() + (value in 0u8..=0b0011_1111) + -> IpDscp + { + IpDscp::try_new(value).unwrap() + } +} + prop_compose! { pub fn ipv6_flow_label_any() (value in 0u32..=0b1111_11111111_11111111u32) @@ -118,7 +136,7 @@ prop_compose! { } prop_compose! { - pub fn linux_sll_arphrd() + pub fn linux_sll_arp_hrd_type_any() (index in 0..=(LinuxSllProtocolType::SUPPORTED_ARPHWD.len()-1)) -> ArpHardwareId { @@ -127,7 +145,7 @@ prop_compose! { } prop_compose! { - pub fn linux_sll_sender_adress_any() + pub fn linux_sll_sender_address_any() (mut sender_address in prop::collection::vec(any::(), 0..8)) -> (u16, [u8; 8]) { @@ -143,8 +161,8 @@ prop_compose! { prop_compose! { pub fn linux_sll_any() (packet_type in linux_sll_packet_type_any(), - arp_hrd_type in linux_sll_arphrd(), - (sender_address_valid_length, sender_address) in linux_sll_sender_adress_any(), + arp_hrd_type in linux_sll_arp_hrd_type_any(), + (sender_address_valid_length, sender_address) in linux_sll_sender_address_any(), protocol_num in any::() ) -> LinuxSllHeader diff --git a/etherparse_proptest_generators/src/lib.rs b/etherparse_proptest_generators/src/lib.rs index cd10ddd8..17306597 100644 --- a/etherparse_proptest_generators/src/lib.rs +++ b/etherparse_proptest_generators/src/lib.rs @@ -6,8 +6,8 @@ pub fn err_field_any() -> impl Strategy { use err::ValueType::*; prop_oneof![ Just(Ipv4PayloadLength), - Just(Ipv4Dscp), - Just(Ipv4Ecn), + Just(IpDscp), + Just(IpEcn), Just(IpFragmentOffset), Just(Ipv6FlowLabel), ]