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 <(); + + /// 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) -> u16 { + unsafe { *((self as *const Self as usize + offset_of!(Self, htype)) as *const u16) } + .swap_bytes() + } + + /// Sets the hardware type field. + /// + /// # Arguments + /// + /// * `htype` - A 2-byte array representing the hardware type. + #[inline] + 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) -> u16 { + unsafe { *((self as *const Self as usize + offset_of!(Self, ptype)) as *const u16) } + .swap_bytes() + } + + /// Sets the protocol type field. + /// + /// # Arguments + /// + /// * `ptype` - A 2-byte array representing the protocol type. + #[inline] + 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. + #[inline] + pub fn hlen(&self) -> u8 { + self.hlen + } + + /// 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 address length field. + #[inline] + pub fn plen(&self) -> u8 { + self.plen + } + + /// 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 operation field. + #[inline] + 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. + /// + /// # Arguments + /// + /// * `oper` - A 2-byte array representing the operation (e.g., request or reply). + #[inline] + 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. + #[inline] + pub fn sha(&self) -> [u8; 6] { + self.sha + } + + /// 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 (SPA) field. + #[inline] + pub fn spa(&self) -> [u8; 4] { + self.spa + } + + /// 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 (THA) field. + #[inline] + pub fn tha(&self) -> [u8; 6] { + self.tha + } + + /// 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 (TPA) field. + #[inline] + pub fn tpa(&self) -> [u8; 4] { + self.tpa + } + + /// 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 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_len_constant() { + assert_eq!(ArpHdr::LEN, 28); + assert_eq!(ArpHdr::LEN, mem::size_of::()); + } + + #[test] + fn test_htype() { + let mut hdr = ArpHdr::default(); + let hw_type = 1u16; + hdr.set_htype(hw_type); + assert_eq!(hdr.htype(), hw_type); + } + + #[test] + fn test_ptype() { + let mut hdr = ArpHdr::default(); + let proto_type = 0x0800u16; + hdr.set_ptype(proto_type); + assert_eq!(hdr.ptype(), proto_type); + } + + #[test] + fn test_hlen() { + let mut hdr = ArpHdr::default(); + hdr.set_hlen(6); + assert_eq!(hdr.hlen(), 6); + } + + #[test] + fn test_plen() { + let mut hdr = ArpHdr::default(); + hdr.set_plen(4); + assert_eq!(hdr.plen(), 4); + } + + #[test] + fn test_oper() { + let mut hdr = ArpHdr::default(); + let op_request = 1u16; + hdr.set_oper(op_request); + assert_eq!(hdr.oper(), op_request); + let op_reply = 2u16; + hdr.set_oper(op_reply); + assert_eq!(hdr.oper(), op_reply); + } + + #[test] + fn test_sha() { + let mut hdr = ArpHdr::default(); + let addr = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]; + hdr.set_sha(addr); + assert_eq!(hdr.sha(), addr); + } + + #[test] + fn test_spa() { + let mut hdr = ArpHdr::default(); + let addr = [192, 168, 1, 1]; + hdr.set_spa(addr); + assert_eq!(hdr.spa(), addr); + } + + #[test] + fn test_tha() { + let mut hdr = ArpHdr::default(); + let addr = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]; + hdr.set_tha(addr); + assert_eq!(hdr.tha(), addr); + } + + #[test] + fn test_tpa() { + let mut hdr = ArpHdr::default(); + let addr = [192, 168, 1, 100]; + hdr.set_tpa(addr); + assert_eq!(hdr.tpa(), addr); + } } 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 new file mode 100644 index 0000000..1b46cd4 --- /dev/null +++ b/src/geneve.rs @@ -0,0 +1,276 @@ +/// 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" + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index b5df8b5..312e065 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,13 @@ #![doc = include_str!("../README.md")] -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] 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,4 +15,3 @@ pub mod tcp; pub mod udp; pub mod vlan; pub mod vxlan; -pub mod llc; 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 { 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); } }