From 1f1a75dbc30e140455a9cd6b11cf1c8f55d9da41 Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Tue, 1 Jul 2025 12:42:18 -0500 Subject: [PATCH 01/11] Update vxlan.rs (#58) Introduce robust and RFC-compliant structures for Virtual eXtensible Local Area Network (RFC 7348) and headers, significantly improving VXLAN message handling. Key improvements include: * Adding vni_and_reserved2 Attribute * Removed BitfieldUnit * Added implementation method * Added Unit Tests Co-authored-by: Sven Cowart --- src/vxlan.rs | 244 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 228 insertions(+), 16 deletions(-) diff --git a/src/vxlan.rs b/src/vxlan.rs index 1b3fc27..8caaa71 100644 --- a/src/vxlan.rs +++ b/src/vxlan.rs @@ -1,33 +1,245 @@ use core::mem; -use crate::bitfield::BitfieldUnit; - -/// VXLAN header, which is present at the beginning of every UDP payload containing VXLAN packets. +/// VXLAN (Virtual eXtensible Local Area Network) header. +/// +/// Encapsulates OSI layer 2 Ethernet frames within layer 4 UDP packets. +/// Uses a 24-bit VXLAN Network Identifier (VNI) for traffic segregation. +/// Header length: 8 bytes. +/// Reference: RFC 7348. #[repr(C, packed)] #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] pub struct VxlanHdr { - /// VXLAN flags. See [`VxlanHdr::vni_valid`] and [`VxlanHdr::set_vni_valid`]. - pub flags: BitfieldUnit<[u8; 1usize]>, - pub _reserved: [u8; 3], - /// VXLAN Virtual Network Identifier. - /// - /// This is a 24-bit number combined with reserved bytes, see [`VxlanHdr::vni`] and - /// [`VxlanHdr::set_vni`]. + /// Flags (8 bits). Bit 3 (I flag) must be 1 if VNI is present. Other bits are reserved (R). + pub flags: u8, + /// Reserved field (24 bits). Must be zero on transmission. + pub _reserved1: [u8; 3], + /// Contains the 24-bit VNI (upper 3 bytes) and an 8-bit reserved field (the lowest byte). + /// The reserved field (the lowest byte) must be zero on transmission. pub vni: [u8; 3], - pub _padding: u8, + pub _reserved2: u8, } +/// Mask for the I-flag (VNI Present flag, bit 3) in the `flags` field. +pub const VXLAN_I_FLAG_MASK: u8 = 0x08; + impl VxlanHdr { - pub const LEN: usize = mem::size_of::(); + /// Length of the VXLAN header in bytes (8 bytes). + pub const LEN: usize = mem::size_of::(); + + /// Creates a new `VxlanHdr`. + /// + /// Sets the I-flag, zeros reserved fields, and sets the VNI. + /// + /// # Parameters + /// - `vni`: The 24-bit VXLAN Network Identifier. + /// + /// # Returns + /// A new `VxlanHdr` instance. + pub fn new(vni: u32) -> Self { + let mut hdr = VxlanHdr { + flags: VXLAN_I_FLAG_MASK, + _reserved1: [0u8; 3], + vni: [0u8; 3], + _reserved2: 0u8, + }; + hdr.set_vni(vni); + hdr + } + + /// Creates a new `VxlanHdr`. + /// + /// Sets the I-flag, zeros reserved fields, and sets the VNI. + /// + /// # Parameters + /// - `flags`: The 8-bit value to set for the flag field. + /// - `vni`: The 24-bit VXLAN Network Identifier. + /// + /// # Returns + /// A new `VxlanHdr` instance. + pub fn with_flags(flags: u8, vni: [u8; 3]) -> Self { + Self { + flags, + _reserved1: [0; 3], + vni, + _reserved2: 0, + } + } + + /// Returns the raw flags' byte. + /// + /// # Returns + /// The 8-bit flags field. + #[inline] + pub fn flags(&self) -> u8 { + self.flags + } + + /// Sets the raw flags byte. + /// + /// # Parameters + /// - `flags`: The 8-bit value to set for the flags field. + #[inline] + pub fn set_flags(&mut self, flags: u8) { + self.flags = flags; + } + + /// Checks if the I-flag (VNI Present) is set. + /// + /// # Returns + /// `true` if the I-flag is set, `false` otherwise. + #[inline] + pub fn vni_present(&self) -> bool { + (self.flags & VXLAN_I_FLAG_MASK) == VXLAN_I_FLAG_MASK + } + /// Sets or clears the I-flag (VNI Present). + /// + /// Preserves other flag bits. + /// + /// # Parameters + /// - `present`: If `true`, sets the I-flag; otherwise, clears it. #[inline] - pub fn vni_valid(&self) -> bool { - self.flags.get_bit(4) + pub fn set_vni_present(&mut self, present: bool) { + if present { + self.flags |= VXLAN_I_FLAG_MASK; + } else { + self.flags &= !VXLAN_I_FLAG_MASK; + } } + /// Returns the VXLAN Network Identifier (VNI). + /// + /// # Returns + /// The 24-bit VNI as a `u32`. + #[inline] + pub fn vni(&self) -> u32 { + u32::from_be_bytes([0, self.vni[0], self.vni[1], self.vni[2]]) + } + + /// Sets the VXLAN Network Identifier (VNI). + /// + /// Masks the input `vni` to 24 bits. Preserves the `reserved2` field. + /// + /// # Parameters + /// - `vni`: The 24-bit VNI value. #[inline] - pub fn set_vni_valid(&mut self, val: bool) { - self.flags.set_bit(4, val) + pub fn set_vni(&mut self, vni: u32) { + let vni_24bit = vni & 0x00FF_FFFF; + let vni_bytes = vni_24bit.to_be_bytes(); + self.vni[0] = vni_bytes[1]; + self.vni[1] = vni_bytes[2]; + self.vni[2] = vni_bytes[3]; + } +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vxlanhdr_len() { + assert_eq!(VxlanHdr::LEN, 8, "VXLAN header length should be 8 bytes"); + } + + #[test] + fn test_vxlanhdr_new() { + let vni_val: u32 = 0xABCDEF; + let hdr = VxlanHdr::new(vni_val); + assert_eq!( + hdr.flags(), + VXLAN_I_FLAG_MASK, + "Flags should have I-flag set and others zero" + ); + assert!(hdr.vni_present(), "VNI present flag should be set by new()"); + assert_eq!(hdr.vni(), vni_val, "VNI should be set correctly by new()"); + } + + #[test] + fn test_flags_management() { + let mut hdr = VxlanHdr::new(0x123); + hdr.set_flags(0xFF); + assert_eq!(hdr.flags(), 0xFF); + assert!( + hdr.vni_present(), + "I-flag should be set if flags byte is 0xFF" + ); + hdr.set_flags(0x00); + assert!( + !hdr.vni_present(), + "I-flag should be clear if flags byte is 0x00" + ); + hdr.set_vni_present(true); + assert_eq!( + hdr.flags(), + VXLAN_I_FLAG_MASK, + "set_vni_present(true) should set I-flag" + ); + assert!(hdr.vni_present()); + hdr.set_vni_present(false); + assert_eq!( + hdr.flags(), + 0x00, + "set_vni_present(false) should clear I-flag" + ); + assert!(!hdr.vni_present()); + hdr.set_flags(0xF0); + assert!(!hdr.vni_present()); + hdr.set_vni_present(true); + assert_eq!( + hdr.flags(), + 0xF0 | VXLAN_I_FLAG_MASK, + "Setting I-flag should preserve other bits" + ); + assert!(hdr.vni_present()); + hdr.set_vni_present(false); + assert_eq!( + hdr.flags(), + 0xF0 & !VXLAN_I_FLAG_MASK, + "Clearing I-flag should preserve other bits" + ); + assert!(!hdr.vni_present()); + } + + #[test] + fn test_vni_management() { + let mut hdr = VxlanHdr::new(0); + let vni_val: u32 = 0xABCDEF; + hdr.set_vni(vni_val); + assert_eq!(hdr.vni(), vni_val, "VNI should be set correctly"); + let large_vni: u32 = 0x12ABCDEF; + hdr.set_vni(large_vni); + assert_eq!(hdr.vni(), 0xABCDEF, "VNI should be masked to 24 bits"); + hdr.set_vni(0); + assert_eq!(hdr.vni(), 0, "VNI should be settable to 0"); + let max_vni: u32 = 0xFFFFFF; + hdr.set_vni(max_vni); + assert_eq!(hdr.vni(), max_vni, "Max 24-bit VNI should be settable"); + hdr.set_vni(0x123456); + assert_eq!(hdr.vni(), 0x123456, "VNI should be updated"); + } + + #[test] + fn test_field_storage_and_retrieval_direct_manipulation() { + let mut hdr = VxlanHdr::with_flags(0x08, [0xAB, 0xCD, 0xEF]); + hdr.flags = 0x08; + hdr.vni = [0xAB, 0xCD, 0xEF]; + assert_eq!(hdr.flags(), 0x08); + assert!(hdr.vni_present()); + assert_eq!(hdr.vni(), 0xABCDEF); + hdr.set_vni_present(false); + assert_eq!( + hdr.flags, 0x00, + "Direct field check after set_vni_present(false)" + ); + hdr.set_vni(0x654321); + assert_eq!(hdr.vni[0], 0x65, "Byte 0 of vni after set_vni"); + assert_eq!(hdr.vni[1], 0x43, "Byte 1 of vni after set_vni"); + assert_eq!(hdr.vni[2], 0x21, "Byte 2 of vni after set_vni"); + assert_eq!(hdr.vni(), 0x654321); + assert_eq!( + hdr.vni[0], 0x65, + "Byte 0 of vni preserved after set_reserved2" + ); + assert_eq!(hdr.vni(), 0x654321); } } From 993395c4b170eab033acd5822692918a9b9e6b85 Mon Sep 17 00:00:00 2001 From: Veronica Manchola Date: Tue, 1 Jul 2025 13:45:08 -0400 Subject: [PATCH 02/11] Adds support for GENEVE (#67) * **`GeneveHdr` struct**: A `#[repr(C, packed)]` struct is defined to accurately model the Geneve header format, ensuring correct memory layout. * **Field Accessors**: Provides `get` and `set` methods for all fields of the Geneve header, including: * `ver` (Version) * `opt_len` (Option Length) * `o_flag` (OAM Flag) * `c_flag` (Critical Options Present Flag) * `protocol_type` (Encapsulated Protocol Type, handled with network byte order) * `vni` (Virtual Network Identifier, handled with network byte order and 24-bit masking) * **Bitfield Handling**: The methods correctly handle bitfield extraction and insertion for `ver`, `opt_len`, `o_flag`, and `c_flag`. * **Network Byte Order**: `protocol_type` and `vni` conversions correctly handle big-endian (network byte order). * **Unit Tests**: Comprehensive unit tests are included to verify the correct behavior of all getters, setters, bitfield operations, and byte order conversions. This addition provides foundational support for parsing and constructing Geneve encapsulated packets within the project. --- src/geneve.rs | 261 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 262 insertions(+) create mode 100644 src/geneve.rs diff --git a/src/geneve.rs b/src/geneve.rs new file mode 100644 index 0000000..8304cfc --- /dev/null +++ b/src/geneve.rs @@ -0,0 +1,261 @@ +/// Represents a Geneve (Generic Network Virtualization Encapsulation) header, according to RFC 8926. +/// Geneve is an encapsulation protocol designed for network virtualization. + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Default)] + +pub struct GeneveHdr { + /// Combined field: Version (2 bits) and Option Length (6 bits). + pub ver_opt_len: u8, + /// Combined field: OAM flag (1 bit), Critical flag (1 bit), Reserved (6 bits). + pub o_c_rsvd: u8, + /// Protocol Type of the encapsulated payload (16 bits). + pub protocol_type: [u8; 2], + /// Virtual Network Identifier (VNI) (24 bits). + pub vni: [u8; 3], + /// Reserved field (8 bits). MUST be zero on transmission. + pub reserved2: u8, +} + +impl GeneveHdr { + /// The length of the Geneve header in bytes. + pub const LEN: usize = core::mem::size_of::(); + + /// Returns the Geneve protocol version (2 bits). + /// + /// According to RFC 8926, the current version is 0. + #[inline] + pub fn ver(&self) -> u8 { + (self.ver_opt_len >> 6) & 0x03 + } + + /// Sets the Geneve protocol version (2 bits). + /// + /// `ver` should be a 2-bit value (0-3). + #[inline] + pub fn set_ver(&mut self, ver: u8) { + let preserved_bits = self.ver_opt_len & 0x3F; + self.ver_opt_len = preserved_bits | ((ver & 0x03) << 6); + } + + /// Returns the length of the option fields in 4-byte multiples (6 bits). + #[inline] + pub fn opt_len(&self) -> u8 { + self.ver_opt_len & 0x3F + } + + /// Sets the length of the option fields (6 bits). + /// + /// `opt_len` should be a 6-bit value (0-63). + #[inline] + pub fn set_opt_len(&mut self, opt_len: u8) { + let preserved_bits = self.ver_opt_len & 0xC0; + self.ver_opt_len = preserved_bits | (opt_len & 0x3F); + } + + /// Returns the OAM (Operations, Administration, and Maintenance) packet flag (1 bit). + /// + /// If set (1), this packet is an OAM packet. Referred to as 'O' bit in RFC 8926. + #[inline] + pub fn o_flag(&self) -> u8 { + (self.o_c_rsvd >> 7) & 0x01 + } + + /// Sets the OAM packet flag (1 bit). + /// + /// `o_flag` should be a 1-bit value (0 or 1). + #[inline] + pub fn set_o_flag(&mut self, o_flag: u8) { + let preserved_bits = self.o_c_rsvd & 0x7F; + self.o_c_rsvd = preserved_bits | ((o_flag & 0x01) << 7); + } + + /// Returns the Critical Options Present flag (1 bit). + /// + /// If set (1), one or more options are marked as critical. Referred to as 'C' bit in RFC 8926. + #[inline] + pub fn c_flag(&self) -> u8 { + (self.o_c_rsvd >> 6) & 0x01 + } + + /// Sets the Critical Options Present flag (1 bit). + /// + /// `c_flag` should be a 1-bit value (0 or 1). + #[inline] + pub fn set_c_flag(&mut self, c_flag: u8) { + let preserved_bits = self.o_c_rsvd & 0xBF; + self.o_c_rsvd = preserved_bits | ((c_flag & 0x01) << 6); + } + + /// Returns the Protocol Type of the encapsulated payload (16 bits, network byte order). + /// + /// This follows the Ethertype convention. + #[inline] + pub fn protocol_type(&self) -> u16 { + u16::from_be_bytes(self.protocol_type) + } + + /// Sets the Protocol Type (16 bits). + /// + /// The value is stored in network byte order. + #[inline] + pub fn set_protocol_type(&mut self, protocol_type: u16) { + self.protocol_type = protocol_type.to_be_bytes(); + } + + /// Returns the Virtual Network Identifier (VNI) (24 bits). + #[inline] + pub fn vni(&self) -> u32 { + u32::from_be_bytes([0, self.vni[0], self.vni[1], self.vni[2]]) + } + + /// Sets the Virtual Network Identifier (VNI) (24 bits). + /// + /// `vni` should be a 24-bit value. Higher bits are masked. + /// The value is stored in network byte order. + #[inline] + pub fn set_vni(&mut self, vni: u32) { + let vni_val = vni & 0x00FFFFFF; + let bytes = vni_val.to_be_bytes(); + self.vni[0] = bytes[1]; + self.vni[1] = bytes[2]; + self.vni[2] = bytes[3] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_len() { + assert_eq!(GeneveHdr::LEN, 8); + } + + #[test] + fn test_default() { + let hdr = GeneveHdr::default(); + assert_eq!(hdr.ver_opt_len, 0); + assert_eq!(hdr.o_c_rsvd, 0); + assert_eq!(hdr.protocol_type, [0, 0]); + assert_eq!(hdr.vni, [0, 0, 0]); + assert_eq!(hdr.reserved2, 0); + + assert_eq!(hdr.ver(), 0); + assert_eq!(hdr.opt_len(), 0); + assert_eq!(hdr.o_flag(), 0); + assert_eq!(hdr.c_flag(), 0); + assert_eq!(hdr.protocol_type(), 0); + assert_eq!(hdr.vni(), 0); + } + + #[test] + fn test_ver() { + let mut hdr = GeneveHdr::default(); + hdr.set_ver(0b10); // Version 2 + assert_eq!(hdr.ver(), 0b10); + assert_eq!(hdr.ver_opt_len, 0b10000000, "Raw byte for ver failed"); + + hdr.set_ver(0b111); // Input 7 (3 bits), should be masked to 0b11 (3) + assert_eq!(hdr.ver(), 0b11); + assert_eq!(hdr.ver_opt_len, 0b11000000, "Masking for ver failed"); + + // Test interaction with opt_len + hdr.ver_opt_len = 0; // Reset + hdr.set_opt_len(0x3F); // Max opt_len (all lower 6 bits set) + hdr.set_ver(0b01); + assert_eq!(hdr.ver(), 0b01); + assert_eq!(hdr.opt_len(), 0x3F, "opt_len altered by set_ver"); + assert_eq!(hdr.ver_opt_len, 0b01111111, "Interaction with opt_len failed"); + } + + #[test] + fn test_opt_len() { + let mut hdr = GeneveHdr::default(); + hdr.set_opt_len(0x2A); // 42 + assert_eq!(hdr.opt_len(), 0x2A); + assert_eq!(hdr.ver_opt_len, 0b00101010, "Raw byte for opt_len failed"); + + hdr.set_opt_len(0xFF); // Input 255, should be masked to 0x3F (63) + assert_eq!(hdr.opt_len(), 0x3F); + assert_eq!(hdr.ver_opt_len, 0b00111111, "Masking for opt_len failed"); + + // Test interaction with ver + hdr.ver_opt_len = 0; // Reset + hdr.set_ver(0b11); // Max ver (top 2 bits set) + hdr.set_opt_len(0x15); // 21 + assert_eq!(hdr.ver(), 0b11, "ver altered by set_opt_len"); + assert_eq!(hdr.opt_len(), 0x15); + assert_eq!(hdr.ver_opt_len, 0b11010101, "Interaction with ver failed"); + } + + #[test] + fn test_o_flag() { + let mut hdr = GeneveHdr::default(); + hdr.set_o_flag(1); + assert_eq!(hdr.o_flag(), 1); + assert_eq!(hdr.o_c_rsvd, 0b10000000, "Raw byte for o_flag failed"); + + // The implementation correctly masks the input, so an input of 2 (0b10) becomes 0. + // This sets the o_flag back to 0. + hdr.set_o_flag(0b10); + assert_eq!(hdr.o_flag(), 0); + assert_eq!(hdr.o_c_rsvd, 0b00000000, "Masking for o_flag failed"); + + // Test that setting the O flag preserves the C flag and reserved bits. + hdr.o_c_rsvd = 0; // Reset + hdr.set_c_flag(1); // o_c_rsvd is now 0b01000000 + + // Now, set the O flag. + hdr.set_o_flag(1); + + // Verify that only the O flag bit changed. + assert_eq!(hdr.o_flag(), 1, "o_flag should be 1"); + assert_eq!(hdr.c_flag(), 1, "c_flag should be preserved"); + } + + #[test] + fn test_c_flag() { + let mut hdr = GeneveHdr::default(); + hdr.set_c_flag(1); + assert_eq!(hdr.c_flag(), 1); + assert_eq!(hdr.o_c_rsvd, 0b01000000, "Raw byte for c_flag failed"); + + // The implementation correctly masks the input, so an input of 2 (0b10) becomes 0. + // This sets the c_flag back to 0. + hdr.set_c_flag(0b10); + assert_eq!(hdr.c_flag(), 0); + assert_eq!(hdr.o_c_rsvd, 0b00000000, "Masking for c_flag failed"); + + // Test that setting the C flag preserves the O flag and reserved bits. + hdr.o_c_rsvd = 0; // Reset + hdr.set_o_flag(1); // o_c_rsvd is now 0b10000000 + + // Now, set the C flag. + hdr.set_c_flag(1); + + // Verify that only the C flag bit changed. + assert_eq!(hdr.c_flag(), 1, "c_flag should be 1"); + assert_eq!(hdr.o_flag(), 1, "o_flag should be preserved"); + } + + #[test] + fn test_protocol_type() { + let mut hdr = GeneveHdr::default(); + hdr.set_protocol_type(0xABCD); + assert_eq!(hdr.protocol_type(), 0xABCD); + assert_eq!(hdr.protocol_type, [0xAB, 0xCD], "Raw bytes for protocol_type failed (Big Endian check)"); + } + + #[test] + fn test_vni() { + let mut hdr = GeneveHdr::default(); + hdr.set_vni(0x00123456); + assert_eq!(hdr.vni(), 0x00123456); + assert_eq!(hdr.vni, [0x12, 0x34, 0x56], "Raw bytes for VNI failed (Big Endian check)"); + + hdr.set_vni(0xFF123456); // Input with >24 bits + assert_eq!(hdr.vni(), 0x00123456, "Masking for VNI failed"); + assert_eq!(hdr.vni, [0x12, 0x34, 0x56], "Raw bytes after VNI masking failed"); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index b5df8b5..b4f4bd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,3 +14,4 @@ pub mod udp; pub mod vlan; pub mod vxlan; pub mod llc; +pub mod geneve; From 976026462f56d0d13c3c34771e16077c128f21f6 Mon Sep 17 00:00:00 2001 From: Michal Rostecki Date: Mon, 14 Jul 2025 08:28:12 +0200 Subject: [PATCH 03/11] chore: Fix rustfmt errors (#70) --- src/bitfield.rs | 10 ++-------- src/geneve.rs | 25 ++++++++++++++++++++----- src/lib.rs | 4 ++-- src/llc.rs | 18 +++++++++--------- src/mac.rs | 2 +- src/mpls.rs | 3 +-- src/udp.rs | 2 +- 7 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/bitfield.rs b/src/bitfield.rs index e93b727..e2640df 100644 --- a/src/bitfield.rs +++ b/src/bitfield.rs @@ -63,10 +63,7 @@ where pub fn get(&self, bit_offset: usize, bit_width: u8) -> u64 { debug_assert!(bit_width <= 64); debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); - debug_assert!( - (bit_offset + (bit_width as usize)) / 8 <= - self.storage.as_ref().len() - ); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); let mut val = 0; @@ -88,10 +85,7 @@ where pub fn set(&mut self, bit_offset: usize, bit_width: u8, val: u64) { debug_assert!(bit_width <= 64); debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); - debug_assert!( - (bit_offset + (bit_width as usize)) / 8 <= - self.storage.as_ref().len() - ); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); for i in 0..(bit_width as usize) { let mask = 1 << i; diff --git a/src/geneve.rs b/src/geneve.rs index 8304cfc..ad39270 100644 --- a/src/geneve.rs +++ b/src/geneve.rs @@ -166,7 +166,10 @@ mod tests { hdr.set_ver(0b01); assert_eq!(hdr.ver(), 0b01); assert_eq!(hdr.opt_len(), 0x3F, "opt_len altered by set_ver"); - assert_eq!(hdr.ver_opt_len, 0b01111111, "Interaction with opt_len failed"); + assert_eq!( + hdr.ver_opt_len, 0b01111111, + "Interaction with opt_len failed" + ); } #[test] @@ -244,7 +247,11 @@ mod tests { let mut hdr = GeneveHdr::default(); hdr.set_protocol_type(0xABCD); assert_eq!(hdr.protocol_type(), 0xABCD); - assert_eq!(hdr.protocol_type, [0xAB, 0xCD], "Raw bytes for protocol_type failed (Big Endian check)"); + assert_eq!( + hdr.protocol_type, + [0xAB, 0xCD], + "Raw bytes for protocol_type failed (Big Endian check)" + ); } #[test] @@ -252,10 +259,18 @@ mod tests { let mut hdr = GeneveHdr::default(); hdr.set_vni(0x00123456); assert_eq!(hdr.vni(), 0x00123456); - assert_eq!(hdr.vni, [0x12, 0x34, 0x56], "Raw bytes for VNI failed (Big Endian check)"); + assert_eq!( + hdr.vni, + [0x12, 0x34, 0x56], + "Raw bytes for VNI failed (Big Endian check)" + ); hdr.set_vni(0xFF123456); // Input with >24 bits assert_eq!(hdr.vni(), 0x00123456, "Masking for VNI failed"); - assert_eq!(hdr.vni, [0x12, 0x34, 0x56], "Raw bytes after VNI masking failed"); + assert_eq!( + hdr.vni, + [0x12, 0x34, 0x56], + "Raw bytes after VNI masking failed" + ); } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index b4f4bd2..5371632 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,10 @@ pub mod arp; pub mod bitfield; pub mod eth; +pub mod geneve; pub mod icmp; pub mod ip; +pub mod llc; pub mod mac; pub mod mpls; pub mod sctp; @@ -13,5 +15,3 @@ pub mod tcp; pub mod udp; pub mod vlan; pub mod vxlan; -pub mod llc; -pub mod geneve; diff --git a/src/llc.rs b/src/llc.rs index 730ef13..ba07f8d 100644 --- a/src/llc.rs +++ b/src/llc.rs @@ -19,15 +19,15 @@ pub struct LlcHdr { /// Represents the type of LLC PDU based on its control field. #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum LlcFrameType { - I, // Information - S, // Supervisory - U, // Unnumbered + I, // Information + S, // Supervisory + U, // Unnumbered Invalid, // Should not happen with valid LLC frames } impl LlcHdr { pub const LEN: usize = mem::size_of::(); - + /// Gets the 7-bit DSAP address part. #[inline] pub fn dsap_addr(&self) -> u8 { @@ -55,7 +55,7 @@ impl LlcHdr { pub fn set_dsap(&mut self, addr: u8, is_group: bool) { self.dsap = ((addr & 0x7F) << 1) | (is_group as u8); } - + /// Gets the 7-bit SSAP address part. #[inline] pub fn ssap_address(&self) -> u8 { @@ -104,13 +104,13 @@ impl LlcHdr { pub fn is_i_format(&self) -> bool { self.frame_type() == LlcFrameType::I } - + /// Returns true if the control field is S-format (16 bits). #[inline] pub fn is_s_format(&self) -> bool { self.frame_type() == LlcFrameType::S } - + /// Returns true if the control field is U-format (8 bits). #[inline] pub fn is_u_format(&self) -> bool { @@ -188,7 +188,7 @@ mod tests { // Test setting with address larger than 7 bits (should be masked) llc.set_ssap(0b10101010, true); // Address 0xAA (should become 0x2A), Response - assert_eq!(llc.ssap_address(), 0x2A); // 0b0101010 + assert_eq!(llc.ssap_address(), 0x2A); // 0b0101010 assert!(llc.ssap_is_response()); assert_eq!(llc.ssap, (0x2A << 1) | 0x01); // 0x55 } @@ -248,7 +248,7 @@ mod tests { assert_eq!(llc.control_byte1(), Some(0x07)); llc.ctrl[0] = 0x0D; // Example: 00001001 -> is 0x09 - // LSBs 01 -> 00001101 is 0x0D + // LSBs 01 -> 00001101 is 0x0D llc.ctrl[0] = 0x09; // (000010_01) llc.ctrl[1] = 0x00; assert_eq!(llc.frame_type(), LlcFrameType::S); diff --git a/src/mac.rs b/src/mac.rs index d3261d9..c5ad200 100644 --- a/src/mac.rs +++ b/src/mac.rs @@ -202,4 +202,4 @@ impl MacHdr { }); bitfield_unit } -} \ No newline at end of file +} diff --git a/src/mpls.rs b/src/mpls.rs index 993461f..7681b97 100644 --- a/src/mpls.rs +++ b/src/mpls.rs @@ -15,7 +15,6 @@ pub struct Mpls { pub ttl: u8, } - impl Mpls { pub const LEN: usize = mem::size_of::(); @@ -173,4 +172,4 @@ mod tests { assert_eq!(mpls_header.ttl(), 0xFF); assert_eq!(mpls_bytes, [0x12, 0x34, 0x56, 0xFF]); } -} \ No newline at end of file +} diff --git a/src/udp.rs b/src/udp.rs index 2f92833..5b0871a 100644 --- a/src/udp.rs +++ b/src/udp.rs @@ -249,7 +249,7 @@ mod test { assert_eq!(udp_hdr.len(), u16::MAX); assert_eq!(udp_hdr.len, [0xFF, 0xFF]); } - + #[test] fn test_empty() { let mut udp_hdr = UdpHdr { From 62bfb9da668c43d0dd8958c87a39690bd8e44b56 Mon Sep 17 00:00:00 2001 From: "Keli (Madison) Grubb" Date: Mon, 14 Jul 2025 02:30:39 -0400 Subject: [PATCH 04/11] test: Multi-target CI Stages (#62) * Update `actions/checkout` action. * Use `dtolnay/rust-toolchain` action instead of `actions-rs`. * Run rustfmt and clippy. * Add cross targets. Co-authored-by: Michal Rostecki --- .github/workflows/test.yml | 62 ++++++++++++++++++++++++++++++++------ README.md | 2 -- src/geneve.rs | 2 +- src/lib.rs | 2 +- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 87c8e8c..eaaeb0e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,16 +12,60 @@ on: jobs: test: runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + - beta + - nightly + target: + - aarch64-unknown-linux-gnu + - aarch64-unknown-linux-musl + - riscv64gc-unknown-linux-gnu + - x86_64-unknown-linux-gnu + - x86_64-unknown-linux-musl + env: + CARGO_BUILD_TARGET: ${{ matrix.target }} + RUST_BACKTRACE: full steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true + - name: Install cross-compilation tools + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu gcc-riscv64-linux-gnu musl-tools - - name: Test - env: - RUST_BACKTRACE: full + - name: Configure Cargo for cross-compilation run: | - cargo test --verbose + mkdir -p .cargo + cat > .cargo/config.toml < Date: Tue, 10 Jun 2025 17:14:17 -0400 Subject: [PATCH 05/11] improve arp implementation with all header fields --- src/arp.rs | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 215 insertions(+), 9 deletions(-) diff --git a/src/arp.rs b/src/arp.rs index faf2a11..be4bfae 100644 --- a/src/arp.rs +++ b/src/arp.rs @@ -5,17 +5,223 @@ use core::mem; #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] pub struct ArpHdr { - pub htype: [u8; 2], - pub ptype: [u8; 2], - pub hlen: u8, - pub plen: u8, - pub oper: [u8; 2], - pub sha: [u8; 6], - pub spa: [u8; 4], - pub tha: [u8; 6], - pub tpa: [u8; 4], + pub hardware_type: u16, + pub protocol_type: u16, + pub hardware_length: u8, + pub protocol_length: u8, + pub operation: u16, + pub sender_hardware_address: [u8; 6], + pub sender_protocol_address: [u8; 4], + pub target_hardware_address: [u8; 6], + pub target_protocol_address: [u8; 4], } impl ArpHdr { pub const LEN: usize = mem::size_of::(); + + // Returns the hardware type field. + #[inline] + pub fn hardware_type(&self) -> u16 { + self.hardware_type + } + + // Sets the hardware type field. + #[inline] + pub fn set_hardware_type(&mut self, hardware_type: u16) { + self.hardware_type = hardware_type; + } + + // Returns the protocol type field. + #[inline] + pub fn protocol_type(&self) -> u16 { + self.protocol_type + } + + // Sets the protocol type field. + #[inline] + pub fn set_protocol_type(&mut self, protocol_type: u16) { + self.protocol_type = protocol_type; + } + + // Returns the hardware length field. + #[inline] + pub fn hardware_length(&self) -> u8 { + self.hardware_length + } + + // Sets the hardware length field. + #[inline] + pub fn set_hardware_length(&mut self, hardware_length: u8) { + self.hardware_length = hardware_length; + } + + // Returns the protocol length field. + #[inline] + pub fn protocol_length(&self) -> u8 { + self.protocol_length + } + + // Sets the protocol length field. + #[inline] + pub fn set_protocol_length(&mut self, protocol_length: u8) { + self.protocol_length = protocol_length; + } + + // Returns the operation field. + #[inline] + pub fn operation(&self) -> u16 { + self.operation + } + + // Sets the operation field. + #[inline] + pub fn set_operation(&mut self, operation: u16) { + self.operation = operation + } + + // Returns the sender hardware address field. + #[inline] + pub fn sender_hardware_address(&self) -> [u8; 6] { + self.sender_hardware_address + } + + // Sets the sender hardware address field. + #[inline] + pub fn set_sender_hardware_address(&mut self, hardware_address: [u8; 6]) { + self.sender_hardware_address = hardware_address + } + + // Returns the sender protocol address field. + #[inline] + pub fn sender_protocol_address(&self) -> [u8; 4] { + self.sender_protocol_address + } + + // Sets the sender protocol address field. + #[inline] + pub fn set_sender_protocol_address(&mut self, protocol_address: [u8; 4]) { + self.sender_protocol_address = protocol_address + } + + // Returns the target hardware address field. + #[inline] + pub fn target_hardware_address(&self) -> [u8; 6] { + self.target_hardware_address + } + + // Sets the target hardware address field. + #[inline] + pub fn set_target_hardware_address(&mut self, hardware_address: [u8; 6]) { + self.target_hardware_address = hardware_address + } + + // Returns the target protocol address field. + #[inline] + pub fn target_protocol_address(&self) -> [u8; 4] { + self.target_protocol_address + } + + // Sets the target protocol address field. + #[inline] + pub fn set_target_protocol_address(&mut self, protocol_address: [u8; 4]) { + self.target_protocol_address = protocol_address + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn default_arp_hdr() -> ArpHdr { + ArpHdr { + hardware_type: 0, + protocol_type: 0, + hardware_length: 0, + protocol_length: 0, + operation: 0, + sender_hardware_address: [0; 6], + sender_protocol_address: [0; 4], + target_hardware_address: [0; 6], + target_protocol_address: [0; 4], + } + } + + #[test] + fn test_len_constant() { + assert_eq!(ArpHdr::LEN, 28); + assert_eq!(ArpHdr::LEN, mem::size_of::()); + } + + #[test] + fn test_hardware_type() { + let mut hdr = default_arp_hdr(); + // Test with Ethernet + hdr.set_hardware_type(1); + assert_eq!(hdr.hardware_type(), 1); + } + + #[test] + fn test_protocol_type() { + let mut hdr = default_arp_hdr(); + // Test with IPv4 + hdr.set_protocol_type(0x0800); + assert_eq!(hdr.protocol_type(), 0x0800); + } + + #[test] + fn test_hardware_length() { + let mut hdr = default_arp_hdr(); + // Test with MAC address length + hdr.set_hardware_length(6); + assert_eq!(hdr.hardware_length(), 6); + } + + #[test] + fn test_protocol_length() { + let mut hdr = default_arp_hdr(); + // Test with IPv4 address length + hdr.set_protocol_length(4); + assert_eq!(hdr.protocol_length(), 4); + } + + #[test] + fn test_operation() { + let mut hdr = default_arp_hdr(); + hdr.set_operation(1); + assert_eq!(hdr.operation(), 1); + hdr.set_operation(2); + assert_eq!(hdr.operation(), 2); + } + + #[test] + fn test_sender_hardware_address() { + let mut hdr = default_arp_hdr(); + let addr = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]; + hdr.set_sender_hardware_address(addr); + assert_eq!(hdr.sender_hardware_address(), addr); + } + + #[test] + fn test_sender_protocol_address() { + let mut hdr = default_arp_hdr(); + let addr = [192, 168, 1, 1]; + hdr.set_sender_protocol_address(addr); + assert_eq!(hdr.sender_protocol_address(), addr); + } + + #[test] + fn test_target_hardware_address() { + let mut hdr = default_arp_hdr(); + let addr = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]; + hdr.set_target_hardware_address(addr); + assert_eq!(hdr.target_hardware_address(), addr); + } + + #[test] + fn test_target_protocol_address() { + let mut hdr = default_arp_hdr(); + let addr = [192, 168, 1, 100]; + hdr.set_target_protocol_address(addr); + assert_eq!(hdr.target_protocol_address(), addr); + } } From b75347b7f31a121c549688b0d71d791b2d999d0b Mon Sep 17 00:00:00 2001 From: Madison Grubb Date: Tue, 10 Jun 2025 17:17:32 -0400 Subject: [PATCH 06/11] swap from u16 to 2-byte array for hardware_type, protocol_type, and operation --- src/arp.rs | 50 ++++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/src/arp.rs b/src/arp.rs index be4bfae..a24eed5 100644 --- a/src/arp.rs +++ b/src/arp.rs @@ -5,11 +5,11 @@ use core::mem; #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] pub struct ArpHdr { - pub hardware_type: u16, - pub protocol_type: u16, + pub hardware_type: [u8; 2], + pub protocol_type: [u8; 2], pub hardware_length: u8, pub protocol_length: u8, - pub operation: u16, + pub operation: [u8; 2], pub sender_hardware_address: [u8; 6], pub sender_protocol_address: [u8; 4], pub target_hardware_address: [u8; 6], @@ -21,25 +21,25 @@ impl ArpHdr { // Returns the hardware type field. #[inline] - pub fn hardware_type(&self) -> u16 { + pub fn hardware_type(&self) -> [u8; 2] { self.hardware_type } // Sets the hardware type field. #[inline] - pub fn set_hardware_type(&mut self, hardware_type: u16) { + pub fn set_hardware_type(&mut self, hardware_type: [u8; 2]) { self.hardware_type = hardware_type; } // Returns the protocol type field. #[inline] - pub fn protocol_type(&self) -> u16 { + pub fn protocol_type(&self) -> [u8; 2] { self.protocol_type } // Sets the protocol type field. #[inline] - pub fn set_protocol_type(&mut self, protocol_type: u16) { + pub fn set_protocol_type(&mut self, protocol_type: [u8; 2]) { self.protocol_type = protocol_type; } @@ -69,13 +69,13 @@ impl ArpHdr { // Returns the operation field. #[inline] - pub fn operation(&self) -> u16 { + pub fn operation(&self) -> [u8; 2] { self.operation } // Sets the operation field. #[inline] - pub fn set_operation(&mut self, operation: u16) { + pub fn set_operation(&mut self, operation: [u8; 2]) { self.operation = operation } @@ -134,11 +134,11 @@ mod tests { fn default_arp_hdr() -> ArpHdr { ArpHdr { - hardware_type: 0, - protocol_type: 0, + hardware_type: [0; 2], + protocol_type: [0; 2], hardware_length: 0, protocol_length: 0, - operation: 0, + operation: [0; 2], sender_hardware_address: [0; 6], sender_protocol_address: [0; 4], target_hardware_address: [0; 6], @@ -155,17 +155,19 @@ mod tests { #[test] fn test_hardware_type() { let mut hdr = default_arp_hdr(); - // Test with Ethernet - hdr.set_hardware_type(1); - assert_eq!(hdr.hardware_type(), 1); + // Test with Ethernet (value 1) + let hw_type = 1u16.to_be_bytes(); + hdr.set_hardware_type(hw_type); + assert_eq!(hdr.hardware_type(), hw_type); } #[test] fn test_protocol_type() { let mut hdr = default_arp_hdr(); - // Test with IPv4 - hdr.set_protocol_type(0x0800); - assert_eq!(hdr.protocol_type(), 0x0800); + // Test with IPv4 (value 0x0800) + let proto_type = 0x0800u16.to_be_bytes(); + hdr.set_protocol_type(proto_type); + assert_eq!(hdr.protocol_type(), proto_type); } #[test] @@ -187,10 +189,14 @@ mod tests { #[test] fn test_operation() { let mut hdr = default_arp_hdr(); - hdr.set_operation(1); - assert_eq!(hdr.operation(), 1); - hdr.set_operation(2); - assert_eq!(hdr.operation(), 2); + // Test with ARP Request (1) + let op_request = 1u16.to_be_bytes(); + hdr.set_operation(op_request); + assert_eq!(hdr.operation(), op_request); + // Test with ARP Reply (2) + let op_reply = 2u16.to_be_bytes(); + hdr.set_operation(op_reply); + assert_eq!(hdr.operation(), op_reply); } #[test] From 9d578a8863816ea52e060e02d3a9810fad9fe580 Mon Sep 17 00:00:00 2001 From: Madison Grubb Date: Tue, 10 Jun 2025 17:21:18 -0400 Subject: [PATCH 07/11] use original var names --- src/arp.rs | 170 ++++++++++++++++++++++++++--------------------------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/src/arp.rs b/src/arp.rs index a24eed5..5425e5e 100644 --- a/src/arp.rs +++ b/src/arp.rs @@ -5,15 +5,15 @@ use core::mem; #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] pub struct ArpHdr { - pub hardware_type: [u8; 2], - pub protocol_type: [u8; 2], - pub hardware_length: u8, - pub protocol_length: u8, - pub operation: [u8; 2], - pub sender_hardware_address: [u8; 6], - pub sender_protocol_address: [u8; 4], - pub target_hardware_address: [u8; 6], - pub target_protocol_address: [u8; 4], + pub htype: [u8; 2], + pub ptype: [u8; 2], + pub hlen: u8, + pub plen: u8, + pub oper: [u8; 2], + pub sha: [u8; 6], + pub spa: [u8; 4], + pub tha: [u8; 6], + pub tpa: [u8; 4], } impl ArpHdr { @@ -21,110 +21,110 @@ impl ArpHdr { // Returns the hardware type field. #[inline] - pub fn hardware_type(&self) -> [u8; 2] { - self.hardware_type + pub fn htype(&self) -> [u8; 2] { + self.htype } // Sets the hardware type field. #[inline] - pub fn set_hardware_type(&mut self, hardware_type: [u8; 2]) { - self.hardware_type = hardware_type; + pub fn set_htype(&mut self, htype: [u8; 2]) { + self.htype = htype; } // Returns the protocol type field. #[inline] - pub fn protocol_type(&self) -> [u8; 2] { - self.protocol_type + pub fn ptype(&self) -> [u8; 2] { + self.ptype } // Sets the protocol type field. #[inline] - pub fn set_protocol_type(&mut self, protocol_type: [u8; 2]) { - self.protocol_type = protocol_type; + pub fn set_ptype(&mut self, ptype: [u8; 2]) { + self.ptype = ptype; } // Returns the hardware length field. #[inline] - pub fn hardware_length(&self) -> u8 { - self.hardware_length + pub fn hlen(&self) -> u8 { + self.hlen } // Sets the hardware length field. #[inline] - pub fn set_hardware_length(&mut self, hardware_length: u8) { - self.hardware_length = hardware_length; + pub fn set_hlen(&mut self, hlen: u8) { + self.hlen = hlen; } // Returns the protocol length field. #[inline] - pub fn protocol_length(&self) -> u8 { - self.protocol_length + pub fn plen(&self) -> u8 { + self.plen } // Sets the protocol length field. #[inline] - pub fn set_protocol_length(&mut self, protocol_length: u8) { - self.protocol_length = protocol_length; + pub fn set_plen(&mut self, plen: u8) { + self.plen = plen; } - // Returns the operation field. + // Returns the oper field. #[inline] - pub fn operation(&self) -> [u8; 2] { - self.operation + pub fn oper(&self) -> [u8; 2] { + self.oper } - // Sets the operation field. + // Sets the oper field. #[inline] - pub fn set_operation(&mut self, operation: [u8; 2]) { - self.operation = operation + pub fn set_oper(&mut self, oper: [u8; 2]) { + self.oper = oper } // Returns the sender hardware address field. #[inline] - pub fn sender_hardware_address(&self) -> [u8; 6] { - self.sender_hardware_address + pub fn sha(&self) -> [u8; 6] { + self.sha } // Sets the sender hardware address field. #[inline] - pub fn set_sender_hardware_address(&mut self, hardware_address: [u8; 6]) { - self.sender_hardware_address = hardware_address + pub fn set_sha(&mut self, hardware_address: [u8; 6]) { + self.sha = hardware_address } // Returns the sender protocol address field. #[inline] - pub fn sender_protocol_address(&self) -> [u8; 4] { - self.sender_protocol_address + pub fn spa(&self) -> [u8; 4] { + self.spa } // Sets the sender protocol address field. #[inline] - pub fn set_sender_protocol_address(&mut self, protocol_address: [u8; 4]) { - self.sender_protocol_address = protocol_address + pub fn set_spa(&mut self, protocol_address: [u8; 4]) { + self.spa = protocol_address } // Returns the target hardware address field. #[inline] - pub fn target_hardware_address(&self) -> [u8; 6] { - self.target_hardware_address + pub fn tha(&self) -> [u8; 6] { + self.tha } // Sets the target hardware address field. #[inline] - pub fn set_target_hardware_address(&mut self, hardware_address: [u8; 6]) { - self.target_hardware_address = hardware_address + pub fn set_tha(&mut self, hardware_address: [u8; 6]) { + self.tha = hardware_address } // Returns the target protocol address field. #[inline] - pub fn target_protocol_address(&self) -> [u8; 4] { - self.target_protocol_address + pub fn tpa(&self) -> [u8; 4] { + self.tpa } // Sets the target protocol address field. #[inline] - pub fn set_target_protocol_address(&mut self, protocol_address: [u8; 4]) { - self.target_protocol_address = protocol_address + pub fn set_tpa(&mut self, protocol_address: [u8; 4]) { + self.tpa = protocol_address } } @@ -134,15 +134,15 @@ mod tests { fn default_arp_hdr() -> ArpHdr { ArpHdr { - hardware_type: [0; 2], - protocol_type: [0; 2], - hardware_length: 0, - protocol_length: 0, - operation: [0; 2], - sender_hardware_address: [0; 6], - sender_protocol_address: [0; 4], - target_hardware_address: [0; 6], - target_protocol_address: [0; 4], + htype: [0; 2], + ptype: [0; 2], + hlen: 0, + plen: 0, + oper: [0; 2], + sha: [0; 6], + spa: [0; 4], + tha: [0; 6], + tpa: [0; 4], } } @@ -153,81 +153,81 @@ mod tests { } #[test] - fn test_hardware_type() { + fn test_htype() { let mut hdr = default_arp_hdr(); // Test with Ethernet (value 1) let hw_type = 1u16.to_be_bytes(); - hdr.set_hardware_type(hw_type); - assert_eq!(hdr.hardware_type(), hw_type); + hdr.set_htype(hw_type); + assert_eq!(hdr.htype(), hw_type); } #[test] - fn test_protocol_type() { + fn test_ptype() { let mut hdr = default_arp_hdr(); // Test with IPv4 (value 0x0800) let proto_type = 0x0800u16.to_be_bytes(); - hdr.set_protocol_type(proto_type); - assert_eq!(hdr.protocol_type(), proto_type); + hdr.set_ptype(proto_type); + assert_eq!(hdr.ptype(), proto_type); } #[test] - fn test_hardware_length() { + fn test_hlen() { let mut hdr = default_arp_hdr(); // Test with MAC address length - hdr.set_hardware_length(6); - assert_eq!(hdr.hardware_length(), 6); + hdr.set_hlen(6); + assert_eq!(hdr.hlen(), 6); } #[test] - fn test_protocol_length() { + fn test_plen() { let mut hdr = default_arp_hdr(); // Test with IPv4 address length - hdr.set_protocol_length(4); - assert_eq!(hdr.protocol_length(), 4); + hdr.set_plen(4); + assert_eq!(hdr.plen(), 4); } #[test] - fn test_operation() { + fn test_oper() { let mut hdr = default_arp_hdr(); // Test with ARP Request (1) let op_request = 1u16.to_be_bytes(); - hdr.set_operation(op_request); - assert_eq!(hdr.operation(), op_request); + hdr.set_oper(op_request); + assert_eq!(hdr.oper(), op_request); // Test with ARP Reply (2) let op_reply = 2u16.to_be_bytes(); - hdr.set_operation(op_reply); - assert_eq!(hdr.operation(), op_reply); + hdr.set_oper(op_reply); + assert_eq!(hdr.oper(), op_reply); } #[test] - fn test_sender_hardware_address() { + fn test_sha() { let mut hdr = default_arp_hdr(); let addr = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]; - hdr.set_sender_hardware_address(addr); - assert_eq!(hdr.sender_hardware_address(), addr); + hdr.set_sha(addr); + assert_eq!(hdr.sha(), addr); } #[test] - fn test_sender_protocol_address() { + fn test_spa() { let mut hdr = default_arp_hdr(); let addr = [192, 168, 1, 1]; - hdr.set_sender_protocol_address(addr); - assert_eq!(hdr.sender_protocol_address(), addr); + hdr.set_spa(addr); + assert_eq!(hdr.spa(), addr); } #[test] - fn test_target_hardware_address() { + fn test_tha() { let mut hdr = default_arp_hdr(); let addr = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]; - hdr.set_target_hardware_address(addr); - assert_eq!(hdr.target_hardware_address(), addr); + hdr.set_tha(addr); + assert_eq!(hdr.tha(), addr); } #[test] - fn test_target_protocol_address() { + fn test_tpa() { let mut hdr = default_arp_hdr(); let addr = [192, 168, 1, 100]; - hdr.set_target_protocol_address(addr); - assert_eq!(hdr.target_protocol_address(), addr); + hdr.set_tpa(addr); + assert_eq!(hdr.tpa(), addr); } } From 1b017bc2266d595f255f427b20d58984357f66bd Mon Sep 17 00:00:00 2001 From: Madison Grubb Date: Thu, 12 Jun 2025 16:44:16 -0400 Subject: [PATCH 08/11] add rustdoc information. add a default function --- src/arp.rs | 150 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 108 insertions(+), 42 deletions(-) diff --git a/src/arp.rs b/src/arp.rs index 5425e5e..69af663 100644 --- a/src/arp.rs +++ b/src/arp.rs @@ -1,127 +1,207 @@ use core::mem; -/// ARP header, which is present after the Ethernet header. +/// Represents an Address Resolution Protocol (ARP) header. +/// +/// The ARP header is typically found after the Ethernet header and is used to +/// map a network protocol address (like an IPv4 address) to a hardware +/// address (like a MAC address). #[repr(C)] #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] pub struct ArpHdr { + /// Hardware type (HTYPE): Specifies the network link protocol type. + /// E.g., Ethernet is 1. pub htype: [u8; 2], + /// Protocol type (PTYPE): Specifies the internetwork protocol for which + /// the ARP request is intended. For IPv4, this has the value 0x0800. pub ptype: [u8; 2], + /// Hardware address length (HLEN): Length in bytes of a hardware address. + /// Ethernet addresses size is 6. pub hlen: u8, + /// Protocol address length (PLEN): Length in bytes of a logical address. + /// IPv4 addresses size is 4. pub plen: u8, + /// Operation (OPER): Specifies the operation that the sender is performing: + /// 1 for request, 2 for reply. pub oper: [u8; 2], + /// Sender hardware address (SHA): The hardware address of the sender. pub sha: [u8; 6], + /// Sender protocol address (SPA): The protocol address of the sender. pub spa: [u8; 4], + /// Target hardware address (THA): The hardware address of the intended + /// receiver. This field is ignored in an ARP request. pub tha: [u8; 6], + /// Target protocol address (TPA): The protocol address of the intended + /// receiver. pub tpa: [u8; 4], } +impl Default for ArpHdr { + /// Creates a new `ArpHdr` with all fields initialized to zero. + fn default() -> Self { + ArpHdr { + htype: [0; 2], + ptype: [0; 2], + hlen: 0, + plen: 0, + oper: [0; 2], + sha: [0; 6], + spa: [0; 4], + tha: [0; 6], + tpa: [0; 4], + } + } +} + impl ArpHdr { + /// The size of the ARP header in bytes. pub const LEN: usize = mem::size_of::(); - // Returns the hardware type field. + /// Creates a new `ArpHdr` with all fields initialized to zero. + /// This is an alias for `ArpHdr::default()`. + pub fn new() -> Self { + Self::default() + } + + /// Returns the hardware type field. #[inline] pub fn htype(&self) -> [u8; 2] { self.htype } - // Sets the hardware type field. + /// Sets the hardware type field. + /// + /// # Arguments + /// + /// * `htype` - A 2-byte array representing the hardware type. #[inline] pub fn set_htype(&mut self, htype: [u8; 2]) { self.htype = htype; } - // Returns the protocol type field. + /// Returns the protocol type field. #[inline] pub fn ptype(&self) -> [u8; 2] { self.ptype } - // Sets the protocol type field. + /// Sets the protocol type field. + /// + /// # Arguments + /// + /// * `ptype` - A 2-byte array representing the protocol type. #[inline] pub fn set_ptype(&mut self, ptype: [u8; 2]) { self.ptype = ptype; } - // Returns the hardware length field. + /// Returns the hardware address length field. #[inline] pub fn hlen(&self) -> u8 { self.hlen } - // Sets the hardware length field. + /// Sets the hardware address length field. + /// + /// # Arguments + /// + /// * `hlen` - A u8 value for the hardware address length. #[inline] pub fn set_hlen(&mut self, hlen: u8) { self.hlen = hlen; } - // Returns the protocol length field. + /// Returns the protocol address length field. #[inline] pub fn plen(&self) -> u8 { self.plen } - // Sets the protocol length field. + /// Sets the protocol address length field. + /// + /// # Arguments + /// + /// * `plen` - A u8 value for the protocol address length. #[inline] pub fn set_plen(&mut self, plen: u8) { self.plen = plen; } - // Returns the oper field. + /// Returns the operation field. #[inline] pub fn oper(&self) -> [u8; 2] { self.oper } - // Sets the oper field. + /// Sets the operation field. + /// + /// # Arguments + /// + /// * `oper` - A 2-byte array representing the operation (e.g., request or reply). #[inline] pub fn set_oper(&mut self, oper: [u8; 2]) { self.oper = oper } - // Returns the sender hardware address field. + /// Returns the sender hardware address (SHA) field. #[inline] pub fn sha(&self) -> [u8; 6] { self.sha } - // Sets the sender hardware address field. + /// Sets the sender hardware address (SHA) field. + /// + /// # Arguments + /// + /// * `hardware_address` - A 6-byte array representing the sender's hardware address. #[inline] pub fn set_sha(&mut self, hardware_address: [u8; 6]) { self.sha = hardware_address } - // Returns the sender protocol address field. + /// Returns the sender protocol address (SPA) field. #[inline] pub fn spa(&self) -> [u8; 4] { self.spa } - // Sets the sender protocol address field. + /// Sets the sender protocol address (SPA) field. + /// + /// # Arguments + /// + /// * `protocol_address` - A 4-byte array representing the sender's protocol address. #[inline] pub fn set_spa(&mut self, protocol_address: [u8; 4]) { self.spa = protocol_address } - // Returns the target hardware address field. + /// Returns the target hardware address (THA) field. #[inline] pub fn tha(&self) -> [u8; 6] { self.tha } - // Sets the target hardware address field. + /// Sets the target hardware address (THA) field. + /// + /// # Arguments + /// + /// * `hardware_address` - A 6-byte array representing the target's hardware address. #[inline] pub fn set_tha(&mut self, hardware_address: [u8; 6]) { self.tha = hardware_address } - // Returns the target protocol address field. + /// Returns the target protocol address (TPA) field. #[inline] pub fn tpa(&self) -> [u8; 4] { self.tpa } - // Sets the target protocol address field. + /// Sets the target protocol address (TPA) field. + /// + /// # Arguments + /// + /// * `protocol_address` - A 4-byte array representing the target's protocol address. #[inline] pub fn set_tpa(&mut self, protocol_address: [u8; 4]) { self.tpa = protocol_address @@ -132,20 +212,6 @@ impl ArpHdr { mod tests { use super::*; - fn default_arp_hdr() -> ArpHdr { - ArpHdr { - htype: [0; 2], - ptype: [0; 2], - hlen: 0, - plen: 0, - oper: [0; 2], - sha: [0; 6], - spa: [0; 4], - tha: [0; 6], - tpa: [0; 4], - } - } - #[test] fn test_len_constant() { assert_eq!(ArpHdr::LEN, 28); @@ -154,7 +220,7 @@ mod tests { #[test] fn test_htype() { - let mut hdr = default_arp_hdr(); + let mut hdr = ArpHdr::default(); // Test with Ethernet (value 1) let hw_type = 1u16.to_be_bytes(); hdr.set_htype(hw_type); @@ -163,7 +229,7 @@ mod tests { #[test] fn test_ptype() { - let mut hdr = default_arp_hdr(); + let mut hdr = ArpHdr::default(); // Test with IPv4 (value 0x0800) let proto_type = 0x0800u16.to_be_bytes(); hdr.set_ptype(proto_type); @@ -172,7 +238,7 @@ mod tests { #[test] fn test_hlen() { - let mut hdr = default_arp_hdr(); + let mut hdr = ArpHdr::default(); // Test with MAC address length hdr.set_hlen(6); assert_eq!(hdr.hlen(), 6); @@ -180,7 +246,7 @@ mod tests { #[test] fn test_plen() { - let mut hdr = default_arp_hdr(); + let mut hdr = ArpHdr::default(); // Test with IPv4 address length hdr.set_plen(4); assert_eq!(hdr.plen(), 4); @@ -188,7 +254,7 @@ mod tests { #[test] fn test_oper() { - let mut hdr = default_arp_hdr(); + let mut hdr = ArpHdr::default(); // Test with ARP Request (1) let op_request = 1u16.to_be_bytes(); hdr.set_oper(op_request); @@ -201,7 +267,7 @@ mod tests { #[test] fn test_sha() { - let mut hdr = default_arp_hdr(); + let mut hdr = ArpHdr::default(); let addr = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]; hdr.set_sha(addr); assert_eq!(hdr.sha(), addr); @@ -209,7 +275,7 @@ mod tests { #[test] fn test_spa() { - let mut hdr = default_arp_hdr(); + let mut hdr = ArpHdr::default(); let addr = [192, 168, 1, 1]; hdr.set_spa(addr); assert_eq!(hdr.spa(), addr); @@ -217,7 +283,7 @@ mod tests { #[test] fn test_tha() { - let mut hdr = default_arp_hdr(); + let mut hdr = ArpHdr::default(); let addr = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]; hdr.set_tha(addr); assert_eq!(hdr.tha(), addr); @@ -225,7 +291,7 @@ mod tests { #[test] fn test_tpa() { - let mut hdr = default_arp_hdr(); + let mut hdr = ArpHdr::default(); let addr = [192, 168, 1, 100]; hdr.set_tpa(addr); assert_eq!(hdr.tpa(), addr); From f20e456a4846c188fcc976f285ed140bae1a076a Mon Sep 17 00:00:00 2001 From: "Keli (Madison) Grubb" Date: Mon, 14 Jul 2025 10:13:43 -0400 Subject: [PATCH 09/11] Apply suggestions from code review Co-authored-by: Michal Rostecki --- src/arp.rs | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/arp.rs b/src/arp.rs index 69af663..e3d7e77 100644 --- a/src/arp.rs +++ b/src/arp.rs @@ -65,8 +65,11 @@ impl ArpHdr { /// Returns the hardware type field. #[inline] - pub fn htype(&self) -> [u8; 2] { - self.htype + pub fn htype(&self) -> u16 { + unsafe { + *((self as *const Self as usize + offset_of!(Self, htype)) as *const u16) + } + .swap_bytes() } /// Sets the hardware type field. @@ -75,14 +78,17 @@ impl ArpHdr { /// /// * `htype` - A 2-byte array representing the hardware type. #[inline] - pub fn set_htype(&mut self, htype: [u8; 2]) { - self.htype = htype; + pub fn set_htype(&mut self, htype: u16) { + self.htype = unsafe { *((&htype.swap_bytes() as *const _ as usize) as *const _) }; } /// Returns the protocol type field. #[inline] - pub fn ptype(&self) -> [u8; 2] { - self.ptype + pub fn ptype(&self) -> u16 { + unsafe { + *((self as *const Self as usize + offset_of!(Self, ptype)) as *const u16) + } + .swap_bytes() } /// Sets the protocol type field. @@ -91,8 +97,8 @@ impl ArpHdr { /// /// * `ptype` - A 2-byte array representing the protocol type. #[inline] - pub fn set_ptype(&mut self, ptype: [u8; 2]) { - self.ptype = ptype; + pub fn set_ptype(&mut self, ptype: u16) { + self.ptype = unsafe { *((&ptype.swap_bytes() as *const _ as usize) as *const _) }; } /// Returns the hardware address length field. @@ -129,8 +135,11 @@ impl ArpHdr { /// Returns the operation field. #[inline] - pub fn oper(&self) -> [u8; 2] { - self.oper + pub fn oper(&self) -> u16 { + unsafe { + *((self as *const Self as usize + offset_of!(Self, oper)) as *const u16) + } + .swap_bytes() } /// Sets the operation field. @@ -139,8 +148,8 @@ impl ArpHdr { /// /// * `oper` - A 2-byte array representing the operation (e.g., request or reply). #[inline] - pub fn set_oper(&mut self, oper: [u8; 2]) { - self.oper = oper + pub fn set_oper(&mut self, oper: u16) { + self.oper = unsafe { *((&oper.swap_bytes() as *const _ as usize) as *const _) }; } /// Returns the sender hardware address (SHA) field. From a9f2df2d97f4eb5924b1295637befdf7922c69d4 Mon Sep 17 00:00:00 2001 From: Madison Grubb Date: Mon, 14 Jul 2025 10:32:14 -0400 Subject: [PATCH 10/11] derive default instead of explicitly creating it. import memoffset and use integer types in getters and setters if the given field is an integer, represented as an array. --- Cargo.toml | 1 + src/arp.rs | 37 +++++++------------------------------ 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 43ab804..323c68e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ readme = "README.md" edition = "2021" [dependencies] +memoffset = "0.9.1" serde = { version = "1", default-features = false, features = ["derive"], optional = true } [dev-dependencies] diff --git a/src/arp.rs b/src/arp.rs index e3d7e77..bf1efcf 100644 --- a/src/arp.rs +++ b/src/arp.rs @@ -1,12 +1,12 @@ -use core::mem; - +use core::mem::{self}; +use memoffset::offset_of; /// Represents an Address Resolution Protocol (ARP) header. /// /// The ARP header is typically found after the Ethernet header and is used to /// map a network protocol address (like an IPv4 address) to a hardware /// address (like a MAC address). #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Default)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] pub struct ArpHdr { /// Hardware type (HTYPE): Specifies the network link protocol type. @@ -36,23 +36,6 @@ pub struct ArpHdr { pub tpa: [u8; 4], } -impl Default for ArpHdr { - /// Creates a new `ArpHdr` with all fields initialized to zero. - fn default() -> Self { - ArpHdr { - htype: [0; 2], - ptype: [0; 2], - hlen: 0, - plen: 0, - oper: [0; 2], - sha: [0; 6], - spa: [0; 4], - tha: [0; 6], - tpa: [0; 4], - } - } -} - impl ArpHdr { /// The size of the ARP header in bytes. pub const LEN: usize = mem::size_of::(); @@ -230,8 +213,7 @@ mod tests { #[test] fn test_htype() { let mut hdr = ArpHdr::default(); - // Test with Ethernet (value 1) - let hw_type = 1u16.to_be_bytes(); + let hw_type = 1u16; hdr.set_htype(hw_type); assert_eq!(hdr.htype(), hw_type); } @@ -239,8 +221,7 @@ mod tests { #[test] fn test_ptype() { let mut hdr = ArpHdr::default(); - // Test with IPv4 (value 0x0800) - let proto_type = 0x0800u16.to_be_bytes(); + let proto_type = 0x0800u16; hdr.set_ptype(proto_type); assert_eq!(hdr.ptype(), proto_type); } @@ -248,7 +229,6 @@ mod tests { #[test] fn test_hlen() { let mut hdr = ArpHdr::default(); - // Test with MAC address length hdr.set_hlen(6); assert_eq!(hdr.hlen(), 6); } @@ -256,7 +236,6 @@ mod tests { #[test] fn test_plen() { let mut hdr = ArpHdr::default(); - // Test with IPv4 address length hdr.set_plen(4); assert_eq!(hdr.plen(), 4); } @@ -264,12 +243,10 @@ mod tests { #[test] fn test_oper() { let mut hdr = ArpHdr::default(); - // Test with ARP Request (1) - let op_request = 1u16.to_be_bytes(); + let op_request = 1u16; hdr.set_oper(op_request); assert_eq!(hdr.oper(), op_request); - // Test with ARP Reply (2) - let op_reply = 2u16.to_be_bytes(); + let op_reply = 2u16; hdr.set_oper(op_reply); assert_eq!(hdr.oper(), op_reply); } From a9f80dd4ffc1b0ee2106436cd29ea609f0b2b5d8 Mon Sep 17 00:00:00 2001 From: Madison Grubb Date: Mon, 14 Jul 2025 10:37:54 -0400 Subject: [PATCH 11/11] fix formatting in arp --- src/arp.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/arp.rs b/src/arp.rs index bf1efcf..d951b50 100644 --- a/src/arp.rs +++ b/src/arp.rs @@ -49,10 +49,8 @@ impl ArpHdr { /// Returns the hardware type field. #[inline] pub fn htype(&self) -> u16 { - unsafe { - *((self as *const Self as usize + offset_of!(Self, htype)) as *const u16) - } - .swap_bytes() + unsafe { *((self as *const Self as usize + offset_of!(Self, htype)) as *const u16) } + .swap_bytes() } /// Sets the hardware type field. @@ -68,10 +66,8 @@ impl ArpHdr { /// Returns the protocol type field. #[inline] pub fn ptype(&self) -> u16 { - unsafe { - *((self as *const Self as usize + offset_of!(Self, ptype)) as *const u16) - } - .swap_bytes() + unsafe { *((self as *const Self as usize + offset_of!(Self, ptype)) as *const u16) } + .swap_bytes() } /// Sets the protocol type field. @@ -119,10 +115,8 @@ impl ArpHdr { /// Returns the operation field. #[inline] pub fn oper(&self) -> u16 { - unsafe { - *((self as *const Self as usize + offset_of!(Self, oper)) as *const u16) - } - .swap_bytes() + unsafe { *((self as *const Self as usize + offset_of!(Self, oper)) as *const u16) } + .swap_bytes() } /// Sets the operation field.