From 35356ce7877150a2de75f50d5eda44ad7c858d61 Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Fri, 6 Jun 2025 18:42:24 -0500 Subject: [PATCH 01/14] added quic implementation --- src/lib.rs | 1 + src/quic.rs | 616 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 617 insertions(+) create mode 100644 src/quic.rs diff --git a/src/lib.rs b/src/lib.rs index 410179e..3e6da0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub mod eth; pub mod icmp; pub mod ip; pub mod mac; +pub mod quic; pub mod sctp; pub mod tcp; pub mod udp; diff --git a/src/quic.rs b/src/quic.rs new file mode 100644 index 0000000..1f368b3 --- /dev/null +++ b/src/quic.rs @@ -0,0 +1,616 @@ +use core::mem; + +const HEADER_FORM_BIT: u8 = 0x80; +const FIXED_BIT_MASK: u8 = 0x40; +const LONG_PACKET_TYPE_MASK: u8 = 0x30; +const LONG_PACKET_TYPE_SHIFT: u8 = 4; +const RESERVED_BITS_LONG_MASK: u8 = 0x0C; +const RESERVED_BITS_LONG_SHIFT: u8 = 2; +const SHORT_SPIN_BIT_MASK: u8 = 0x20; +const SHORT_SPIN_BIT_SHIFT: u8 = 5; +const SHORT_RESERVED_BITS_MASK: u8 = 0x18; +const SHORT_RESERVED_BITS_SHIFT: u8 = 3; +const SHORT_KEY_PHASE_BIT_MASK: u8 = 0x04; +const SHORT_KEY_PHASE_BIT_SHIFT: u8 = 2; + +const PN_LENGTH_BITS_MASK: u8 = 0x03; +/// Mask for Packet Number Length bits (bits 1-0). + +/// Represents the 7-byte fixed-size prefix of a QUIC Long Header or provides methods +/// to interpret the first byte of a QUIC Short Header. +/// +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Default)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct QuicHdr { + /// The first byte of the QUIC header. Its interpretation depends on the Header Form bit. + /// - Bit 7: Header Form (1 for Long, 0 for Short) + /// - Bit 6: Fixed Bit (usually 1) + /// - Bits 5-0: Type/Flag specific bits. + pub first_byte: u8, + /// QUIC version (e.g., 0x00000001 for QUIC v1). Network byte order. Only in Long Headers. + pub version: [u8; 4], + /// Destination Connection ID Length. Only in Long Headers (explicitly). For Short Headers, + /// this field might be used by application logic to store the known DCID length. + pub dc_id_len: u8, + /// Source Connection ID Length. Only in Long Headers. + pub sc_id_len: u8, +} + +impl QuicHdr { + /// Length of the `QuicHdr` struct, relevant for Long Headers. + pub const LEN: usize = mem::size_of::(); + + /// Creates a `QuicHdr` for a Long Header, configured as an Initial packet. + /// + /// The first byte is set for Long Header (form bit = 1), Fixed Bit = 1, + /// Long Packet Type = Initial (0b00), and Reserved Bits = 0. + /// + /// # Parameters + /// - `version` - QUIC version (e.g., `0x00000001` for v1), host byte order. + /// - `dc_id_len` - Destination Connection ID length. + /// - `sc_id_len` - Source Connection ID length. + /// - `pn_len_bits` - Encoded Packet Number Length (actual length - 1, value 0-3). + /// + /// # Returns + /// A new `QuicHdr` instance. + pub fn new(version: u32, dc_id_len: u8, sc_id_len: u8, pn_len_bits: u8) -> Self { + let first_byte = HEADER_FORM_BIT + | FIXED_BIT_MASK + | (0b00 << LONG_PACKET_TYPE_SHIFT) + | (0b00 << RESERVED_BITS_LONG_SHIFT) + | (pn_len_bits & PN_LENGTH_BITS_MASK); + Self { + first_byte, + version: version.to_be_bytes(), + dc_id_len, + sc_id_len, + } + } + + /// Creates the first byte for a QUIC Short Header. + /// + /// Sets Header Form to 0, Fixed Bit to 1, and specified Short Header flags. + /// Reserved bits (4-3) are set to 0. + /// + /// # Parameters + /// - `spin_bit` - The value of the Spin bit (0 or 1). + /// - `key_phase` - The value of the Key Phase bit (0 or 1). + /// - `pn_len_bits` - The encoded Packet Number Length (actual length - 1, value 0-3). + /// + /// # Returns + /// The constructed `first_byte` for a Short Header. + pub fn new_short_header_first_byte(spin_bit: bool, key_phase: bool, pn_len_bits: u8) -> u8 { + let spin_val = if spin_bit { 1 } else { 0 }; + let key_phase_val = if key_phase { 1 } else { 0 }; + FIXED_BIT_MASK + | (spin_val << SHORT_SPIN_BIT_SHIFT) + | (key_phase_val << SHORT_KEY_PHASE_BIT_SHIFT) + | (pn_len_bits & PN_LENGTH_BITS_MASK) + } + + /// Returns the raw first byte of the header. + /// + /// # Returns + /// The `first_byte` field. + #[inline] + pub fn first_byte(&self) -> u8 { + self.first_byte + } + + /// Sets the raw first byte of the header. + /// + /// # Parameters + /// - `first_byte` - The new value for the first byte. + #[inline] + pub fn set_first_byte(&mut self, first_byte: u8) { + self.first_byte = first_byte; + } + + /// Checks if the Header Form bit (bit 7 of `first_byte`) indicates a Long Header. + /// + /// # Returns + /// `true` if it's a Long Header (bit 7 is 1), `false` otherwise (Short Header). + #[inline] + pub fn is_long_header(&self) -> bool { + (self.first_byte & HEADER_FORM_BIT) == HEADER_FORM_BIT + } + + /// Sets the Header Form bit (bit 7 of `first_byte`). + /// + /// # Parameters + /// - `is_long` - If `true`, sets for Long Header (bit 7 = 1); if `false`, sets for Short Header (bit 7 = 0). + #[inline] + pub fn set_header_form(&mut self, is_long: bool) { + if is_long { + self.first_byte |= HEADER_FORM_BIT; + } else { + self.first_byte &= !HEADER_FORM_BIT; + } + } + + /// Gets the Fixed Bit (bit 6 of `first_byte`). + /// + /// In QUIC v1 (RFC 9000): + /// - For Long Headers: `1` for Initial, 0-RTT, Handshake, Retry. `0` for Version Negotiation. + /// - For Short Headers: Must be `1`. + /// + /// # Returns + /// The value of the Fixed Bit (0 or 1). + #[inline] + pub fn fixed_bit(&self) -> u8 { + (self.first_byte & FIXED_BIT_MASK) >> 6 + } + + /// Sets the Fixed Bit (bit 6 of `first_byte`). + /// + /// # Parameters + /// - `val` - The new value for the Fixed Bit (0 or 1). Input is masked to 1 bit. + #[inline] + pub fn set_fixed_bit(&mut self, val: u8) { + self.first_byte = (self.first_byte & !FIXED_BIT_MASK) | ((val & 0x01) << 6); + } + + /// Gets the Long Packet Type (bits 5-4 of `first_byte`). Assumes Long Header. + /// Common QUIC v1 types (RFC 9000): 00 (Initial), 01 (0-RTT), 10 (Handshake), 11 (Retry). + /// + /// # Returns + /// The Long Packet Type value (0-3). Only valid if `is_long_header()` is `true` and `fixed_bit()` is `1`. + #[inline] + pub fn long_packet_type(&self) -> u8 { + (self.first_byte & LONG_PACKET_TYPE_MASK) >> LONG_PACKET_TYPE_SHIFT + } + + /// Sets the Long Packet Type (bits 5-4 of `first_byte`). Assumes Long Header. + /// + /// # Parameters + /// - `lptype` - The Long Packet Type (0-3). Input is masked to 2 bits. + #[inline] + pub fn set_long_packet_type(&mut self, lptype: u8) { + self.first_byte = (self.first_byte & !LONG_PACKET_TYPE_MASK) + | ((lptype << LONG_PACKET_TYPE_SHIFT) & LONG_PACKET_TYPE_MASK); + } + + /// Gets the Reserved Bits (bits 3-2 of `first_byte`) for common Long Headers. + /// Must be 0 for Initial, 0-RTT, Handshake packets in QUIC v1. + /// + /// # Returns + /// The Reserved Bits value (0-3). Only valid for certain Long Header types. + #[inline] + pub fn reserved_bits_long(&self) -> u8 { + (self.first_byte & RESERVED_BITS_LONG_MASK) >> RESERVED_BITS_LONG_SHIFT + } + + /// Sets the Reserved Bits (bits 3-2 of `first_byte`) for common Long Headers. + /// `val` MUST be 0 for Initial, 0-RTT, Handshake packets in QUIC v1. + /// + /// # Parameters + /// - `val` - The Reserved Bits value (0-3). Input is masked to 2 bits. + #[inline] + pub fn set_reserved_bits_long(&mut self, val: u8) { + self.first_byte = (self.first_byte & !RESERVED_BITS_LONG_MASK) + | ((val << RESERVED_BITS_LONG_SHIFT) & RESERVED_BITS_LONG_MASK); + } + + /// Gets the Packet Number Length bits (bits 1-0 of `first_byte`) for common Long Headers. + /// Encoded length (actual length - 1). + /// + /// # Returns + /// The encoded Packet Number Length value (0-3). Valid for certain Long Header types. + #[inline] + pub fn pn_length_bits_long(&self) -> u8 { + self.first_byte & PN_LENGTH_BITS_MASK + } + + /// Sets the Packet Number Length bits (bits 1-0 of `first_byte`) for common Long Headers. + /// + /// # Parameters + /// - `val` - Encoded 2-bit value (0-3, for actual lengths 1-4 bytes). Masked to 2 bits. + #[inline] + pub fn set_pn_length_bits_long(&mut self, val: u8) { + self.first_byte = (self.first_byte & !PN_LENGTH_BITS_MASK) | (val & PN_LENGTH_BITS_MASK); + } + + /// Gets actual Packet Number Length (bytes) for common Long Headers. (`pn_length_bits_long() + 1`). + /// + /// # Returns + /// Actual Packet Number Length (1 to 4 bytes). + #[inline] + pub fn packet_number_length_long(&self) -> usize { + (self.pn_length_bits_long() + 1) as usize + } + + /// Sets Packet Number Length for common Long Headers, using actual length (1-4 bytes). + /// Clamped if `len` is out of range. + /// + /// # Parameters + /// - `len` - Actual length in bytes (1-4). + #[inline] + pub fn set_packet_number_length_long(&mut self, len: usize) { + let encoded_val = match len { + 1 => 0b00, + 2 => 0b01, + 3 => 0b10, + 4 => 0b11, + _ if len < 1 => 0b00, + _ => 0b11, + }; + self.set_pn_length_bits_long(encoded_val); + } + + /// Gets the Spin Bit (bit 5 of `first_byte`). Assumes Short Header. + /// + /// # Returns + /// `true` if Spin Bit is 1, `false` if 0. Only valid if `!is_long_header()`. + #[inline] + pub fn short_spin_bit(&self) -> bool { + (self.first_byte & SHORT_SPIN_BIT_MASK) != 0 + } + + /// Sets the Spin Bit (bit 5 of `first_byte`). Assumes Short Header. + /// + /// # Parameters + /// - `spin` - Value for the Spin Bit (`true` for 1, `false` for 0). + #[inline] + pub fn set_short_spin_bit(&mut self, spin: bool) { + if spin { + self.first_byte |= SHORT_SPIN_BIT_MASK; + } else { + self.first_byte &= !SHORT_SPIN_BIT_MASK; + } + } + + /// Gets the Reserved Bits (bits 4-3 of `first_byte`). Assumes Short Header. + /// These bits MUST be 0 in QUIC v1. + /// + /// # Returns + /// The value of the Reserved Bits (0-3). Only valid if `!is_long_header()`. + #[inline] + pub fn short_reserved_bits(&self) -> u8 { + (self.first_byte & SHORT_RESERVED_BITS_MASK) >> SHORT_RESERVED_BITS_SHIFT + } + + /// Sets the Reserved Bits (bits 4-3 of `first_byte`). Assumes Short Header. + /// These bits MUST be set to 0 (0b00) in QUIC v1. This method enforces this. + /// + /// # Parameters + /// - `reserved` - The value for the Reserved Bits. If not 0, they will be set to 0. + #[inline] + pub fn set_short_reserved_bits(&mut self, _reserved: u8) { + self.first_byte &= !SHORT_RESERVED_BITS_MASK; + } + + /// Gets the Key Phase Bit (bit 2 of `first_byte`). Assumes Short Header. + /// + /// # Returns + /// `true` if Key Phase Bit is 1, `false` if 0. Only valid if `!is_long_header()`. + #[inline] + pub fn short_key_phase(&self) -> bool { + (self.first_byte & SHORT_KEY_PHASE_BIT_MASK) != 0 + } + + /// Sets the Key Phase Bit (bit 2 of `first_byte`). Assumes Short Header. + /// + /// # Parameters + /// - `key_phase` - Value for the Key Phase Bit (`true` for 1, `false` for 0). + #[inline] + pub fn set_short_key_phase(&mut self, key_phase: bool) { + if key_phase { + self.first_byte |= SHORT_KEY_PHASE_BIT_MASK; + } else { + self.first_byte &= !SHORT_KEY_PHASE_BIT_MASK; + } + } + + /// Gets the Packet Number Length bits (bits 1-0 of `first_byte`). Assumes Short Header. + /// Encoded length (actual length - 1). + /// + /// # Returns + /// The encoded Packet Number Length value (0-3). Only valid if `!is_long_header()`. + #[inline] + pub fn short_pn_length_bits(&self) -> u8 { + self.first_byte & PN_LENGTH_BITS_MASK + } + + /// Sets the Packet Number Length bits (bits 1-0 of `first_byte`). Assumes Short Header. + /// + /// # Parameters + /// - `val` - Encoded 2-bit value (0-3, for actual lengths 1-4 bytes). Masked to 2 bits. + #[inline] + pub fn set_short_pn_length_bits(&mut self, val: u8) { + self.first_byte = (self.first_byte & !PN_LENGTH_BITS_MASK) | (val & PN_LENGTH_BITS_MASK); + } + + /// Gets actual Packet Number Length (bytes) for Short Headers. (`short_pn_length_bits() + 1`). + /// + /// # Returns + /// Actual Packet Number Length (1 to 4 bytes). + #[inline] + pub fn short_packet_number_length(&self) -> usize { + (self.short_pn_length_bits() + 1) as usize + } + + /// Sets Packet Number Length for Short Headers, using actual length (1-4 bytes). + /// Clamped if `len` is out of range. + /// + /// # Parameters + /// - `len` - Actual length in bytes (1-4). + #[inline] + pub fn set_short_packet_number_length(&mut self, len: usize) { + let encoded_val = match len { + 1 => 0b00, + 2 => 0b01, + 3 => 0b10, + 4 => 0b11, + _ if len < 1 => 0b00, + _ => 0b11, + }; + self.set_short_pn_length_bits(encoded_val); + } + + /// Returns the QUIC version from the header (host byte order). Long Headers only. + /// + /// # Returns + /// The QUIC version. Panics or returns garbage if called on a Short Header's `QuicHdr`. + #[inline] + pub fn version(&self) -> u32 { + u32::from_be_bytes(self.version) + } + + /// Sets the QUIC version in the header. `version` should be host byte order. Long Headers only. + /// + /// # Parameters + /// - `version` - The QUIC version (host byte order). + #[inline] + pub fn set_version(&mut self, version: u32) { + self.version = version.to_be_bytes(); + } + + /// Returns Destination Connection ID Length. For Long Headers, this is from the header. + /// For Short Headers, this field in the struct is not from the wire's first byte. + /// + /// # Returns + /// The DCID length. + #[inline] + pub fn dc_id_len(&self) -> u8 { + self.dc_id_len + } + + /// Sets Destination Connection ID Length. + /// + /// # Parameters + /// - `len` - The new DCID length. + #[inline] + pub fn set_dc_id_len(&mut self, len: u8) { + self.dc_id_len = len; + } + + /// Returns Source Connection ID Length. Long Headers only. + /// + /// # Returns + /// The SCID length. + #[inline] + pub fn sc_id_len(&self) -> u8 { + self.sc_id_len + } + + /// Sets Source Connection ID Length. Long Headers only. + /// + /// # Parameters + /// - `len` - The new SCID length. + #[inline] + pub fn set_sc_id_len(&mut self, len: u8) { + self.sc_id_len = len; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_quic_hdr_len() { + assert_eq!(QuicHdr::LEN, 7, "QuicHdr::LEN should be 7 bytes"); + } + + #[test] + fn test_quic_hdr_new_and_long_header_getters() { + let version_val = 0x00000001; + let dc_id_len_val = 8; + let sc_id_len_val = 4; + let pn_len_bits_val = 0b11; + let hdr = QuicHdr::new(version_val, dc_id_len_val, sc_id_len_val, pn_len_bits_val); + assert!(hdr.is_long_header()); + assert_eq!( + hdr.fixed_bit(), + 1, + "Fixed bit should be 1 for Initial packet" + ); + assert_eq!( + hdr.long_packet_type(), + 0b00, + "Long Packet Type should be Initial (00)" + ); + assert_eq!(hdr.reserved_bits_long(), 0b00, "Reserved bits should be 00"); + assert_eq!(hdr.pn_length_bits_long(), pn_len_bits_val); + assert_eq!( + hdr.packet_number_length_long(), + (pn_len_bits_val + 1) as usize + ); + let expected_first_byte = HEADER_FORM_BIT + | FIXED_BIT_MASK + | (0b00 << LONG_PACKET_TYPE_SHIFT) + | (0b00 << RESERVED_BITS_LONG_SHIFT) + | pn_len_bits_val; + assert_eq!( + hdr.first_byte(), + expected_first_byte, + "First byte construction for Long Header is incorrect" + ); + assert_eq!(hdr.version(), version_val); + assert_eq!(hdr.dc_id_len(), dc_id_len_val); + assert_eq!(hdr.sc_id_len(), sc_id_len_val); + } + + #[test] + fn test_first_byte_setters_and_getters_for_long_header() { + let mut hdr = QuicHdr::new(1, 0, 0, 0); + hdr.set_header_form(false); + assert!(!hdr.is_long_header()); + hdr.set_header_form(true); + assert!(hdr.is_long_header()); + hdr.set_fixed_bit(0); + assert_eq!(hdr.fixed_bit(), 0); + hdr.set_fixed_bit(1); + assert_eq!(hdr.fixed_bit(), 1); + hdr.set_long_packet_type(0b01); + assert_eq!(hdr.long_packet_type(), 0b01); + hdr.set_reserved_bits_long(0b10); + assert_eq!(hdr.reserved_bits_long(), 0b10); + hdr.set_reserved_bits_long(0b00); + hdr.set_pn_length_bits_long(0b01); + assert_eq!(hdr.pn_length_bits_long(), 0b01); + assert_eq!(hdr.packet_number_length_long(), 2); + hdr.set_packet_number_length_long(4); + assert_eq!(hdr.pn_length_bits_long(), 0b11); + assert_eq!(hdr.packet_number_length_long(), 4); + hdr.set_packet_number_length_long(0); + assert_eq!(hdr.pn_length_bits_long(), 0b00); + assert_eq!(hdr.packet_number_length_long(), 1); + hdr.set_packet_number_length_long(5); + assert_eq!(hdr.pn_length_bits_long(), 0b11); + assert_eq!(hdr.packet_number_length_long(), 4); + } + + #[test] + fn test_multi_byte_field_getters_and_setters() { + let mut hdr = QuicHdr::default(); + let test_version = 0x12345678; + hdr.set_version(test_version); + assert_eq!(hdr.version(), test_version); + let test_dcid_len = 20; + hdr.set_dc_id_len(test_dcid_len); + assert_eq!(hdr.dc_id_len(), test_dcid_len); + let test_scid_len = 0; + hdr.set_sc_id_len(test_scid_len); + assert_eq!(hdr.sc_id_len(), test_scid_len); + } + + #[test] + fn test_new_short_header_first_byte() { + let fb1 = QuicHdr::new_short_header_first_byte(true, false, 0b01); + assert_eq!(fb1, 0b01100001, "Expected 0x61"); + assert_eq!( + (fb1 & HEADER_FORM_BIT), + 0, + "Short header form bit incorrect" + ); + assert_eq!( + (fb1 & FIXED_BIT_MASK), + FIXED_BIT_MASK, + "Short header fixed bit incorrect" + ); + assert_eq!( + (fb1 & SHORT_SPIN_BIT_MASK) >> SHORT_SPIN_BIT_SHIFT, + 1, + "Spin bit incorrect" + ); + assert_eq!( + (fb1 & SHORT_RESERVED_BITS_MASK), + 0, + "Reserved bits not zero" + ); + assert_eq!( + (fb1 & SHORT_KEY_PHASE_BIT_MASK) >> SHORT_KEY_PHASE_BIT_SHIFT, + 0, + "Key phase incorrect" + ); + assert_eq!( + (fb1 & PN_LENGTH_BITS_MASK), + 0b01, + "PN length bits incorrect" + ); + let fb2 = QuicHdr::new_short_header_first_byte(false, true, 0b11); + assert_eq!(fb2, 0b01000111, "Expected 0x47"); + assert_eq!((fb2 & SHORT_SPIN_BIT_MASK) >> SHORT_SPIN_BIT_SHIFT, 0); + assert_eq!( + (fb2 & SHORT_KEY_PHASE_BIT_MASK) >> SHORT_KEY_PHASE_BIT_SHIFT, + 1 + ); + assert_eq!((fb2 & PN_LENGTH_BITS_MASK), 0b11); + } + + #[test] + fn test_short_header_setters_and_getters() { + let mut hdr = QuicHdr::default(); + hdr.set_header_form(false); + hdr.set_fixed_bit(1); + hdr.set_short_spin_bit(true); + assert!(hdr.short_spin_bit()); + assert_eq!(hdr.first_byte & SHORT_SPIN_BIT_MASK, SHORT_SPIN_BIT_MASK); + hdr.set_short_spin_bit(false); + assert!(!hdr.short_spin_bit()); + assert_eq!(hdr.first_byte & SHORT_SPIN_BIT_MASK, 0); + hdr.first_byte |= SHORT_RESERVED_BITS_MASK; + assert_eq!(hdr.short_reserved_bits(), 0b11); + hdr.set_short_reserved_bits(0b10); + assert_eq!( + hdr.short_reserved_bits(), + 0b00, + "Short reserved bits should be forced to 0" + ); + assert_eq!(hdr.first_byte & SHORT_RESERVED_BITS_MASK, 0); + hdr.set_short_key_phase(true); + assert!(hdr.short_key_phase()); + assert_eq!( + hdr.first_byte & SHORT_KEY_PHASE_BIT_MASK, + SHORT_KEY_PHASE_BIT_MASK + ); + hdr.set_short_key_phase(false); + assert!(!hdr.short_key_phase()); + assert_eq!(hdr.first_byte & SHORT_KEY_PHASE_BIT_MASK, 0); + hdr.set_short_pn_length_bits(0b10); // 3 bytes + assert_eq!(hdr.short_pn_length_bits(), 0b10); + assert_eq!(hdr.short_packet_number_length(), 3); + hdr.set_short_packet_number_length(1); + assert_eq!(hdr.short_pn_length_bits(), 0b00); + assert_eq!(hdr.short_packet_number_length(), 1); + hdr.set_short_packet_number_length(4); + assert_eq!(hdr.short_pn_length_bits(), 0b11); + assert_eq!(hdr.short_packet_number_length(), 4); + hdr.set_short_packet_number_length(0); + assert_eq!(hdr.short_pn_length_bits(), 0b00); + assert_eq!(hdr.short_packet_number_length(), 1); + hdr.set_short_packet_number_length(5); + assert_eq!(hdr.short_pn_length_bits(), 0b11); + assert_eq!(hdr.short_packet_number_length(), 4); + hdr.set_header_form(false); + hdr.set_fixed_bit(1); + hdr.set_short_spin_bit(true); + hdr.set_short_reserved_bits(0); + hdr.set_short_key_phase(true); + hdr.set_short_pn_length_bits(0b01); + assert_eq!( + hdr.first_byte(), + 0b01100101, + "Constructed short header byte mismatch" + ); + } + + #[test] + fn test_raw_first_byte_combined_set_and_readback() { + let mut hdr = QuicHdr::new(1, 0, 0, 0); + // Construct first_byte: Long Header (1), Fixed (1), Type (Retry=11), Reserved (00), PN Len (2 bytes actual = 01 encoded) + // Bit: 7 6 5 4 3 2 1 0 + // Value: 1 1 1 1 0 0 0 1 => 0xF1 + let first_byte_val = 0xF1; + hdr.set_first_byte(first_byte_val); + assert_eq!(hdr.first_byte(), first_byte_val); + assert!(hdr.is_long_header()); + assert_eq!(hdr.fixed_bit(), 1); + assert_eq!(hdr.long_packet_type(), 0b11); + assert_eq!(hdr.reserved_bits_long(), 0b00); + assert_eq!(hdr.pn_length_bits_long(), 0b01); + assert_eq!(hdr.packet_number_length_long(), 2); + } +} From f6c1d13d7803bb7dca74dbf3e185e4829cf93368 Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Thu, 12 Jun 2025 13:08:30 -0500 Subject: [PATCH 02/14] Refactored quic.rs and added integration test. --- Cargo.toml | 2 + src/quic.rs | 2670 ++++++++++++++++++++++++++++----- tests/quic_hdr_integration.rs | 171 +++ 3 files changed, 2475 insertions(+), 368 deletions(-) create mode 100644 tests/quic_hdr_integration.rs diff --git a/Cargo.toml b/Cargo.toml index 43ab804..8eb6c10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ edition = "2021" serde = { version = "1", default-features = false, features = ["derive"], optional = true } [dev-dependencies] +lazy_static = "1.5.0" aya-ebpf = { git = "https://github.com/aya-rs/aya", branch = "main" } aya-log-ebpf = { git = "https://github.com/aya-rs/aya", branch = "main" } bincode = { version = "2.0.0-rc.3", default-features = false, features = ["serde","alloc"] } + diff --git a/src/quic.rs b/src/quic.rs index 1f368b3..409ef86 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -1,258 +1,1518 @@ -use core::mem; +//! QUIC header and Connection‑ID types with full support for +//! variable‑length IDs, long/short header forms and zero‑allocation +//! (kernel‑friendly) (de)serialisation. +//! +//! Designed for use inside eBPF (`aya`) programs → `#![no_std]`, +//! fixed‑capacity buffers, no heap, packed layouts. -const HEADER_FORM_BIT: u8 = 0x80; -const FIXED_BIT_MASK: u8 = 0x40; -const LONG_PACKET_TYPE_MASK: u8 = 0x30; +#![cfg_attr(not(test), no_std)] + +use core::{cmp, fmt, hash, ptr}; + +pub const QUIC_MAX_CID_LEN: usize = 20; + +const HEADER_FORM_BIT: u8 = 0x80; // Bit 7: 1 for Long Header, 0 for Short Header +const FIXED_BIT_MASK: u8 = 0x40; // Bit 6: Must be 1 in QUIC v1 (except Version Negotiation) + +const LONG_PACKET_TYPE_MASK: u8 = 0x30; // Bits 5-4: Packet Type const LONG_PACKET_TYPE_SHIFT: u8 = 4; -const RESERVED_BITS_LONG_MASK: u8 = 0x0C; +const RESERVED_BITS_LONG_MASK: u8 = 0x0C; // Bits 3-2: Reserved (Type-Specific in some cases like Retry) const RESERVED_BITS_LONG_SHIFT: u8 = 2; -const SHORT_SPIN_BIT_MASK: u8 = 0x20; -const SHORT_SPIN_BIT_SHIFT: u8 = 5; -const SHORT_RESERVED_BITS_MASK: u8 = 0x18; + +const SHORT_SPIN_BIT_MASK: u8 = 0x20; // Bit 5: Spin Bit +const SHORT_RESERVED_BITS_MASK: u8 = 0x18; // Bits 4-3: Reserved const SHORT_RESERVED_BITS_SHIFT: u8 = 3; -const SHORT_KEY_PHASE_BIT_MASK: u8 = 0x04; -const SHORT_KEY_PHASE_BIT_SHIFT: u8 = 2; +const SHORT_KEY_PHASE_BIT_MASK: u8 = 0x04; // Bit 2: Key Phase + +const PN_LENGTH_BITS_MASK: u8 = 0x03; // Bits 1-0: Encoded Packet Number Length (actual_length - 1) + +/// Error type for safe getter/setter operations on `QuicHdr`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum QuicHdrError { + /// Operation is not applicable to the current header form (e.g., asking for Long Packet Type on a Short Header). + InvalidHeaderForm, + /// Provided length for a field (e.g. CID) is invalid or exceeds maximum allowed. + InvalidLength, +} + +impl fmt::Display for QuicHdrError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + QuicHdrError::InvalidHeaderForm => { + write!(f, "Operation invalid for current QUIC header form") + } + QuicHdrError::InvalidLength => { + write!(f, "Invalid length provided for a QUIC header field") + } + } + } +} + +/// Macro to implement common functionality for Connection ID wrapper structs. +macro_rules! impl_cid_common { + ($t:ident, $doc_name:literal, $with_len_on_wire:tt) => { + #[doc = concat!("Wrapper for a QUIC ", $doc_name, " Connection ID.")] + /// + /// Stores the CID bytes in a fixed-size buffer (`QUIC_MAX_CID_LEN`) + /// and tracks its actual current length via an internal `len` field. + /// + /// For CIDs in Long Headers (like `QuicDstConnLong`, `QuicSrcConnLong`), the length byte + /// is also present on the wire preceding the CID bytes. The `len` field in these structs + /// directly corresponds to this on-wire length byte. + /// + /// For CIDs in Short Headers (like `QuicDstConnShort`), the length is implicit or contextually + /// known (e.g., from connection state). The internal `len` field in `QuicDstConnShort` + /// still tracks the CID's actual length for consistency and for `as_slice()`, but this + /// `len` field itself is *not* serialized as a separate byte on the wire for Short Header CIDs. + /// + /// # Examples + /// + /// ``` + /// // This example is generic. See specific types like QuicDstConnLong or QuicDstConnShort + /// // for more tailored examples. + /// use network_types::quic::{QuicDstConnLong, QUIC_MAX_CID_LEN}; // Using QuicDstConnLong as an example + /// + /// // Create a new, empty CID + /// let mut cid = QuicDstConnLong::new(); + /// assert_eq!(cid.len(), 0); + /// assert!(cid.is_empty()); + /// + /// // Set the CID value + /// let cid_data = [0x01, 0x02, 0x03, 0x04]; + /// cid.set(&cid_data); + /// assert_eq!(cid.len(), 4); + /// assert_eq!(cid.as_slice(), &cid_data); + /// + /// // Example eBPF context considerations: + /// // If this CID struct (e.g., QuicDstConnLong) matches a part of your packet structure + /// // that includes the length byte on the wire: + /// // ```no_run + /// # use core::ptr; + /// # #[repr(C, packed)] struct MyPacketPart { a_cid: QuicDstConnLong } + /// # let base_ptr: *const MyPacketPart = ptr::null(); // Ptr to packet data part + /// // let cid_from_packet = unsafe { ptr::read_volatile(&((*base_ptr).a_cid)) }; + /// // assert!(cid_from_packet.len() <= QUIC_MAX_CID_LEN as u8); + /// // Process `cid_from_packet.as_slice()`... + /// // ``` + /// // + /// // If the CID struct is like QuicDstConnShort where 'len' is not on wire with the CID: + /// // You would determine 'known_cid_len' from context, read 'known_cid_len' bytes, + /// // then use `cid.set()` to populate a stack instance of QuicDstConnShort. + /// // ```no_run + /// # use network_types::quic::{QuicDstConnShort}; + /// # let packet_cid_bytes_ptr: *const u8 = core::ptr::null(); // Ptr to raw CID bytes in packet + /// # let known_cid_len: usize = 8; // Length from eBPF map or connection state + /// let mut my_cid_short = QuicDstConnShort::new(); + /// if known_cid_len > 0 && known_cid_len <= QUIC_MAX_CID_LEN { + /// let mut cid_buffer = [0u8; QUIC_MAX_CID_LEN]; + /// // In eBPF: unsafe { bpf_probe_read_kernel(...) } to copy bytes here + /// // For this example, assume cid_buffer is populated from packet_cid_bytes_ptr + /// // unsafe { core::ptr::copy_nonoverlapping(packet_cid_bytes_ptr, cid_buffer.as_mut_ptr(), known_cid_len); } + /// my_cid_short.set(&cid_buffer[..known_cid_len]); + /// // assert_eq!(my_cid_short.len(), known_cid_len as u8); + /// } + /// // ``` + /// ``` + #[repr(C, packed)] + #[derive(Copy, Clone)] + pub struct $t { + len: u8, + bytes: [u8; QUIC_MAX_CID_LEN], + } + + impl $t { + + pub const LEN: usize = core::mem::size_of::(); + + /// Construct a new (empty) ID – all bytes zero, length 0. + /// + /// # Returns + /// A new, zero-initialized CID instance with `len = 0`. + #[inline] + pub const fn new() -> Self { + Self { + len: 0, + bytes: [0u8; QUIC_MAX_CID_LEN], + } + } + /// Current byte‑length of the CID. + /// + /// # Returns + /// The actual length of the CID data stored, from 0 to `QUIC_MAX_CID_LEN`. + #[inline] + pub const fn len(&self) -> u8 { + self.len + } + /// True if CID length is zero. + /// + /// # Returns + /// `true` if the CID has a length of 0, `false` otherwise. + #[inline] + pub const fn is_empty(&self) -> bool { + self.len == 0 + } + /// Slice containing only the used part of the CID. + /// + /// # Returns + /// A `&[u8]` slice representing the actual CID bytes. + /// The slice length is determined by `self.len()`. + /// + /// # Safety + /// This method uses `get_unchecked` for performance, relying on the invariant + /// that `self.len` is always `<= QUIC_MAX_CID_LEN`, which is maintained by `set()`. + #[inline] + pub fn as_slice(&self) -> &[u8] { + unsafe { self.bytes.get_unchecked(..self.len as usize) } + } + /// Mutable slice containing only the used part of the CID. + /// + /// # Returns + /// A `&mut [u8]` mutable slice representing the actual CID bytes. + /// The slice length is determined by `self.len()`. + /// + /// # Safety + /// This method uses `get_unchecked_mut` for performance, relying on the invariant + /// that `self.len` is always `<= QUIC_MAX_CID_LEN`, which is maintained by `set()`. + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { self.bytes.get_unchecked_mut(..self.len as usize) } + } + /// Set the ID from a byte‑slice. + /// Excess bytes from `data` are truncated if `data.len() > QUIC_MAX_CID_LEN`. + /// Bytes within the internal buffer beyond the new length (`n`) are zeroed + /// to ensure consistent behavior for equality checks and hashing. + /// + /// # Parameters + /// * `data`: A byte slice containing the new CID data. + pub fn set(&mut self, data: &[u8]) { + let n = cmp::min(data.len(), QUIC_MAX_CID_LEN); + // Safety: `n` is guaranteed to be `<= QUIC_MAX_CID_LEN`. + // `self.bytes.as_mut_ptr()` is valid for writes up to `QUIC_MAX_CID_LEN`. + // `data.as_ptr()` is valid for reads up to `data.len()`. + // `copy_nonoverlapping` is safe as `n` respects the shorter of these lengths. + unsafe { + ptr::copy_nonoverlapping(data.as_ptr(), self.bytes.as_mut_ptr(), n); + } + if n < QUIC_MAX_CID_LEN { + // Zero the remaining padding to guarantee deterministic equality & hashing. + // Safety: `n < QUIC_MAX_CID_LEN` ensures `self.bytes.as_mut_ptr().add(n)` is valid + // and `QUIC_MAX_CID_LEN - n` is the correct count of bytes to zero. + unsafe { + ptr::write_bytes(self.bytes.as_mut_ptr().add(n), 0, QUIC_MAX_CID_LEN - n); + } + } + self.len = n as u8; + } + } -const PN_LENGTH_BITS_MASK: u8 = 0x03; -/// Mask for Packet Number Length bits (bits 1-0). + impl Default for $t { + fn default() -> Self { + Self::new() + } + } + + impl fmt::Debug for $t { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}(", stringify!($t))?; + for (i, b_val) in self.as_slice().iter().enumerate() { + if i > 0 { + write!(f, ":")?; + } + write!(f, "{:02x}", b_val)?; + } + write!(f, ")") + } + } + impl cmp::PartialEq for $t { + fn eq(&self, other: &Self) -> bool { + self.len == other.len && self.as_slice() == other.as_slice() + } + } + impl Eq for $t {} + impl hash::Hash for $t { + fn hash(&self, state: &mut H) { + self.len.hash(state); + state.write(self.as_slice()); + } + } + + #[cfg(feature = "serde")] + mod serde_cid_impl { + use super::*; + use serde::{ + de::{self, Visitor}, + ser::SerializeStruct, + Deserializer, Serializer, + }; + + impl serde::Serialize for $t { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if $with_len_on_wire { + let mut st = serializer.serialize_struct(stringify!($t), 2)?; + st.serialize_field("len", &self.len)?; + st.serialize_field("bytes", &serde_bytes::Bytes::new(self.as_slice()))?; + st.end() + } else { + serializer.serialize_bytes(self.as_slice()) + } + } + } + + struct CidVisitor; + impl<'de> Visitor<'de> for CidVisitor { + type Value = $t; + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + if $with_len_on_wire { + write!(f, "length-prefixed QUIC Connection-ID (len, bytes)") + } else { + write!(f, "raw QUIC Connection-ID bytes") + } + } -/// Represents the 7-byte fixed-size prefix of a QUIC Long Header or provides methods -/// to interpret the first byte of a QUIC Short Header. + fn visit_seq(self, mut seq: A) -> Result<$t, A::Error> + where + A: de::SeqAccess<'de>, + { + let len: u8 = seq.next_element()?.ok_or_else(|| { + de::Error::invalid_length(0, &"Missing CID len in sequence") + })?; + let bytes_buf: serde_bytes::ByteBuf = seq.next_element()?.ok_or_else(|| { + de::Error::invalid_length(1, &"Missing CID bytes in sequence") + })?; + + if bytes_buf.len() != len as usize { + return Err(de::Error::invalid_value( + de::Unexpected::Bytes(bytes_buf.as_ref()), + &format!("CID bytes for explicit length {}", len).as_ref(), + )); + } + if len > QUIC_MAX_CID_LEN as u8 { + return Err(de::Error::invalid_length( + len as usize, + &format!("CID length {} exceeds max {}", len, QUIC_MAX_CID_LEN) + .as_ref(), + )); + } + let mut id = $t::new(); + id.set(bytes_buf.as_ref()); + id.len = len; // Ensure `len` is set correctly after `set` + Ok(id) + } + + fn visit_bytes(self, v: &[u8]) -> Result<$t, E> + where + E: de::Error, + { + if v.len() > QUIC_MAX_CID_LEN { + return Err(de::Error::invalid_length( + v.len(), + &format!("CID length {} exceeds max {}", v.len(), QUIC_MAX_CID_LEN) + .as_ref(), + )); + } + let mut id = $t::new(); + id.set(v); + Ok(id) + } + } + + impl<'de> serde::Deserialize<'de> for $t { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if $with_len_on_wire { + // For length-prefixed, expect a sequence of (len, bytes) + deserializer.deserialize_tuple(2, CidVisitor) + } else { + // For raw bytes, expect just the bytes + deserializer.deserialize_bytes(CidVisitor) + } + } + } + } + }; +} + +impl_cid_common!(QuicDstConnLong, "Destination (Long Hdr)", true); +impl_cid_common!(QuicSrcConnLong, "Source (Long Hdr)", true); +impl_cid_common!(QuicDstConnShort, "Destination (Short Hdr)", false); + +/// Invariant fields specific to QUIC **Long Headers**. +/// +/// Contains the QUIC version and variable-length Destination and Source Connection IDs. /// +/// # Examples +/// +/// ``` +/// use network_types::quic::{QuicHdrLong, QuicDstConnLong, QuicSrcConnLong}; +/// +/// let mut long_hdr_fields = QuicHdrLong::default(); +/// long_hdr_fields.set_version(0x00000001); // QUIC v1 +/// +/// let dcid_data = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]; +/// let scid_data = [0xAA, 0xBB, 0xCC, 0xDD]; +/// +/// long_hdr_fields.dst.set(&dcid_data); +/// long_hdr_fields.src.set(&scid_data); +/// +/// assert_eq!(long_hdr_fields.version(), 0x00000001); +/// assert_eq!(long_hdr_fields.dst.len(), 8); +/// assert_eq!(long_hdr_fields.dst.as_slice(), &dcid_data); +/// assert_eq!(long_hdr_fields.src.len(), 4); +/// assert_eq!(long_hdr_fields.src.as_slice(), &scid_data); +/// +/// // In an eBPF program, this struct would typically be part of a `QuicHdr`'s `inner.long` +/// // field. If `quic_hdr_ptr` is a pointer to a `QuicHdr` (known to be Long Header) +/// // in packet memory: +/// // ```no_run +/// # use network_types::quic::{QuicHdr, QuicHeaderType}; +/// # use core::ptr; +/// # let mut quic_hdr_on_stack = QuicHdr::new(QuicHeaderType::QuicLong); // Example stack instance +/// # let quic_hdr_ptr: *const QuicHdr = &quic_hdr_on_stack; +/// // // Assume quic_hdr_ptr is valid and points to a Long Header QuicHdr. +/// // let long_specific_ptr: *const QuicHdrLong = unsafe { &(*quic_hdr_ptr).inner.long }; +/// // let version = unsafe { (*long_specific_ptr).version() }; +/// // let dcid_len = unsafe { (*long_specific_ptr).dst.len() }; +/// // // Access to CID bytes via: unsafe { (*long_specific_ptr).dst.as_slice() } +/// // // Important: This assumes `quic_hdr_ptr` and its `inner.long` field are correctly +/// // // populated, which requires careful parsing in eBPF. +/// // ``` +/// ``` #[repr(C, packed)] -#[derive(Debug, Copy, Clone, Default)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct QuicHdr { - /// The first byte of the QUIC header. Its interpretation depends on the Header Form bit. - /// - Bit 7: Header Form (1 for Long, 0 for Short) - /// - Bit 6: Fixed Bit (usually 1) - /// - Bits 5-0: Type/Flag specific bits. - pub first_byte: u8, - /// QUIC version (e.g., 0x00000001 for QUIC v1). Network byte order. Only in Long Headers. +#[derive(Copy, Clone, Default, Debug)] +pub struct QuicHdrLong { + /// QUIC Version (e.g., `0x00000001` for v1). Network byte order. pub version: [u8; 4], - /// Destination Connection ID Length. Only in Long Headers (explicitly). For Short Headers, - /// this field might be used by application logic to store the known DCID length. - pub dc_id_len: u8, - /// Source Connection ID Length. Only in Long Headers. - pub sc_id_len: u8, + /// Destination Connection ID, including its on-wire length byte (managed by `QuicDstConnLong`). + pub dst: QuicDstConnLong, + /// Source Connection ID, including its on-wire length byte (managed by `QuicSrcConnLong`). + pub src: QuicSrcConnLong, } +impl QuicHdrLong { -impl QuicHdr { - /// Length of the `QuicHdr` struct, relevant for Long Headers. - pub const LEN: usize = mem::size_of::(); + pub const LEN: usize = core::mem::size_of::(); + + /// The minimum length of a Long Header on the wire, consisting of: + /// 1 (First Byte, part of `QuicHdr`) + 4 (Version) + 1 (DCID Len Byte) + 1 (SCID Len Byte) = 7 bytes. + /// This does not include any actual CID data bytes. + pub const MIN_LEN_ON_WIRE: usize = 7; - /// Creates a `QuicHdr` for a Long Header, configured as an Initial packet. + /// Gets the QUIC version in host byte order. /// - /// The first byte is set for Long Header (form bit = 1), Fixed Bit = 1, - /// Long Packet Type = Initial (0b00), and Reserved Bits = 0. + /// # Returns + /// The 32-bit QUIC version number. + #[inline] + pub fn version(&self) -> u32 { + u32::from_be_bytes(self.version) + } + /// Sets the QUIC version. /// /// # Parameters - /// - `version` - QUIC version (e.g., `0x00000001` for v1), host byte order. - /// - `dc_id_len` - Destination Connection ID length. - /// - `sc_id_len` - Source Connection ID length. - /// - `pn_len_bits` - Encoded Packet Number Length (actual length - 1, value 0-3). - /// - /// # Returns - /// A new `QuicHdr` instance. - pub fn new(version: u32, dc_id_len: u8, sc_id_len: u8, pn_len_bits: u8) -> Self { - let first_byte = HEADER_FORM_BIT - | FIXED_BIT_MASK - | (0b00 << LONG_PACKET_TYPE_SHIFT) - | (0b00 << RESERVED_BITS_LONG_SHIFT) - | (pn_len_bits & PN_LENGTH_BITS_MASK); + /// * `v`: The QUIC version in host byte order. It will be stored in network byte order. + #[inline] + pub fn set_version(&mut self, v: u32) { + self.version = v.to_be_bytes(); + } +} + +/// Invariant fields specific to QUIC **Short Headers** (1-RTT packets). +/// +/// Contains the Destination Connection ID. The length of the DCID is not +/// encoded directly in the short header; it must be known from the connection context. +/// QUIC v1 Short Headers do not have a Source Connection ID. +/// +/// # Examples +/// +/// ``` +/// use network_types::quic::{QuicHdrShort, QuicDstConnShort}; +/// +/// let mut short_hdr_fields = QuicHdrShort::default(); +/// +/// let dcid_data = [0x11, 0x22, 0x33, 0x44, 0x55]; +/// // For short headers, the length of the DCID is known contextually. +/// // The `QuicDstConnShort` struct's `set` method will store this data +/// // and internally track its length. +/// short_hdr_fields.dst.set(&dcid_data); +/// +/// assert_eq!(short_hdr_fields.dst.len(), 5); +/// assert_eq!(short_hdr_fields.dst.as_slice(), &dcid_data); +/// +/// // In an eBPF program, `QuicHdrShort` would be part of `QuicHdr.inner.short`. +/// // The DCID bytes would be read from the packet based on a contextually known length. +/// // That length would also be used to initialize `QuicHdr.header_type` +/// // (e.g., `QuicHeaderType::QuicShort { dc_id_len: known_len }`). +/// // +/// // ```no_run +/// # use network_types::quic::{QuicHdr, QuicHeaderType, QUIC_MAX_CID_LEN}; +/// # use core::ptr; +/// # let known_dcid_len_from_context = 5u8; +/// # let mut quic_hdr_on_stack = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: known_dcid_len_from_context }); +/// // // Populate quic_hdr_on_stack.inner.short.dst from packet data... +/// // // For example, if packet_dcid_ptr points to the DCID bytes in the packet: +/// # let packet_dcid_ptr: *const u8 = core::ptr::null(); // Example pointer to DCID data in packet +/// // if known_dcid_len_from_context > 0 { +/// // let mut temp_cid_buf = [0u8; QUIC_MAX_CID_LEN]; +/// // // In eBPF: unsafe { bpf_probe_read_kernel(temp_cid_buf.as_mut_ptr(), known_dcid_len_from_context as u32, packet_dcid_ptr) }; +/// // // For example purposes, assume temp_cid_buf is populated: +/// // // unsafe { ptr::copy_nonoverlapping(packet_dcid_ptr, temp_cid_buf.as_mut_ptr(), known_dcid_len_from_context as usize); } +/// // unsafe { quic_hdr_on_stack.inner.short.dst.set(&temp_cid_buf[..known_dcid_len_from_context as usize]); } +/// // } +/// // +/// // // Accessing via the QuicHdr instance: +/// // assert_eq!(quic_hdr_on_stack.dc_id().len(), known_dcid_len_from_context as usize); +/// // ``` +/// ``` +#[repr(C, packed)] +#[derive(Copy, Clone, Default, Debug)] +pub struct QuicHdrShort { + /// Destination Connection ID. Its length is determined contextually. + /// `QuicDstConnShort` internally tracks its `len` for `as_slice` but this `len` + /// is not part of the on-wire format for the Short Header DCID itself. + pub dst: QuicDstConnShort, + // No Source CID field in QUIC v1 Short Headers on the wire. +} +impl QuicHdrShort { + pub const LEN: usize = core::mem::size_of::(); + + /// The minimum length of a Short Header on the wire, consisting of: + /// 1 (First Byte, part of `QuicHdr`) + 0 (DCID bytes, if DCID len is 0) = 1 byte. + pub const MIN_LEN_ON_WIRE: usize = 1; +} + +/// Union to hold either Long or Short header-specific data. +/// +/// This allows `QuicHdr` to have a fixed size while representing variable structures. +/// Access must be guarded by checking `QuicHdr::is_long_header()` or `QuicHdr::header_type`. +/// +/// # Note on eBPF Usage +/// +/// In eBPF programs, direct access to union fields (e.g., `hdr.inner.long` or `hdr.inner.short`) +/// must be done with extreme care, ensuring that the `QuicHdr` instance's `header_type` +/// correctly reflects the actual packet type being processed. The `QuicHdr` safe accessor methods +/// (like `version()`, `dc_id()`, etc.) handle these checks. +/// +/// If constructing a `QuicHdr` from packet data in eBPF, the `inner` union would be populated +/// after determining the header type and reading the relevant bytes (version, CIDs, etc.) +/// from the packet. The `QuicHdr::new()` method initializes the union appropriately based +/// on the provided `QuicHeaderType`. +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub union QuicHdrUn { + /// Data for a Long Header. + pub long: QuicHdrLong, + /// Data for a Short Header. + pub short: QuicHdrShort, +} +impl Default for QuicHdrUn { + fn default() -> Self { Self { - first_byte, - version: version.to_be_bytes(), - dc_id_len, - sc_id_len, + long: QuicHdrLong::default(), // Default to long for safety, though QuicHdr::new manages this. } } +} +impl fmt::Debug for QuicHdrUn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Actual data is debugged via QuicHdr, which knows the active variant. + f.write_str("QuicHdrUn { ... }") + } +} - /// Creates the first byte for a QUIC Short Header. - /// - /// Sets Header Form to 0, Fixed Bit to 1, and specified Short Header flags. - /// Reserved bits (4-3) are set to 0. +/// Discriminator for `QuicHdr` to indicate active header form and necessary context. +/// This is a logical field, not directly part of the wire format itself, but aids in (de)serialization +/// and safe access to the `QuicHdrUn` union. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum QuicHeaderType { + /// Indicates a Long Header. All length information for CIDs is in the packet. + QuicLong, + /// Indicates a Short Header. + /// `dc_id_len` is the contextual length of the Destination Connection ID, as this + /// length is not present on the wire for Short Headers. It must be known by the + /// endpoint processing the packet. QUIC v1 Short headers do not have a Source CID. + QuicShort { dc_id_len: u8 }, +} + +/// Represents a QUIC packet header, capable of handling both Long and Short forms +/// with variable-length Connection IDs. +/// +/// The `header_type` field is crucial for interpreting the `inner` union correctly +/// and for proper serialization/deserialization, especially for Short Headers where +/// the Destination CID length is not on the wire. +/// +/// # Examples +/// +/// ## Creating and using a Long Header +/// ``` +/// use network_types::quic::{QuicHdr, QuicHeaderType, QUIC_MAX_CID_LEN}; +/// +/// // Create a new Long Header (e.g., for an Initial packet) +/// let mut long_hdr = QuicHdr::new(QuicHeaderType::QuicLong); +/// +/// // Set Long Header specific fields +/// assert!(long_hdr.set_long_packet_type(0b00).is_ok()); // Initial packet type +/// assert!(long_hdr.set_version(0x00000001).is_ok()); // QUIC v1 +/// assert!(long_hdr.set_packet_number_length_long(4).is_ok()); // 4-byte packet number +/// +/// let dcid_data = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]; +/// let scid_data = [0xAA, 0xBB, 0xCC, 0xDD]; +/// long_hdr.set_dc_id(&dcid_data); // Sets DCID and its length (8) +/// assert!(long_hdr.set_sc_id(&scid_data).is_ok()); // Sets SCID and its length (4) +/// +/// assert!(long_hdr.is_long_header()); +/// assert_eq!(long_hdr.long_packet_type(), Ok(0b00)); +/// assert_eq!(long_hdr.version(), Ok(0x00000001)); +/// assert_eq!(long_hdr.dc_id(), &dcid_data); +/// assert_eq!(long_hdr.sc_id().unwrap(), &scid_data); +/// assert_eq!(long_hdr.dc_id_len_on_wire(), Ok(8)); +/// assert_eq!(long_hdr.sc_id_len_on_wire(), Ok(4)); +/// +/// // The first_byte is also partially set by `new` and field setters: +/// // Example: (Form | Fixed | Type | Reserved | PN Len) +/// // (1 | 1 | 00 | 00 | 11) = 0b11000011 = 0xC3 +/// // Actual first_byte depends on reserved bits and PN length settings. +/// // long_hdr.set_reserved_bits_long(0).unwrap(); // Explicitly set reserved bits +/// // assert_eq!(long_hdr.first_byte(), 0xC3); // If PN Len is 4 (encoded 3) and Type 0 +/// ``` +/// +/// ## Creating and using a Short Header +/// ``` +/// use network_types::quic::{QuicHdr, QuicHeaderType, QUIC_MAX_CID_LEN}; +/// +/// // For Short Headers, the DCID length is not on the wire, so it must be known. +/// let dcid_len_short: u8 = 8; +/// let mut short_hdr = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: dcid_len_short }); +/// +/// // Set Short Header specific fields +/// assert!(short_hdr.set_short_spin_bit(true).is_ok()); +/// assert!(short_hdr.set_short_key_phase(false).is_ok()); +/// assert!(short_hdr.set_short_packet_number_length(2).is_ok()); // 2-byte packet number +/// +/// let dcid_data_short = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]; +/// // Ensure dcid_data_short matches dcid_len_short if providing full slice +/// short_hdr.set_dc_id(&dcid_data_short[..dcid_len_short as usize]); +/// +/// assert!(!short_hdr.is_long_header()); +/// assert_eq!(short_hdr.short_spin_bit(), Ok(true)); +/// assert_eq!(short_hdr.dc_id_effective_len(), dcid_len_short); +/// assert_eq!(short_hdr.dc_id(), &dcid_data_short[..dcid_len_short as usize]); +/// +/// // Attempting to access Long Header fields will fail +/// assert!(short_hdr.version().is_err()); +/// assert!(short_hdr.sc_id().is_err()); +/// ``` +/// +/// ## eBPF Context Example Snippet +/// ```no_run +/// use core::{mem, ptr, cmp}; +/// use aya_ebpf::programs::TcContext; +/// use aya_log_ebpf::debug as aya_ebpf_debug; // Import the debug macro appropriately +/// use network_types::eth::EthHdr; +/// use network_types::ip::Ipv4Hdr; // Assuming IPv4 for simplicity +/// // Placeholder for UDP header length if not using a full UdpHdr type +/// const UDP_HDR_LEN: usize = 8; +/// use network_types::quic::{QuicHdr, QuicHeaderType, QUIC_MAX_CID_LEN, QuicHdrError}; +/// +/// fn handle_quic_packet(ctx: &TcContext) -> Result { +/// let data_start = ctx.data(); +/// let data_end = ctx.data_end(); +/// // Calculate offset to where QUIC header might start +/// // This assumes Eth + IPv4 + UDP. Adjust for IPv6 or other encapsulations. +/// let quic_offset = EthHdr::LEN + Ipv4Hdr::LEN + UDP_HDR_LEN; +/// let quic_base_ptr = unsafe { (data_start as *const u8).add(quic_offset) }; +/// // Ensure there's at least one byte for the QUIC header (first_byte) +/// if unsafe { quic_base_ptr.add(1) } > (data_end as *const u8) { +/// aya_ebpf_debug!(ctx, "QUIC packet too short for first_byte"); +/// return Err(()); +/// } +/// let first_byte = unsafe { ptr::read_volatile(quic_base_ptr) }; +/// let mut hdr_on_stack: QuicHdr; +/// let mut current_parse_offset: usize = 1; // Start parsing after the first_byte +/// if (first_byte & 0x80) != 0 { // Long Header +/// hdr_on_stack = QuicHdr::new(QuicHeaderType::QuicLong); +/// hdr_on_stack.set_first_byte(first_byte); // Set first_byte, includes type, PN len etc. +/// // --- Parse Version (4 bytes) --- +/// let version_offset = current_parse_offset; +/// if unsafe { quic_base_ptr.add(version_offset + 4) } > (data_end as *const u8) { +/// aya_ebpf_debug!(ctx, "QUIC Long: Packet too short for version"); +/// return Err(()); +/// } +/// let mut ver_bytes = [0u8; 4]; +/// unsafe { +/// // In real eBPF, use bpf_probe_read_kernel or ctx.read_at helper if available & safe +/// ptr::copy_nonoverlapping(quic_base_ptr.add(version_offset), ver_bytes.as_mut_ptr(), 4); +/// } +/// if hdr_on_stack.set_version(u32::from_be_bytes(ver_bytes)).is_err() { +/// // Should not happen if type is QuicLong, but good practice +/// aya_ebpf_debug!(ctx, "QUIC Long: Failed to set version internally"); return Err(()); +/// } +/// current_parse_offset += 4; +/// // --- Parse DCID Len (1 byte) & DCID --- +/// let dcil_offset = current_parse_offset; +/// if unsafe { quic_base_ptr.add(dcil_offset + 1) } > (data_end as *const u8) { +/// aya_ebpf_debug!(ctx, "QUIC Long: Packet too short for DCIL"); return Err(()); +/// } +/// let dcil = unsafe { ptr::read_volatile(quic_base_ptr.add(dcil_offset)) }; +/// current_parse_offset += 1; +/// if dcil as usize > QUIC_MAX_CID_LEN { +/// aya_ebpf_debug!(ctx, "QUIC Long: DCIL {} exceeds max {}", dcil, QUIC_MAX_CID_LEN); return Err(()); +/// } +/// if unsafe { quic_base_ptr.add(current_parse_offset + dcil as usize) } > (data_end as *const u8) { +/// aya_ebpf_debug!(ctx, "QUIC Long: Packet too short for DCID data (len {})", dcil); return Err(()); +/// } +/// let mut dcid_buf = [0u8; QUIC_MAX_CID_LEN]; // Max buffer +/// if dcil > 0 { +/// unsafe { +/// ptr::copy_nonoverlapping(quic_base_ptr.add(current_parse_offset), dcid_buf.as_mut_ptr(), dcil as usize); +/// } +/// } +/// hdr_on_stack.set_dc_id(&dcid_buf[..dcil as usize]); +/// current_parse_offset += dcil as usize; +/// let scil_offset = current_parse_offset; +/// if unsafe { quic_base_ptr.add(scil_offset + 1) } > (data_end as *const u8) { +/// aya_ebpf_debug!(ctx, "QUIC Long: Packet too short for SCIL"); return Err(()); +/// } +/// let scil = unsafe { ptr::read_volatile(quic_base_ptr.add(scil_offset)) }; +/// current_parse_offset += 1; +/// if scil as usize > QUIC_MAX_CID_LEN { +/// aya_ebpf_debug!(ctx, "QUIC Long: SCIL {} exceeds max {}", scil, QUIC_MAX_CID_LEN); return Err(()); +/// } +/// if unsafe { quic_base_ptr.add(current_parse_offset + scil as usize) } > (data_end as *const u8) { +/// aya_ebpf_debug!(ctx, "QUIC Long: Packet too short for SCID data (len {})", scil); return Err(()); +/// } +/// let mut scid_buf = [0u8; QUIC_MAX_CID_LEN]; // Max buffer +/// if scil > 0 { +/// unsafe { +/// ptr::copy_nonoverlapping(quic_base_ptr.add(current_parse_offset), scid_buf.as_mut_ptr(), scil as usize); +/// } +/// } +/// if hdr_on_stack.set_sc_id(&scid_buf[..scil as usize]).is_err() { +/// aya_ebpf_debug!(ctx, "QUIC Long: Failed to set SCID internally"); return Err(()); +/// } +/// current_parse_offset += scil as usize; +/// // Long header successfully parsed into hdr_on_stack +/// if let Ok(pkt_type) = hdr_on_stack.long_packet_type() { +/// aya_ebpf_debug!(ctx, "QUIC Long: Type={}, Ver={}, DCID_len={}, SCID_len={}", +/// pkt_type, hdr_on_stack.version().unwrap_or(0), dcil, scil); +/// } +/// } else { // Short Header +/// // For Short Headers, DCID length must be known from context (e.g., connection tracking via eBPF map) +/// // For this example, let's assume a fixed length or one fetched from a hypothetical map. +/// let known_dcid_len: u8 = 8; // Example: 8-byte DCID for short header connections +/// hdr_on_stack = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: known_dcid_len }); +/// hdr_on_stack.set_first_byte(first_byte); // Set first_byte, includes spin, key, PN len etc. +/// if known_dcid_len as usize > QUIC_MAX_CID_LEN { +/// aya_ebpf_debug!(ctx, "QUIC Short: Contextual DCID len {} exceeds max", known_dcid_len); return Err(()); +/// } +/// if unsafe { quic_base_ptr.add(current_parse_offset + known_dcid_len as usize) } > (data_end as *const u8) { +/// aya_ebpf_debug!(ctx, "QUIC Short: Packet too short for DCID data (len {})", known_dcid_len); return Err(()); +/// } +/// let mut dcid_buf = [0u8; QUIC_MAX_CID_LEN]; +/// if known_dcid_len > 0 { +/// unsafe { +/// ptr::copy_nonoverlapping(quic_base_ptr.add(current_parse_offset), dcid_buf.as_mut_ptr(), known_dcid_len as usize); +/// } +/// } +/// hdr_on_stack.set_dc_id(&dcid_buf[..known_dcid_len as usize]); +/// // Note: set_dc_id for short header also updates QuicHeaderType's dc_id_len field +/// // to match the actual data set, if it was shorter than `known_dcid_len` due to QUIC_MAX_CID_LEN. +/// current_parse_offset += known_dcid_len as usize; +/// // Short header successfully parsed into hdr_on_stack +/// aya_ebpf_debug!(ctx, "QUIC Short: DCID_len_ctx={}, Spin={}", +/// known_dcid_len, hdr_on_stack.short_spin_bit().unwrap_or(false) as u8); +/// if let Some(dcid_slice_preview) = hdr_on_stack.dc_id().get(..cmp::min(4, hdr_on_stack.dc_id().len())) { +/// // Log first few bytes of DCID for example +/// // aya_log_ebpf::debug! doesn't directly support byte slice formatting easily. +/// // For actual logging of slices, you might log byte by byte or a hex representation if needed. +/// aya_ebpf_debug!(ctx, "QUIC Short: DCID preview len {}", dcid_slice_preview.len()); +/// } +/// } +/// // Now `hdr_on_stack` is populated. You can use its methods to access fields. +/// // For example, to get the Packet Number length (common for both forms, but different bits): +/// let pn_len_result = if hdr_on_stack.is_long_header() { +/// hdr_on_stack.packet_number_length_long() +/// } else { +/// hdr_on_stack.short_packet_number_length() +/// }; +/// if let Ok(len) = pn_len_result { +/// aya_ebpf_debug!(ctx, "QUIC Packet Number actual length: {} bytes", len); +/// // Packet number itself would be after the CIDs (for long) or DCID (for short) +/// // and before the payload. Parsing it is beyond this header example. +/// // let _pn_offset = quic_offset + current_parse_offset; +/// } +/// // The actual packet number bytes follow the header fields parsed so far. +/// // Accessing them requires reading `len` bytes from `quic_base_ptr.add(current_parse_offset)`. +/// Ok(0) +/// } +/// // Important Note: The use of `ptr::copy_nonoverlapping` assumes direct memory access. +/// // In a real eBPF program, especially for TC (sk_buff context), you would typically use +/// // `ctx.load::(offset)` for fixed-size reads or `bpf_probe_read_kernel` / `bpf_probe_read_user` +/// // for reading arbitrary memory into a buffer, ensuring safety and verifier compliance. +/// // This example uses raw pointers for conceptual clarity on structure parsing. +/// // Boundary checks (`if ptr.add(len) > data_end`) are crucial. +/// ``` + +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub struct QuicHdr { + first_byte: u8, + inner: QuicHdrUn, + header_type: QuicHeaderType, // Logical field, not on wire. Crucial for interpretation. +} + +impl QuicHdr { + + pub const LEN: usize = core::mem::size_of::(); + + /// Minimum on-wire size of a QUIC Long Header (1 (flags/type) + 4 (version) + 1 (DCIL byte) + 1 (SCIL byte) = 7 bytes), + /// This excludes any actual CID data bytes. + pub const MIN_LONG_HDR_LEN_ON_WIRE: usize = QuicHdrLong::MIN_LEN_ON_WIRE; + + /// Minimum on-wire size of a QUIC Short Header (1 (flags/type) = 1 byte). + /// This excludes any actual DCID data bytes. + pub const MIN_SHORT_HDR_LEN_ON_WIRE: usize = QuicHdrShort::MIN_LEN_ON_WIRE; + + /// Creates a new `QuicHdr` initialized for the specified `header_type`. + /// The `first_byte` is partially initialized based on the header form (Long/Short) and Fixed Bit. + /// Other bits in `first_byte` (like Packet Type, PN length encoding, Spin Bit, Key Phase) + /// must be set separately by the caller using the provided setter methods. /// /// # Parameters - /// - `spin_bit` - The value of the Spin bit (0 or 1). - /// - `key_phase` - The value of the Key Phase bit (0 or 1). - /// - `pn_len_bits` - The encoded Packet Number Length (actual length - 1, value 0-3). + /// * `header_type`: The type of QUIC header to initialize, providing context for + /// Short Header DCID length. /// /// # Returns - /// The constructed `first_byte` for a Short Header. - pub fn new_short_header_first_byte(spin_bit: bool, key_phase: bool, pn_len_bits: u8) -> u8 { - let spin_val = if spin_bit { 1 } else { 0 }; - let key_phase_val = if key_phase { 1 } else { 0 }; - FIXED_BIT_MASK - | (spin_val << SHORT_SPIN_BIT_SHIFT) - | (key_phase_val << SHORT_KEY_PHASE_BIT_SHIFT) - | (pn_len_bits & PN_LENGTH_BITS_MASK) + /// A new `QuicHdr` instance. + pub fn new(header_type: QuicHeaderType) -> Self { + match header_type { + QuicHeaderType::QuicLong => { + let first_byte = HEADER_FORM_BIT | FIXED_BIT_MASK; + Self { + first_byte, + inner: QuicHdrUn { + long: QuicHdrLong::default(), + }, + header_type, + } + } + QuicHeaderType::QuicShort { dc_id_len } => { + // dc_id_len captured here + let first_byte = FIXED_BIT_MASK; // Set Fixed bit (Form bit is 0) + let mut short_data = QuicHdrShort::default(); + // Initialize the length of the dst CID within short_data if needed, + // though `set_dc_id` will manage this. + // The dc_id_len from header_type is the primary source of truth for effective length. + short_data.dst.len = cmp::min(dc_id_len, QUIC_MAX_CID_LEN as u8); + Self { + first_byte, + inner: QuicHdrUn { short: short_data }, + header_type, + } + } + } } - /// Returns the raw first byte of the header. + /// Gets the raw `first_byte` of the header. This byte contains several bit-packed fields. /// /// # Returns - /// The `first_byte` field. + /// The `u8` value of the first byte. #[inline] pub fn first_byte(&self) -> u8 { self.first_byte } - /// Sets the raw first byte of the header. + /// Sets the raw `first_byte` of the header. /// /// # Parameters - /// - `first_byte` - The new value for the first byte. + /// * `b`: The new `u8` value for the first byte. + /// + /// # Safety + /// Caller must ensure consistency of the `first_byte`'s Header Form bit + /// with the `self.header_type` discriminator. Use `set_header_type` for + /// safe structural changes if the header form bit is altered by this call. + /// It's generally safer to use specific setters like `set_long_packet_type`, etc. + /// after `QuicHdr::new()`, and then call this if you need to set the *entire* byte + /// from a pre-calculated value (e.g. from a packet). #[inline] - pub fn set_first_byte(&mut self, first_byte: u8) { - self.first_byte = first_byte; + pub fn set_first_byte(&mut self, b: u8) { + self.first_byte = b; + // Developer note: After calling this, ensure self.header_type is still valid. + // For example, if 'b' flips the HEADER_FORM_BIT, self.header_type should be updated + // via set_header_type() to match, which also reinitializes self.inner. } - /// Checks if the Header Form bit (bit 7 of `first_byte`) indicates a Long Header. + /// Checks if the Header Form bit (most significant bit of `first_byte`) indicates a Long Header. /// /// # Returns - /// `true` if it's a Long Header (bit 7 is 1), `false` otherwise (Short Header). + /// `true` if bit 7 is 1 (Long Header), `false` if 0 (Short Header). #[inline] pub fn is_long_header(&self) -> bool { (self.first_byte & HEADER_FORM_BIT) == HEADER_FORM_BIT } - /// Sets the Header Form bit (bit 7 of `first_byte`). + /// Gets the Fixed Bit (bit 6 of `first_byte`). + /// Per RFC 9000, this bit MUST be 1 for all QUIC v1 packets except Version Negotiation. /// - /// # Parameters - /// - `is_long` - If `true`, sets for Long Header (bit 7 = 1); if `false`, sets for Short Header (bit 7 = 0). + /// # Returns + /// The value of the Fixed Bit (0 or 1). #[inline] - pub fn set_header_form(&mut self, is_long: bool) { - if is_long { - self.first_byte |= HEADER_FORM_BIT; + pub fn fixed_bit(&self) -> u8 { + (self.first_byte & FIXED_BIT_MASK) >> 6 + } + + /// Gets the Long Packet Type (bits 5-4 of `first_byte`) if this is a Long Header. + /// Common types for QUIC v1 (RFC 9000) are: + /// * `0b00` (0): Initial + /// * `0b01` (1): 0-RTT + /// * `0b10` (2): Handshake + /// * `0b11` (3): Retry + /// + /// # Returns + /// `Ok(u8)` with the packet type (0-3) if Long Header, + /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. + #[inline] + pub fn long_packet_type(&self) -> Result { + if self.is_long_header() { + // Safety: is_long_header() ensures conditions for unchecked_long_packet_type are met. + Ok(unsafe { self.unchecked_long_packet_type() }) } else { - self.first_byte &= !HEADER_FORM_BIT; + Err(QuicHdrError::InvalidHeaderForm) } } - /// Gets the Fixed Bit (bit 6 of `first_byte`). + /// Gets the Reserved Bits (bits 3-2 of `first_byte`) if this is a Long Header. + /// For QUIC v1 Initial, 0-RTT, and Handshake packets, these bits MUST be 0. + /// For Retry packets, these bits are part of the "Retry Packet Type" or ODCIL. /// - /// In QUIC v1 (RFC 9000): - /// - For Long Headers: `1` for Initial, 0-RTT, Handshake, Retry. `0` for Version Negotiation. - /// - For Short Headers: Must be `1`. + /// # Returns + /// `Ok(u8)` with the reserved bits value (0-3) if Long Header, + /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. + #[inline] + pub fn reserved_bits_long(&self) -> Result { + if self.is_long_header() { + // Safety: is_long_header() ensures conditions for unchecked_reserved_bits_long are met. + Ok(unsafe { self.unchecked_reserved_bits_long() }) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Gets the encoded Packet Number Length (bits 1-0 of `first_byte`) if this is a Long Header + /// (for types that include a Packet Number: Initial, 0-RTT, Handshake). + /// The value is `actual_length_in_bytes - 1`. So, 0b00 means 1 byte, 0b11 means 4 bytes. /// /// # Returns - /// The value of the Fixed Bit (0 or 1). + /// `Ok(u8)` with the encoded length (0-3) if Long Header, + /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. + /// Note: This doesn't check if the specific Long Packet Type actually has a packet number. #[inline] - pub fn fixed_bit(&self) -> u8 { - (self.first_byte & FIXED_BIT_MASK) >> 6 + pub fn pn_length_bits_long(&self) -> Result { + if self.is_long_header() { + // Safety: is_long_header() ensures conditions for unchecked_pn_length_bits_long are met. + Ok(unsafe { self.unchecked_pn_length_bits_long() }) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Gets the actual Packet Number Length in bytes (1 to 4) if this is a Long Header + /// and the header type includes a packet number. + /// + /// # Returns + /// `Ok(usize)` with the actual length (1-4 bytes) if Long Header, + /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. + #[inline] + pub fn packet_number_length_long(&self) -> Result { + self.pn_length_bits_long().map(|bits| (bits + 1) as usize) + } + + /// Gets the Spin Bit (bit 5 of `first_byte`) if this is a Short Header. + /// + /// # Returns + /// `Ok(bool)` (`true` if bit is 1, `false` if 0) if Short Header, + /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. + #[inline] + pub fn short_spin_bit(&self) -> Result { + if !self.is_long_header() { + // Safety: !is_long_header() ensures conditions for unchecked_short_spin_bit are met. + Ok(unsafe { self.unchecked_short_spin_bit() }) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Gets the Reserved Bits (bits 4-3 of `first_byte`) if this is a Short Header. + /// These bits MUST be 0 in QUIC v1 unless an extension defines otherwise. + /// + /// # Returns + /// `Ok(u8)` with the reserved bits value (0-3) if Short Header, + /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. + #[inline] + pub fn short_reserved_bits(&self) -> Result { + if !self.is_long_header() { + // Safety: !is_long_header() ensures conditions for unchecked_short_reserved_bits are met. + Ok(unsafe { self.unchecked_short_reserved_bits() }) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Gets the Key Phase Bit (bit 2 of `first_byte`) if this is a Short Header. + /// + /// # Returns + /// `Ok(bool)` (`true` if bit is 1, `false` if 0) if Short Header, + /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. + #[inline] + pub fn short_key_phase(&self) -> Result { + if !self.is_long_header() { + // Safety: !is_long_header() ensures conditions for unchecked_short_key_phase are met. + Ok(unsafe { self.unchecked_short_key_phase() }) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Gets the encoded Packet Number Length (bits 1-0 of `first_byte`) if this is a Short Header. + /// The value is `actual_length_in_bytes - 1`. + /// + /// # Returns + /// `Ok(u8)` with the encoded length (0-3) if Short Header, + /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. + #[inline] + pub fn short_pn_length_bits(&self) -> Result { + if !self.is_long_header() { + // Safety: !is_long_header() ensures conditions for unchecked_short_pn_length_bits are met. + Ok(unsafe { self.unchecked_short_pn_length_bits() }) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Gets the actual Packet Number Length in bytes (1 to 4) if this is a Short Header. + /// + /// # Returns + /// `Ok(usize)` with the actual length (1-4 bytes) if Short Header, + /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. + #[inline] + pub fn short_packet_number_length(&self) -> Result { + self.short_pn_length_bits().map(|bits| (bits + 1) as usize) + } + + /// Gets the QUIC version in host byte order, if this is a Long Header. + /// + /// # Returns + /// `Ok(u32)` with the version if Long Header, + /// `Err(QuicHdrError::InvalidHeaderForm)` for Short Headers. + #[inline] + pub fn version(&self) -> Result { + if self.is_long_header() { + // Safety: is_long_header() ensures self.inner.long is the active and initialized variant. + Ok(unsafe { self.unchecked_version() }) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Gets the Destination Connection ID length as present on the wire for Long Headers. + /// This is the value of the DCIL byte. + /// + /// # Returns + /// `Ok(u8)` with the on-wire DCID length if Long Header, + /// `Err(QuicHdrError::InvalidHeaderForm)` for Short Headers (as DCID length is not on the wire for them). + #[inline] + pub fn dc_id_len_on_wire(&self) -> Result { + if self.is_long_header() { + // Safety: is_long_header() ensures self.inner.long is active. + Ok(unsafe { self.unchecked_dc_id_len_on_wire() }) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Gets the effective length of the Destination Connection ID. + /// For Long Headers, this is read from the Dst CID Len byte (via `self.inner.long.dst.len()`). + /// For Short Headers, this is the contextual length stored in `header_type.dc_id_len`. + /// + /// # Returns + /// The `u8` effective length of the DCID. + #[inline] + pub fn dc_id_effective_len(&self) -> u8 { + // Safety: self.header_type is always consistent with the active union variant. + unsafe { self.unchecked_dc_id_effective_len() } + } + + /// Gets a slice to the Destination Connection ID bytes. + /// The length of the slice is determined by `dc_id_effective_len()`. + /// + /// # Returns + /// A `&[u8]` slice of the DCID. + #[inline] + pub fn dc_id(&self) -> &[u8] { + // Safety: self.header_type is consistent, and dc_id_effective_len ensures bounds. + unsafe { self.unchecked_dc_id() } + } + + /// Gets the Source Connection ID length as present on the wire (Long Headers only). + /// This is the value of the SCIL byte. + /// + /// # Returns + /// `Ok(u8)` with the on-wire SCID length if Long Header, + /// `Err(QuicHdrError::InvalidHeaderForm)` for Short Headers. + #[inline] + pub fn sc_id_len_on_wire(&self) -> Result { + if self.is_long_header() { + // Safety: is_long_header() ensures self.inner.long is active. + Ok(unsafe { self.unchecked_sc_id_len_on_wire() }) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Gets a slice to the Source Connection ID bytes if this is a Long Header. + /// + /// # Returns + /// `Ok(&[u8])` slice of the SCID if Long Header, + /// `Err(QuicHdrError::InvalidHeaderForm)` for Short Headers. + #[inline] + pub fn sc_id(&self) -> Result<&[u8], QuicHdrError> { + if self.is_long_header() { + // Safety: is_long_header() ensures self.inner.long is active and its .src.as_slice() is valid. + Ok(unsafe { self.unchecked_sc_id() }) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Gets the current `QuicHeaderType` discriminator. + /// This provides context for interpreting the union and, for Short Headers, the Destination CID length. + /// + /// # Returns + /// The `QuicHeaderType` of this header instance. + #[inline] + pub fn header_type(&self) -> QuicHeaderType { + self.header_type + } + + /// Sets the Header Form bit (bit 7) in `first_byte`. + /// + /// # Parameters + /// * `is_long`: If `true`, sets for Long Header; `false` for Short. + /// + /// # Safety + /// This method only changes the bit in `first_byte`. + /// For a safe structural change of the header form (Long vs. Short) which also + /// reinitialized the `inner` union data and updates `header_type`, use `set_header_type()`. + /// If this method is used to change the header form bit, the caller is responsible for + /// ensuring `header_type` and `inner` data are also updated to maintain consistency. + #[inline] + pub fn set_header_form_bit(&mut self, is_long: bool) { + // Safety: Direct bit manipulation; caller must ensure overall consistency. + unsafe { self.unchecked_set_header_form_bit(is_long) }; } /// Sets the Fixed Bit (bit 6 of `first_byte`). /// /// # Parameters - /// - `val` - The new value for the Fixed Bit (0 or 1). Input is masked to 1 bit. + /// * `val`: The new value for the Fixed Bit (0 or 1). Input is masked to 1 bit. #[inline] pub fn set_fixed_bit(&mut self, val: u8) { - self.first_byte = (self.first_byte & !FIXED_BIT_MASK) | ((val & 0x01) << 6); + // Safety: Direct bitwise operation on self.first_byte. + unsafe { self.unchecked_set_fixed_bit(val) }; } - /// Gets the Long Packet Type (bits 5-4 of `first_byte`). Assumes Long Header. - /// Common QUIC v1 types (RFC 9000): 00 (Initial), 01 (0-RTT), 10 (Handshake), 11 (Retry). + /// Sets the Long Packet Type (bits 5-4 of `first_byte`) if this is a Long Header. + /// + /// # Parameters + /// * `lptype`: The Long Packet Type (0-3). Input is masked to 2 bits. /// /// # Returns - /// The Long Packet Type value (0-3). Only valid if `is_long_header()` is `true` and `fixed_bit()` is `1`. + /// `Ok(())` if the operation is applicable and successful, + /// `Err(QuicHdrError::InvalidHeaderForm)` if called on a Short Header. #[inline] - pub fn long_packet_type(&self) -> u8 { - (self.first_byte & LONG_PACKET_TYPE_MASK) >> LONG_PACKET_TYPE_SHIFT + pub fn set_long_packet_type(&mut self, lptype: u8) -> Result<(), QuicHdrError> { + if self.is_long_header() { + // Safety: `is_long_header()` check ensures conditions for `unchecked_set_long_packet_type` are met. + unsafe { self.unchecked_set_long_packet_type(lptype) }; + Ok(()) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } } - /// Sets the Long Packet Type (bits 5-4 of `first_byte`). Assumes Long Header. + /// Sets the Reserved Bits (bits 3-2 of `first_byte`) if this is a Long Header. /// /// # Parameters - /// - `lptype` - The Long Packet Type (0-3). Input is masked to 2 bits. + /// * `val`: The Reserved Bits value (0-3). Input is masked to 2 bits. + /// For QUIC v1 Initial, 0-RTT, Handshake, `val` MUST typically be 0. + /// + /// # Returns + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. #[inline] - pub fn set_long_packet_type(&mut self, lptype: u8) { - self.first_byte = (self.first_byte & !LONG_PACKET_TYPE_MASK) - | ((lptype << LONG_PACKET_TYPE_SHIFT) & LONG_PACKET_TYPE_MASK); + pub fn set_reserved_bits_long(&mut self, val: u8) -> Result<(), QuicHdrError> { + if self.is_long_header() { + // Safety: Condition met. + unsafe { self.unchecked_set_reserved_bits_long(val) }; + Ok(()) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } } - /// Gets the Reserved Bits (bits 3-2 of `first_byte`) for common Long Headers. - /// Must be 0 for Initial, 0-RTT, Handshake packets in QUIC v1. + /// Sets the encoded Packet Number Length (bits 1-0 of `first_byte`) if this is a Long Header. + /// + /// # Parameters + /// * `val`: Encoded Packet Number Length (`actual_length_in_bytes - 1`, range 0-3). Masked to 2 bits. /// /// # Returns - /// The Reserved Bits value (0-3). Only valid for certain Long Header types. + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. #[inline] - pub fn reserved_bits_long(&self) -> u8 { - (self.first_byte & RESERVED_BITS_LONG_MASK) >> RESERVED_BITS_LONG_SHIFT + pub fn set_pn_length_bits_long(&mut self, val: u8) -> Result<(), QuicHdrError> { + if self.is_long_header() { + // Safety: Condition met. + unsafe { self.unchecked_set_pn_length_bits_long(val) }; + Ok(()) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } } - /// Sets the Reserved Bits (bits 3-2 of `first_byte`) for common Long Headers. - /// `val` MUST be 0 for Initial, 0-RTT, Handshake packets in QUIC v1. + /// Sets the actual Packet Number Length (1-4 bytes) if this is a Long Header. + /// Clamps input `len_bytes` to the valid 1-4 range if out of bounds. /// /// # Parameters - /// - `val` - The Reserved Bits value (0-3). Input is masked to 2 bits. + /// * `len_bytes`: Actual PN length in bytes (1-4). + /// + /// # Returns + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + pub fn set_packet_number_length_long(&mut self, len_bytes: usize) -> Result<(), QuicHdrError> { + if self.is_long_header() { + // Safety: Condition met. + unsafe { self.unchecked_set_packet_number_length_long(len_bytes) }; + Ok(()) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Sets the Spin Bit (bit 5 of `first_byte`) if this is a Short Header. + /// + /// # Parameters + /// * `spin`: Value for the Spin Bit (`true` for 1, `false` for 0). + /// + /// # Returns + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Short Header. #[inline] - pub fn set_reserved_bits_long(&mut self, val: u8) { - self.first_byte = (self.first_byte & !RESERVED_BITS_LONG_MASK) - | ((val << RESERVED_BITS_LONG_SHIFT) & RESERVED_BITS_LONG_MASK); + pub fn set_short_spin_bit(&mut self, spin: bool) -> Result<(), QuicHdrError> { + if !self.is_long_header() { + // Safety: Condition met. + unsafe { self.unchecked_set_short_spin_bit(spin) }; + Ok(()) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } } - /// Gets the Packet Number Length bits (bits 1-0 of `first_byte`) for common Long Headers. - /// Encoded length (actual length - 1). + /// Sets the Reserved Bits (bits 4-3 of `first_byte`) if this is a Short Header. + /// + /// # Parameters + /// * `val`: The Reserved Bits value (0-3). Input is masked to 2 bits. + /// For QUIC v1, `val` MUST typically be 0. /// /// # Returns - /// The encoded Packet Number Length value (0-3). Valid for certain Long Header types. + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Short Header. #[inline] - pub fn pn_length_bits_long(&self) -> u8 { - self.first_byte & PN_LENGTH_BITS_MASK + pub fn set_short_reserved_bits(&mut self, val: u8) -> Result<(), QuicHdrError> { + if !self.is_long_header() { + // Safety: Condition met. + unsafe { self.unchecked_set_short_reserved_bits(val) }; + Ok(()) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Sets the Key Phase Bit (bit 2 of `first_byte`) if this is a Short Header. + /// + /// # Parameters + /// * `key_phase`: Value for the Key Phase Bit (`true` for 1, `false` for 0). + /// + /// # Returns + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Short Header. + #[inline] + pub fn set_short_key_phase(&mut self, key_phase: bool) -> Result<(), QuicHdrError> { + if !self.is_long_header() { + // Safety: Condition met. + unsafe { self.unchecked_set_short_key_phase(key_phase) }; + Ok(()) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } } - /// Sets the Packet Number Length bits (bits 1-0 of `first_byte`) for common Long Headers. + /// Sets the encoded Packet Number Length (bits 1-0 of `first_byte`) if this is a Short Header. /// /// # Parameters - /// - `val` - Encoded 2-bit value (0-3, for actual lengths 1-4 bytes). Masked to 2 bits. + /// * `val`: Encoded Packet Number Length (`actual_length_in_bytes - 1`, range 0-3). Masked to 2 bits. + /// + /// # Returns + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Short Header. #[inline] - pub fn set_pn_length_bits_long(&mut self, val: u8) { - self.first_byte = (self.first_byte & !PN_LENGTH_BITS_MASK) | (val & PN_LENGTH_BITS_MASK); + pub fn set_short_pn_length_bits(&mut self, val: u8) -> Result<(), QuicHdrError> { + if !self.is_long_header() { + // Safety: Condition met. + unsafe { self.unchecked_set_short_pn_length_bits(val) }; + Ok(()) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Sets the actual Packet Number Length (1-4 bytes) if this is a Short Header. + /// Clamps input `len_bytes` to the valid 1-4 range if out of bounds. + /// + /// # Parameters + /// * `len_bytes`: Actual PN length in bytes (1-4). + /// + /// # Returns + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Short Header. + pub fn set_short_packet_number_length(&mut self, len_bytes: usize) -> Result<(), QuicHdrError> { + if !self.is_long_header() { + // Safety: Condition met. + unsafe { self.unchecked_set_short_packet_number_length(len_bytes) }; + Ok(()) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } } - /// Gets actual Packet Number Length (bytes) for common Long Headers. (`pn_length_bits_long() + 1`). + /// Sets the QUIC version (host byte order), if this is a Long Header. + /// Does nothing if it's a Short Header. + /// + /// # Parameters + /// * `v`: The QUIC version in host byte order. /// /// # Returns - /// Actual Packet Number Length (1 to 4 bytes). + /// `Ok(())` if successful (i.e., was a Long Header), + /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. #[inline] - pub fn packet_number_length_long(&self) -> usize { - (self.pn_length_bits_long() + 1) as usize + pub fn set_version(&mut self, v: u32) -> Result<(), QuicHdrError> { + if self.is_long_header() { + // Safety: Condition met, self.inner.long is active. + unsafe { self.unchecked_set_version(v) }; + Ok(()) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Sets the effective length of the Destination Connection ID. + /// + /// # Parameters + /// * `new_len`: The new desired effective length for the DCID. Values are clamped + /// to `QUIC_MAX_CID_LEN`. + /// + /// For Long Headers, this updates the internal `len` field of `self.inner.long.dst`, + /// which corresponds to the on-wire DCIL byte. The actual DCID bytes are *not* zeroed + /// or changed by this call alone if the length shrinks; use `set_dc_id` to change bytes. + /// For Short Headers, this updates the contextual `dc_id_len` in `self.header_type` + /// and also updates the internal `len` of `self.inner.short.dst` for consistency. + pub fn set_dc_id_effective_len(&mut self, new_len: u8) { + // Safety: unchecked method correctly handles union based on self.header_type. + unsafe { self.unchecked_set_dc_id_effective_len(new_len) }; } - /// Sets Packet Number Length for common Long Headers, using actual length (1-4 bytes). - /// Clamped if `len` is out of range. + /// Sets the Destination Connection ID from a slice. + /// This updates the internal CID bytes and its length. + /// For Long Headers, `self.inner.long.dst.len` (on-wire DCIL) is updated to `data.len()`. + /// For Short Headers, `self.header_type.dc_id_len` (contextual length) and + /// `self.inner.short.dst.len` (internal tracking) are updated to `data.len()`. /// /// # Parameters - /// - `len` - Actual length in bytes (1-4). + /// * `data`: A byte slice containing the new DCID. Length is clamped to `QUIC_MAX_CID_LEN`. + pub fn set_dc_id(&mut self, data: &[u8]) { + // Safety: unchecked method correctly handles union based on self.header_type. + unsafe { self.unchecked_set_dc_id(data) }; + } + + /// Sets the Source Connection ID length (Long Headers only). + /// This updates the internal `len` field of `self.inner.long.src`, which + /// corresponds to the on-wire SCIL byte. The SCID bytes are *not* zeroed or changed. + /// + /// # Parameters + /// * `len`: The new desired length for the SCID. Values are clamped to `QUIC_MAX_CID_LEN`. + /// + /// # Returns + /// `Ok(())` if successful (i.e., was a Long Header), + /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. + pub fn set_sc_id_len_on_wire(&mut self, len: u8) -> Result<(), QuicHdrError> { + if self.is_long_header() { + // Safety: Condition met, self.inner.long is active. + unsafe { self.unchecked_set_sc_id_len_on_wire(len) }; + Ok(()) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Sets the Source Connection ID from a slice (Long Headers only). + /// This updates the internal SCID bytes and its length (`self.inner.long.src.len`). + /// + /// # Parameters + /// * `data`: A byte slice containing the new SCID. Length clamped to `QUIC_MAX_CID_LEN`. + /// + /// # Returns + /// `Ok(())` if successful (i.e., was a Long Header), + /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. + pub fn set_sc_id(&mut self, data: &[u8]) -> Result<(), QuicHdrError> { + if self.is_long_header() { + // Safety: Condition met, self.inner.long is active. + unsafe { self.unchecked_set_sc_id(data) }; + Ok(()) + } else { + Err(QuicHdrError::InvalidHeaderForm) + } + } + + /// Sets the `QuicHeaderType` and reinitialized the inner `QuicHdrUn` union + /// to its default state for the new type if the fundamental header form (Long/Short) changes. + /// This is to prevent misinterpreting stale bytes from the previous union variant. + /// The `first_byte`'s Header Form bit and Fixed Bit are updated to match the new type. + /// Other specific bits in `first_byte` (like packet type, PN length) are *not* reset by this + /// method and should be configured by the caller as needed for the new type. + /// + /// # Parameters + /// * `new_type`: The new `QuicHeaderType` to set. + /// + /// # Safety + /// This method correctly manages union transitions by re-initializing the union. + /// Any CID data or other header fields must be repopulated by the caller if they need to be + /// preserved or set for the new header type. + pub fn set_header_type(&mut self, new_type: QuicHeaderType) { + let other_bits = self.first_byte & !(HEADER_FORM_BIT | FIXED_BIT_MASK); + // Safety: This method correctly manages union transitions and first_byte consistency. + unsafe { self.unchecked_set_header_type(new_type) }; // This sets self.header_type and Form/Fixed bits in first_byte. + // Re-apply the other bits that were not part of Form/Fixed. + self.first_byte |= other_bits; + } +} + +// Unsafe (unchecked) methods +impl QuicHdr { + /// Gets the Long Packet Type from `first_byte` without checking a header form. + /// + /// # Returns + /// The Long Packet Type value (0-3). + /// + /// # Safety + /// Caller must ensure `self.is_long_header()` is true. #[inline] - pub fn set_packet_number_length_long(&mut self, len: usize) { - let encoded_val = match len { + pub unsafe fn unchecked_long_packet_type(&self) -> u8 { + (self.first_byte & LONG_PACKET_TYPE_MASK) >> LONG_PACKET_TYPE_SHIFT + } + + /// Sets the Long Packet Type in `first_byte` without checking header form. + /// + /// # Parameters + /// * `lptype`: The Long Packet Type (0-3). + /// + /// # Safety + /// Caller must ensure `self.is_long_header()` is true. + #[inline] + pub unsafe fn unchecked_set_long_packet_type(&mut self, lptype: u8) { + self.first_byte = (self.first_byte & !LONG_PACKET_TYPE_MASK) + | ((lptype & 0x03) << LONG_PACKET_TYPE_SHIFT); + } + + /// Gets the Reserved Bits (Long Header) from `first_byte` without checking header form. + /// + /// # Returns + /// The Reserved Bits value (0-3). + /// + /// # Safety + /// Caller must ensure `self.is_long_header()` is true. + #[inline] + pub unsafe fn unchecked_reserved_bits_long(&self) -> u8 { + (self.first_byte & RESERVED_BITS_LONG_MASK) >> RESERVED_BITS_LONG_SHIFT + } + + /// Sets the Reserved Bits (Long Header) in `first_byte` without checking header form. + /// + /// # Parameters + /// * `val`: The Reserved Bits value (0-3). + /// + /// # Safety + /// Caller must ensure `self.is_long_header()` is true. + #[inline] + pub unsafe fn unchecked_set_reserved_bits_long(&mut self, val: u8) { + self.first_byte = (self.first_byte & !RESERVED_BITS_LONG_MASK) + | ((val & 0x03) << RESERVED_BITS_LONG_SHIFT); + } + + /// Gets the encoded Packet Number Length (Long Header) from `first_byte` without checking header form. + /// + /// # Returns + /// The encoded PN Length (0-3). + /// + /// # Safety + /// Caller must ensure `self.is_long_header()` is true and the packet type includes a PN. + #[inline] + pub unsafe fn unchecked_pn_length_bits_long(&self) -> u8 { + self.first_byte & PN_LENGTH_BITS_MASK + } + + /// Sets the encoded Packet Number Length (Long Header) in `first_byte` without checking header form. + /// + /// # Parameters + /// * `val`: Encoded PN Length (0-3). + /// + /// # Safety + /// Caller must ensure `self.is_long_header()` is true. + #[inline] + pub unsafe fn unchecked_set_pn_length_bits_long(&mut self, val: u8) { + self.first_byte = (self.first_byte & !PN_LENGTH_BITS_MASK) | (val & PN_LENGTH_BITS_MASK); + } + + /// Sets the Packet Number Length (Long Header) from actual length without checking header form. + /// + /// # Parameters + /// * `len_bytes`: Actual PN length (1-4 bytes). Values outside this range are clamped to 1. + /// + /// # Safety + /// Caller must ensure `self.is_long_header()` is true. + pub unsafe fn unchecked_set_packet_number_length_long(&mut self, len_bytes: usize) { + let encoded_val = match len_bytes { 1 => 0b00, 2 => 0b01, 3 => 0b10, 4 => 0b11, - _ if len < 1 => 0b00, - _ => 0b11, + _ => 0b00, // Default to 1-byte PN length if input is invalid }; - self.set_pn_length_bits_long(encoded_val); + self.unchecked_set_pn_length_bits_long(encoded_val); } - /// Gets the Spin Bit (bit 5 of `first_byte`). Assumes Short Header. + /// Gets the Spin Bit (Short Header) from `first_byte` without checking header form. /// /// # Returns - /// `true` if Spin Bit is 1, `false` if 0. Only valid if `!is_long_header()`. + /// `true` if Spin Bit is 1, `false` if 0. + /// + /// # Safety + /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub fn short_spin_bit(&self) -> bool { + pub unsafe fn unchecked_short_spin_bit(&self) -> bool { (self.first_byte & SHORT_SPIN_BIT_MASK) != 0 } - /// Sets the Spin Bit (bit 5 of `first_byte`). Assumes Short Header. + /// Sets the Spin Bit (Short Header) in `first_byte` without checking header form. /// /// # Parameters - /// - `spin` - Value for the Spin Bit (`true` for 1, `false` for 0). + /// * `spin`: Value for the Spin Bit. + /// + /// # Safety + /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub fn set_short_spin_bit(&mut self, spin: bool) { + pub unsafe fn unchecked_set_short_spin_bit(&mut self, spin: bool) { if spin { self.first_byte |= SHORT_SPIN_BIT_MASK; } else { @@ -260,41 +1520,52 @@ impl QuicHdr { } } - /// Gets the Reserved Bits (bits 4-3 of `first_byte`). Assumes Short Header. - /// These bits MUST be 0 in QUIC v1. + /// Gets the Reserved Bits (Short Header) from `first_byte` without checking header form. /// /// # Returns - /// The value of the Reserved Bits (0-3). Only valid if `!is_long_header()`. + /// The Reserved Bits value (0-3). + /// + /// # Safety + /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub fn short_reserved_bits(&self) -> u8 { + pub unsafe fn unchecked_short_reserved_bits(&self) -> u8 { (self.first_byte & SHORT_RESERVED_BITS_MASK) >> SHORT_RESERVED_BITS_SHIFT } - /// Sets the Reserved Bits (bits 4-3 of `first_byte`). Assumes Short Header. - /// These bits MUST be set to 0 (0b00) in QUIC v1. This method enforces this. + /// Sets the Reserved Bits (Short Header) in `first_byte` without checking header form. /// /// # Parameters - /// - `reserved` - The value for the Reserved Bits. If not 0, they will be set to 0. + /// * `val`: The Reserved Bits value (0-3). + /// + /// # Safety + /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub fn set_short_reserved_bits(&mut self, _reserved: u8) { - self.first_byte &= !SHORT_RESERVED_BITS_MASK; + pub unsafe fn unchecked_set_short_reserved_bits(&mut self, val: u8) { + self.first_byte = (self.first_byte & !SHORT_RESERVED_BITS_MASK) + | ((val & 0x03) << SHORT_RESERVED_BITS_SHIFT); } - /// Gets the Key Phase Bit (bit 2 of `first_byte`). Assumes Short Header. + /// Gets the Key Phase Bit (Short Header) from `first_byte` without checking header form. /// /// # Returns - /// `true` if Key Phase Bit is 1, `false` if 0. Only valid if `!is_long_header()`. + /// `true` if Key Phase Bit is 1, `false` if 0. + /// + /// # Safety + /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub fn short_key_phase(&self) -> bool { + pub unsafe fn unchecked_short_key_phase(&self) -> bool { (self.first_byte & SHORT_KEY_PHASE_BIT_MASK) != 0 } - /// Sets the Key Phase Bit (bit 2 of `first_byte`). Assumes Short Header. + /// Sets the Key Phase Bit (Short Header) in `first_byte` without checking header form. /// /// # Parameters - /// - `key_phase` - Value for the Key Phase Bit (`true` for 1, `false` for 0). + /// * `key_phase`: Value for the Key Phase Bit. + /// + /// # Safety + /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub fn set_short_key_phase(&mut self, key_phase: bool) { + pub unsafe fn unchecked_set_short_key_phase(&mut self, key_phase: bool) { if key_phase { self.first_byte |= SHORT_KEY_PHASE_BIT_MASK; } else { @@ -302,315 +1573,978 @@ impl QuicHdr { } } - /// Gets the Packet Number Length bits (bits 1-0 of `first_byte`). Assumes Short Header. - /// Encoded length (actual length - 1). + /// Gets the encoded Packet Number Length (Short Header) from `first_byte` without checking header form. /// /// # Returns - /// The encoded Packet Number Length value (0-3). Only valid if `!is_long_header()`. + /// The encoded PN Length (0-3). + /// + /// # Safety + /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub fn short_pn_length_bits(&self) -> u8 { + pub unsafe fn unchecked_short_pn_length_bits(&self) -> u8 { self.first_byte & PN_LENGTH_BITS_MASK } - /// Sets the Packet Number Length bits (bits 1-0 of `first_byte`). Assumes Short Header. + /// Sets the encoded Packet Number Length (Short Header) in `first_byte` without checking header form. /// /// # Parameters - /// - `val` - Encoded 2-bit value (0-3, for actual lengths 1-4 bytes). Masked to 2 bits. - #[inline] - pub fn set_short_pn_length_bits(&mut self, val: u8) { - self.first_byte = (self.first_byte & !PN_LENGTH_BITS_MASK) | (val & PN_LENGTH_BITS_MASK); - } - - /// Gets actual Packet Number Length (bytes) for Short Headers. (`short_pn_length_bits() + 1`). + /// * `val`: Encoded PN Length (0-3). /// - /// # Returns - /// Actual Packet Number Length (1 to 4 bytes). + /// # Safety + /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub fn short_packet_number_length(&self) -> usize { - (self.short_pn_length_bits() + 1) as usize + pub unsafe fn unchecked_set_short_pn_length_bits(&mut self, val: u8) { + self.first_byte = (self.first_byte & !PN_LENGTH_BITS_MASK) | (val & PN_LENGTH_BITS_MASK); } - /// Sets Packet Number Length for Short Headers, using actual length (1-4 bytes). - /// Clamped if `len` is out of range. + /// Sets the Packet Number Length (Short Header) from actual length without checking header form. /// /// # Parameters - /// - `len` - Actual length in bytes (1-4). - #[inline] - pub fn set_short_packet_number_length(&mut self, len: usize) { - let encoded_val = match len { + /// * `len_bytes`: Actual PN length (1-4 bytes). Values outside this range are clamped to 1. + /// + /// # Safety + /// Caller must ensure `!self.is_long_header()` is true. + pub unsafe fn unchecked_set_short_packet_number_length(&mut self, len_bytes: usize) { + let encoded_val = match len_bytes { 1 => 0b00, 2 => 0b01, 3 => 0b10, 4 => 0b11, - _ if len < 1 => 0b00, - _ => 0b11, + _ => 0b00, // Default to 1-byte PN length }; - self.set_short_pn_length_bits(encoded_val); + self.unchecked_set_short_pn_length_bits(encoded_val); } - /// Returns the QUIC version from the header (host byte order). Long Headers only. + /// Gets the QUIC version without checking header form. /// /// # Returns - /// The QUIC version. Panics or returns garbage if called on a Short Header's `QuicHdr`. + /// The QUIC version (host byte order). + /// + /// # Safety + /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. #[inline] - pub fn version(&self) -> u32 { - u32::from_be_bytes(self.version) + pub unsafe fn unchecked_version(&self) -> u32 { + self.inner.long.version() } - /// Sets the QUIC version in the header. `version` should be host byte order. Long Headers only. + /// Sets the QUIC version without checking header form. /// /// # Parameters - /// - `version` - The QUIC version (host byte order). + /// * `v`: The QUIC version (host byte order). + /// + /// # Safety + /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. + #[inline] + pub unsafe fn unchecked_set_version(&mut self, v: u32) { + self.inner.long.set_version(v); + } + + /// Gets the on-wire DCID length (Long Header) without checking header form. + /// + /// # Returns + /// The on-wire DCID length from `self.inner.long.dst.len`. + /// + /// # Safety + /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. + #[inline] + pub unsafe fn unchecked_dc_id_len_on_wire(&self) -> u8 { + self.inner.long.dst.len() + } + + /// Gets the effective DCID length based on `header_type` without checking union validity. + /// + /// # Returns + /// The effective DCID length. + /// + /// # Safety + /// Caller must ensure `self.header_type` is consistent with the active union variant + /// and that the corresponding variant's CID `len` field or `dc_id_len` context is correctly set. #[inline] - pub fn set_version(&mut self, version: u32) { - self.version = version.to_be_bytes(); + pub unsafe fn unchecked_dc_id_effective_len(&self) -> u8 { + match self.header_type { + QuicHeaderType::QuicLong => self.inner.long.dst.len(), + QuicHeaderType::QuicShort { dc_id_len } => dc_id_len, + } + } + + /// Sets the effective DCID length without checking union validity. + /// + /// # Parameters + /// * `new_len`: The new effective DCID length. Clamped to `QUIC_MAX_CID_LEN`. + /// + /// # Safety + /// Caller must ensure `self.header_type` is consistent with the active union variant. + /// This updates lengths in both `header_type` (for Short) and the CID struct itself. + pub unsafe fn unchecked_set_dc_id_effective_len(&mut self, new_len: u8) { + let validated_len = cmp::min(new_len, QUIC_MAX_CID_LEN as u8); + match &mut self.header_type { + QuicHeaderType::QuicLong => { + self.inner.long.dst.len = validated_len; + } + QuicHeaderType::QuicShort { + dc_id_len: current_dc_len, + } => { + *current_dc_len = validated_len; + self.inner.short.dst.len = validated_len; // Keep short.dst.len consistent + } + } } - /// Returns Destination Connection ID Length. For Long Headers, this is from the header. - /// For Short Headers, this field in the struct is not from the wire's first byte. + /// Gets the DCID slice based on `header_type` without checking union validity. /// /// # Returns - /// The DCID length. + /// A slice to the DCID bytes. + /// + /// # Safety + /// Caller must ensure `self.header_type` is consistent with the active union variant and + /// `unchecked_dc_id_effective_len()` returns a valid length for the active variant's buffer. #[inline] - pub fn dc_id_len(&self) -> u8 { - self.dc_id_len + pub unsafe fn unchecked_dc_id(&self) -> &[u8] { + let len = self.unchecked_dc_id_effective_len() as usize; + match self.header_type { + // Use .bytes[] directly with computed length to avoid as_slice() using its own internal len + // that might not match dc_id_len from QuicHeaderType::QuicShort context. + QuicHeaderType::QuicLong => &self.inner.long.dst.bytes[..len], + QuicHeaderType::QuicShort { .. } => &self.inner.short.dst.bytes[..len], + } } - /// Sets Destination Connection ID Length. + /// Sets the DCID without checking union validity. /// /// # Parameters - /// - `len` - The new DCID length. + /// * `data`: Slice containing the new DCID. Length is clamped by `set()`. + /// + /// # Safety + /// Caller must ensure `self.header_type` is consistent with the active union variant. + /// This updates lengths in `header_type` (for Short) and the CID struct itself via `set()`. + pub unsafe fn unchecked_set_dc_id(&mut self, data: &[u8]) { + match &mut self.header_type { + QuicHeaderType::QuicLong => { + self.inner.long.dst.set(data); + } + QuicHeaderType::QuicShort { + dc_id_len: current_dc_len, + } => { + self.inner.short.dst.set(data); + *current_dc_len = self.inner.short.dst.len(); + } + } + } + + /// Gets the on-wire SCID length (Long Header) without checking header form. + /// + /// # Returns + /// The on-wire SCID length from `self.inner.long.src.len`. + /// + /// # Safety + /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. #[inline] - pub fn set_dc_id_len(&mut self, len: u8) { - self.dc_id_len = len; + pub unsafe fn unchecked_sc_id_len_on_wire(&self) -> u8 { + self.inner.long.src.len() + } + + /// Sets the on-wire SCID length (Long Header) without checking header form. CID bytes are not changed. + /// + /// # Parameters + /// * `len`: The new SCID length. Clamped to `QUIC_MAX_CID_LEN`. + /// + /// # Safety + /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. + pub unsafe fn unchecked_set_sc_id_len_on_wire(&mut self, len: u8) { + self.inner.long.src.len = cmp::min(len, QUIC_MAX_CID_LEN as u8); } - /// Returns Source Connection ID Length. Long Headers only. + /// Gets a slice to the SCID (Long Header) without checking header form. /// /// # Returns - /// The SCID length. + /// A slice to the SCID bytes. + /// + /// # Safety + /// Caller must ensure `self.header_type` is `QuicLong`, `self.inner.long` is initialized, + /// and `self.inner.long.src.len()` is a valid length for `self.inner.long.src.bytes`. #[inline] - pub fn sc_id_len(&self) -> u8 { - self.sc_id_len + pub unsafe fn unchecked_sc_id(&self) -> &[u8] { + self.inner.long.src.as_slice() + } + + /// Sets the SCID (Long Header) without checking header form. + /// + /// # Parameters + /// * `data`: Slice containing the new SCID. Length clamped by `set()`. + /// + /// # Safety + /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. + pub unsafe fn unchecked_set_sc_id(&mut self, data: &[u8]) { + self.inner.long.src.set(data); } - /// Sets Source Connection ID Length. Long Headers only. + /// Sets the `QuicHeaderType` and manages union transition, without boundary checks for CIDs. + /// The `first_byte`'s Header Form and Fixed Bits are updated according to the `new_type`. + /// Other bits in `first_byte` are *not* touched by this specific unsafe method. /// /// # Parameters - /// - `len` - The new SCID length. + /// * `new_type`: The new `QuicHeaderType`. + /// + /// # Safety + /// This method correctly handles the re-initialization of the `inner` union + /// when the fundamental header form (Long/Short) changes, preventing misinterpretation + /// of stale bytes. + /// Caller must repopulate CID data if it needs to be preserved across such a type change. + /// Caller should ensure other bits in `first_byte` are appropriate for `new_type`. + pub unsafe fn unchecked_set_header_type(&mut self, new_type: QuicHeaderType) { + let current_is_long = self.is_long_header(); // Based on current first_byte + let new_is_long = match new_type { + QuicHeaderType::QuicLong => true, + QuicHeaderType::QuicShort { .. } => false, + }; + + if current_is_long != new_is_long { + // Form is changing, reinitialize union and update first_byte's Form bit + if new_is_long { + self.inner = QuicHdrUn { + long: QuicHdrLong::default(), + }; + self.first_byte = (self.first_byte | HEADER_FORM_BIT) | FIXED_BIT_MASK; + // Long + Fixed + } else { + let dc_id_len_for_short = if let QuicHeaderType::QuicShort { dc_id_len } = new_type + { + dc_id_len + } else { + 0 + }; + let mut short_data = QuicHdrShort::default(); + short_data.dst.len = cmp::min(dc_id_len_for_short, QUIC_MAX_CID_LEN as u8); + self.inner = QuicHdrUn { short: short_data }; + self.first_byte = (self.first_byte & !HEADER_FORM_BIT) | FIXED_BIT_MASK; + // Short + Fixed + } + } else { + // Form is not changing, but QuicShort's dc_id_len might be. + // Ensure fixed bit is correct for the form. + if new_is_long { + // And current_is_long is also true + self.first_byte |= HEADER_FORM_BIT | FIXED_BIT_MASK; + } else { + self.first_byte = (self.first_byte & !HEADER_FORM_BIT) | FIXED_BIT_MASK; + // If type is QuicShort, update inner.short.dst.len if dc_id_len changes + if let QuicHeaderType::QuicShort { dc_id_len } = new_type { + self.inner.short.dst.len = cmp::min(dc_id_len, QUIC_MAX_CID_LEN as u8); + } + } + } + self.header_type = new_type; // Set the new logical type + } + + /// Sets the Header Form bit in `first_byte` without performing structural changes. + /// + /// # Parameters + /// * `is_long`: `true` for Long Header form, `false` for Short. + /// + /// # Safety + /// This directly modifies the `first_byte`. Caller must ensure that `header_type` and + /// the active `inner` union variant are consistent with this change. Prefer using + /// the safe `set_header_type` for structural changes. #[inline] - pub fn set_sc_id_len(&mut self, len: u8) { - self.sc_id_len = len; + pub unsafe fn unchecked_set_header_form_bit(&mut self, is_long: bool) { + if is_long { + self.first_byte |= HEADER_FORM_BIT; + } else { + self.first_byte &= !HEADER_FORM_BIT; + } + } + + /// Sets the Fixed Bit in `first_byte` without checking header form. + /// + /// # Parameters + /// * `val`: The new value for the Fixed Bit (0 or 1). Input masked to 1 bit. + /// + /// # Safety + /// This is a direct bitwise operation on `first_byte`. + #[inline] + pub unsafe fn unchecked_set_fixed_bit(&mut self, val: u8) { + self.first_byte = (self.first_byte & !FIXED_BIT_MASK) | ((val & 1) << 6); + } +} + +impl fmt::Debug for QuicHdr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut s = f.debug_struct("QuicHdr"); + s.field( + "first_byte", + &format_args!( + "{:#04x} (Form: {}, Fixed: {}, TypeSpecific: {:#04x})", + self.first_byte, + if self.is_long_header() { + "Long" + } else { + "Short" + }, + self.fixed_bit(), + self.first_byte & !(HEADER_FORM_BIT | FIXED_BIT_MASK) + ), + ); + s.field("header_type", &self.header_type); + match self.header_type { + QuicHeaderType::QuicLong => { + // Safely access long header fields for debug output + s.field("version", &self.version().ok()); + s.field("dc_id", &self.dc_id()); + s.field("sc_id", &self.sc_id().ok()); + // For more detail, could print packet_type, pn_len_bits etc. + } + QuicHeaderType::QuicShort { .. } => { + s.field("dc_id", &self.dc_id()); + s.field("spin_bit", &self.short_spin_bit().ok()); + s.field("key_phase", &self.short_key_phase().ok()); + } + }; + s.finish() + } +} + +#[cfg(feature = "serde")] +mod serde_header_impl { + use super::*; + use serde::{ + de::{self, Error as SerdeError, Visitor}, + Deserializer, Serializer, + }; + + impl serde::Serialize for QuicHdr { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // Max possible size: 1 (first_byte) + 4 (version) + 1 (DCIL) + MAX_CID + 1 (SCIL) + MAX_CID + let mut buf = [0u8; 1 + 4 + 1 + QUIC_MAX_CID_LEN + 1 + QUIC_MAX_CID_LEN]; + let mut current_idx = 0usize; + buf[current_idx] = self.first_byte; + current_idx += 1; + match self.header_type { + QuicHeaderType::QuicLong => { + let long_hdr = unsafe { &self.inner.long }; // Safe due to header_type check + buf[current_idx..current_idx + 4].copy_from_slice(&long_hdr.version); + current_idx += 4; + let dc_len = long_hdr.dst.len() as usize; // Length from QuicDstConnLong + buf[current_idx] = long_hdr.dst.len(); + current_idx += 1; + buf[current_idx..current_idx + dc_len].copy_from_slice(long_hdr.dst.as_slice()); + current_idx += dc_len; + let sc_len = long_hdr.src.len() as usize; // Length from QuicSrcConnLong + buf[current_idx] = long_hdr.src.len(); + current_idx += 1; + buf[current_idx..current_idx + sc_len].copy_from_slice(long_hdr.src.as_slice()); + current_idx += sc_len; + } + QuicHeaderType::QuicShort { dc_id_len } => { + // For short headers, dc_id_len from header_type is authoritative. + // self.inner.short.dst.bytes contains the data. + // self.inner.short.dst.len() should match dc_id_len if consistent. + let short_hdr_dst_bytes = unsafe { &self.inner.short.dst.bytes }; // Safe due to header_type + let actual_dc_len = cmp::min(dc_id_len as usize, QUIC_MAX_CID_LEN); + + if actual_dc_len > 0 { + buf[current_idx..current_idx + actual_dc_len] + .copy_from_slice(&short_hdr_dst_bytes[..actual_dc_len]); + } + current_idx += actual_dc_len; + } + } + serializer.serialize_bytes(&buf[..current_idx]) + } + } + + struct QuicHdrVisitor; + impl<'de> Visitor<'de> for QuicHdrVisitor { + type Value = QuicHdr; + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "raw QUIC header bytes") + } + fn visit_bytes(self, v: &[u8]) -> Result + where + E: de::Error, + { + if v.is_empty() { + return Err(E::custom("QUIC header cannot be empty")); + } + let first_byte = v[0]; + let mut current_idx = 1usize; // Start parsing after the first byte + if (first_byte & HEADER_FORM_BIT) != 0 { + // Long Header + if v.len() < QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE { + // Basic check for minimal parts + return Err(E::custom(format!( + "Truncated long header: got {} bytes, need at least {}", + v.len(), + QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE + ))); + } + // Create with QuicLong type, then populate. first_byte set at the end. + let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); + // Version + if v.len() < current_idx + 4 { + return Err(E::custom("Truncated version in long header")); + } + let mut version_bytes = [0u8; 4]; + version_bytes.copy_from_slice(&v[current_idx..current_idx + 4]); + hdr.set_version(u32::from_be_bytes(version_bytes)) + .map_err(E::custom)?; + current_idx += 4; + // DCID + if v.len() < current_idx + 1 { + return Err(E::custom("Missing DCIL in long header")); + } + let dc_len_on_wire = v[current_idx] as usize; + current_idx += 1; + if dc_len_on_wire > QUIC_MAX_CID_LEN { + return Err(E::custom(format!( + "DCID length {} from wire exceeds max {}", + dc_len_on_wire, QUIC_MAX_CID_LEN + ))); + } + if v.len() < current_idx + dc_len_on_wire { + return Err(E::custom("Truncated DCID in long header")); + } + hdr.set_dc_id(&v[current_idx..current_idx + dc_len_on_wire]); // This also sets length in .inner.long.dst + current_idx += dc_len_on_wire; + // SCID + if v.len() < current_idx + 1 { + return Err(E::custom("Missing SCIL in long header")); + } + let sc_len_on_wire = v[current_idx] as usize; + current_idx += 1; + if sc_len_on_wire > QUIC_MAX_CID_LEN { + return Err(E::custom(format!( + "SCID length {} from wire exceeds max {}", + sc_len_on_wire, QUIC_MAX_CID_LEN + ))); + } + if v.len() < current_idx + sc_len_on_wire { + return Err(E::custom("Truncated SCID in long header")); + } + hdr.set_sc_id(&v[current_idx..current_idx + sc_len_on_wire]) + .map_err(E::custom)?; + hdr.set_first_byte(first_byte); // Set the original first_byte + Ok(hdr) + } else { + // Short Header + // For short headers, the length of DCID is not on the wire. + // The deserializer must infer it from the remaining bytes. + // This makes QuicHeaderType::QuicShort { dc_id_len } crucial. + let dcid_bytes_from_slice = &v[current_idx..]; + let dc_id_len_parsed = + cmp::min(dcid_bytes_from_slice.len(), QUIC_MAX_CID_LEN) as u8; + let mut hdr = QuicHdr::new(QuicHeaderType::QuicShort { + dc_id_len: dc_id_len_parsed, + }); + hdr.set_dc_id(&dcid_bytes_from_slice[..dc_id_len_parsed as usize]); + hdr.set_first_byte(first_byte); // Set the original first_byte + Ok(hdr) + } + } + } + impl<'de> serde::Deserialize<'de> for QuicHdr { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_bytes(QuicHdrVisitor) + } } } #[cfg(test)] mod tests { use super::*; + #[cfg(feature = "serde")] + use serde_test::{assert_tokens, Token}; #[test] - fn test_quic_hdr_len() { - assert_eq!(QuicHdr::LEN, 7, "QuicHdr::LEN should be 7 bytes"); + fn test_min_header_len_constants() { + assert_eq!(QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE, 7); + assert_eq!(QuicHdr::MIN_SHORT_HDR_LEN_ON_WIRE, 1); } #[test] - fn test_quic_hdr_new_and_long_header_getters() { - let version_val = 0x00000001; - let dc_id_len_val = 8; - let sc_id_len_val = 4; - let pn_len_bits_val = 0b11; - let hdr = QuicHdr::new(version_val, dc_id_len_val, sc_id_len_val, pn_len_bits_val); + fn test_long_header_creation_and_accessors() { + let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); assert!(hdr.is_long_header()); - assert_eq!( - hdr.fixed_bit(), - 1, - "Fixed bit should be 1 for Initial packet" - ); - assert_eq!( - hdr.long_packet_type(), - 0b00, - "Long Packet Type should be Initial (00)" - ); - assert_eq!(hdr.reserved_bits_long(), 0b00, "Reserved bits should be 00"); - assert_eq!(hdr.pn_length_bits_long(), pn_len_bits_val); - assert_eq!( - hdr.packet_number_length_long(), - (pn_len_bits_val + 1) as usize - ); - let expected_first_byte = HEADER_FORM_BIT - | FIXED_BIT_MASK - | (0b00 << LONG_PACKET_TYPE_SHIFT) - | (0b00 << RESERVED_BITS_LONG_SHIFT) - | pn_len_bits_val; - assert_eq!( - hdr.first_byte(), - expected_first_byte, - "First byte construction for Long Header is incorrect" - ); - assert_eq!(hdr.version(), version_val); - assert_eq!(hdr.dc_id_len(), dc_id_len_val); - assert_eq!(hdr.sc_id_len(), sc_id_len_val); + assert_eq!(hdr.first_byte() & 0xC0, HEADER_FORM_BIT | FIXED_BIT_MASK); // Form and Fixed bits + assert!(hdr.set_long_packet_type(0b01).is_ok()); // 0-RTT + assert_eq!(hdr.long_packet_type(), Ok(0b01)); + assert!(hdr.set_reserved_bits_long(0b00).is_ok()); + assert_eq!(hdr.reserved_bits_long(), Ok(0b00)); + assert!(hdr.set_packet_number_length_long(4).is_ok()); // 4 bytes PN + assert_eq!(hdr.pn_length_bits_long(), Ok(0b11)); // Encoded as 3 + assert_eq!(hdr.packet_number_length_long(), Ok(4)); + assert!(hdr.set_version(0x0000_0001).is_ok()); + assert_eq!(hdr.version(), Ok(0x0000_0001)); + let dcid_data = [1, 2, 3, 4, 5, 6, 7, 8]; + let scid_data = [0xA, 0xB, 0xC, 0xD]; + hdr.set_dc_id(&dcid_data); // Sets DCID and its length (8) + assert!(hdr.set_sc_id(&scid_data).is_ok()); // Sets SCID and its length (4) + assert_eq!(hdr.dc_id_effective_len(), 8); + assert_eq!(hdr.dc_id_len_on_wire(), Ok(8)); + assert_eq!(hdr.dc_id(), &dcid_data); + assert_eq!(hdr.sc_id_len_on_wire(), Ok(4)); + assert_eq!(hdr.sc_id().unwrap(), &scid_data); + // Check internal consistency of CIDs within the QuicHdrLong part + unsafe { + assert_eq!(hdr.inner.long.dst.len(), 8); + assert_eq!(hdr.inner.long.dst.as_slice(), &dcid_data); + assert_eq!(hdr.inner.long.src.len(), 4); + assert_eq!(hdr.inner.long.src.as_slice(), &scid_data); + } + } + + #[test] + fn test_short_header_creation_and_accessors() { + let dcid_data = [0xAA, 0xBB, 0xCC]; + // For short headers, DCID length is contextual. + let mut hdr = QuicHdr::new(QuicHeaderType::QuicShort { + dc_id_len: dcid_data.len() as u8, + }); + assert!(!hdr.is_long_header()); + assert_eq!(hdr.first_byte() & 0xC0, FIXED_BIT_MASK); // Form bit 0, Fixed bit 1 + + assert!(hdr.set_short_spin_bit(true).is_ok()); + assert_eq!(hdr.short_spin_bit(), Ok(true)); + assert!(hdr.set_short_reserved_bits(0b00).is_ok()); + assert_eq!(hdr.short_reserved_bits(), Ok(0b00)); + assert!(hdr.set_short_key_phase(false).is_ok()); + assert_eq!(hdr.short_key_phase(), Ok(false)); + assert!(hdr.set_short_packet_number_length(1).is_ok()); + assert_eq!(hdr.short_pn_length_bits(), Ok(0b00)); + assert_eq!(hdr.short_packet_number_length(), Ok(1)); + hdr.set_dc_id(&dcid_data); + if let QuicHeaderType::QuicShort { dc_id_len } = hdr.header_type() { + assert_eq!(dc_id_len, dcid_data.len() as u8); + } else { + panic!("Header type mismatch after setting DCID for short header."); + } + assert_eq!(hdr.dc_id_effective_len(), dcid_data.len() as u8); + assert_eq!(hdr.dc_id(), &dcid_data); + assert!(hdr.sc_id_len_on_wire().is_err()); + assert!(hdr.sc_id().is_err()); + assert!(hdr.version().is_err()); } #[test] - fn test_first_byte_setters_and_getters_for_long_header() { - let mut hdr = QuicHdr::new(1, 0, 0, 0); - hdr.set_header_form(false); + fn test_header_type_transition_and_cid_reset() { + let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); + hdr.set_dc_id(&[1, 2, 3]); + assert!(hdr.set_version(123).is_ok()); + assert_eq!(hdr.dc_id(), &[1, 2, 3]); + let original_first_byte_bits = hdr.first_byte() & 0x3F; + hdr.set_header_type(QuicHeaderType::QuicShort { dc_id_len: 0 }); assert!(!hdr.is_long_header()); - hdr.set_header_form(true); + assert!(hdr.version().is_err()); + assert_eq!(hdr.dc_id_effective_len(), 0); + assert_eq!(hdr.dc_id(), &[]); + assert_eq!(hdr.first_byte() & HEADER_FORM_BIT, 0); + assert_eq!(hdr.first_byte() & FIXED_BIT_MASK, FIXED_BIT_MASK); + assert_eq!(hdr.first_byte() & 0x3F, original_first_byte_bits); + hdr.set_dc_id(&[4, 5, 6]); + assert_eq!(hdr.dc_id_effective_len(), 3); + assert_eq!(hdr.dc_id(), &[4, 5, 6]); + if let QuicHeaderType::QuicShort { dc_id_len } = hdr.header_type() { + assert_eq!(dc_id_len, 3); + } else { + panic!("Not a short header!"); + } + let short_first_byte_bits = hdr.first_byte() & 0x3F; + hdr.set_header_type(QuicHeaderType::QuicLong); assert!(hdr.is_long_header()); - hdr.set_fixed_bit(0); - assert_eq!(hdr.fixed_bit(), 0); - hdr.set_fixed_bit(1); - assert_eq!(hdr.fixed_bit(), 1); - hdr.set_long_packet_type(0b01); - assert_eq!(hdr.long_packet_type(), 0b01); - hdr.set_reserved_bits_long(0b10); - assert_eq!(hdr.reserved_bits_long(), 0b10); - hdr.set_reserved_bits_long(0b00); - hdr.set_pn_length_bits_long(0b01); - assert_eq!(hdr.pn_length_bits_long(), 0b01); - assert_eq!(hdr.packet_number_length_long(), 2); - hdr.set_packet_number_length_long(4); - assert_eq!(hdr.pn_length_bits_long(), 0b11); - assert_eq!(hdr.packet_number_length_long(), 4); - hdr.set_packet_number_length_long(0); - assert_eq!(hdr.pn_length_bits_long(), 0b00); - assert_eq!(hdr.packet_number_length_long(), 1); - hdr.set_packet_number_length_long(5); - assert_eq!(hdr.pn_length_bits_long(), 0b11); - assert_eq!(hdr.packet_number_length_long(), 4); + assert_eq!(hdr.version(), Ok(0)); + assert_eq!(hdr.dc_id_effective_len(), 0); + assert_eq!(hdr.dc_id(), &[]); + assert_eq!(hdr.sc_id_len_on_wire(), Ok(0)); + assert_eq!(hdr.first_byte() & HEADER_FORM_BIT, HEADER_FORM_BIT); + assert_eq!(hdr.first_byte() & FIXED_BIT_MASK, FIXED_BIT_MASK); + assert_eq!(hdr.first_byte() & 0x3F, short_first_byte_bits); } + #[cfg(feature = "serde")] #[test] - fn test_multi_byte_field_getters_and_setters() { - let mut hdr = QuicHdr::default(); - let test_version = 0x12345678; - hdr.set_version(test_version); - assert_eq!(hdr.version(), test_version); - let test_dcid_len = 20; - hdr.set_dc_id_len(test_dcid_len); - assert_eq!(hdr.dc_id_len(), test_dcid_len); - let test_scid_len = 0; - hdr.set_sc_id_len(test_scid_len); - assert_eq!(hdr.sc_id_len(), test_scid_len); + fn test_long_header_serde_roundtrip() { + let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); + // Set first byte: Form=1, Fixed=1, Type=0(Initial), Reserved=0, PNLEN=2 (encoded 0b01) + hdr.set_first_byte(0xC0 | (0b00 << 4) | (0b00 << 2) | 0b01); + assert!(hdr.set_version(0x01020304).is_ok()); + hdr.set_dc_id(&[0xAA; 8]); + assert!(hdr.set_sc_id(&[0xBB; 4]).is_ok()); + let expected_first_byte = hdr.first_byte(); // Should be 0xC0 | 0b01 = 0xC1 + assert_eq!(expected_first_byte, 0xC1); + let bytes = bincode::serialize(&hdr).expect("Serialization failed"); + assert_eq!(bytes[0], expected_first_byte); + assert_eq!(&bytes[1..5], &0x01020304u32.to_be_bytes()); // Version + assert_eq!(bytes[5], 8); // DCIL + assert_eq!(&bytes[6..14], &[0xAA; 8]); // DCID + assert_eq!(bytes[14], 4); // SCIL + assert_eq!(&bytes[15..19], &[0xBB; 4]); // SCID + assert_eq!(bytes.len(), 19); // Total length + let de: QuicHdr = bincode::deserialize(&bytes).expect("Deserialization failed"); + assert_eq!(de.first_byte(), expected_first_byte); + assert!(de.is_long_header()); + assert_eq!(de.header_type(), QuicHeaderType::QuicLong); // Deserializer sets this + assert_eq!(de.version().unwrap(), 0x01020304); + assert_eq!(de.dc_id_effective_len(), 8); + assert_eq!(de.dc_id(), &[0xAA; 8]); + assert_eq!(de.sc_id_len_on_wire().unwrap(), 4); + assert_eq!(de.sc_id().unwrap(), &[0xBB; 4]); + assert_eq!(de.long_packet_type(), Ok(0b00)); + assert_eq!(de.reserved_bits_long(), Ok(0b00)); + assert_eq!(de.pn_length_bits_long(), Ok(0b01)); // PNLEN=2 } + #[cfg(feature = "serde")] #[test] - fn test_new_short_header_first_byte() { - let fb1 = QuicHdr::new_short_header_first_byte(true, false, 0b01); - assert_eq!(fb1, 0b01100001, "Expected 0x61"); - assert_eq!( - (fb1 & HEADER_FORM_BIT), - 0, - "Short header form bit incorrect" + fn test_short_header_serde_roundtrip() { + let dcid_data = [0xCC, 0xDD, 0xEE, 0xFF, 0x11]; + let mut hdr = QuicHdr::new(QuicHeaderType::QuicShort { + dc_id_len: dcid_data.len() as u8, + }); + hdr.set_first_byte(0x40 | SHORT_SPIN_BIT_MASK | SHORT_KEY_PHASE_BIT_MASK | 0b00); + hdr.set_dc_id(&dcid_data); + let expected_first_byte = hdr.first_byte(); + assert_eq!(expected_first_byte, 0x64); + let bytes = bincode::serialize(&hdr).expect("Serialization failed"); + assert_eq!(bytes[0], expected_first_byte); + assert_eq!(&bytes[1..], &dcid_data); // DCID directly follows + assert_eq!(bytes.len(), 1 + dcid_data.len()); + let de: QuicHdr = bincode::deserialize(&bytes).expect("Deserialization failed"); + assert_eq!(de.first_byte(), expected_first_byte); + assert!(!de.is_long_header()); + if let QuicHeaderType::QuicShort { dc_id_len } = de.header_type() { + assert_eq!(dc_id_len, dcid_data.len() as u8); + } else { + panic!("Deserialized to wrong header type: {:?}", de.header_type()); + } + assert_eq!(de.dc_id_effective_len(), dcid_data.len() as u8); + assert_eq!(de.dc_id(), &dcid_data); + assert_eq!(de.short_spin_bit(), Ok(true)); + assert_eq!(de.short_reserved_bits(), Ok(0b00)); + assert_eq!(de.short_key_phase(), Ok(true)); + assert_eq!(de.short_pn_length_bits(), Ok(0b00)); // PNLEN=1 + } + + #[test] + fn test_cid_struct_helpers() { + let mut cid_long = QuicDstConnLong::new(); + assert!(cid_long.is_empty()); + cid_long.set(&[0xDE, 0xAD, 0xBE, 0xEF]); + assert_eq!(cid_long.len(), 4); + assert!(!cid_long.is_empty()); + assert_eq!(cid_long.as_slice(), &[0xDE, 0xAD, 0xBE, 0xEF]); + let mut full_cid_bytes_expected = [0u8; QUIC_MAX_CID_LEN]; + full_cid_bytes_expected[0..4].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]); + assert_eq!(cid_long.bytes, full_cid_bytes_expected); + let mut cid_short = QuicDstConnShort::new(); + cid_short.set(&[0x11, 0x22]); + assert_eq!(cid_short.len(), 2); + assert_eq!(cid_short.as_slice(), &[0x11, 0x22]); + } + + #[cfg(feature = "serde")] + #[test] + fn test_cid_serde_long_direct() { + let mut cid = QuicDstConnLong::new(); + cid.set(&[1, 2, 3]); + assert_tokens( + &cid, + &[ + Token::Struct { + name: "QuicDstConnLong", + len: 2, + }, + Token::Str("len"), + Token::U8(3), + Token::Str("bytes"), + Token::BorrowedBytes(&[1, 2, 3]), + Token::StructEnd, + ], ); + } + + #[cfg(feature = "serde")] + #[test] + fn test_cid_serde_short_direct() { + let mut cid = QuicDstConnShort::new(); + cid.set(&[1, 2, 3, 4]); + assert_tokens(&cid, &[Token::BorrowedBytes(&[1, 2, 3, 4])]); + } + + #[test] + fn test_parse_realistic_long_header_initial_packet() { + let packet_bytes: &[u8] = &[ + 0xC1, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, 0x08, + 0x08, 0x76, 0x05, 0x96, 0x95, 0xC0, 0x9A, 0x58, 0x57, + ]; + let mut current_offset = 0; + let first_byte = packet_bytes[current_offset]; + current_offset += 1; assert_eq!( - (fb1 & FIXED_BIT_MASK), - FIXED_BIT_MASK, - "Short header fixed bit incorrect" + (first_byte & HEADER_FORM_BIT), + HEADER_FORM_BIT, + "Should be Long Header according to first byte" ); - assert_eq!( - (fb1 & SHORT_SPIN_BIT_MASK) >> SHORT_SPIN_BIT_SHIFT, - 1, - "Spin bit incorrect" + let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); + hdr.set_first_byte(first_byte); + let mut version_bytes = [0u8; 4]; + version_bytes.copy_from_slice(&packet_bytes[current_offset..current_offset + 4]); + current_offset += 4; + hdr.set_version(u32::from_be_bytes(version_bytes)) + .expect("Set version failed for Long Header"); + let dcid_len_on_wire = packet_bytes[current_offset]; + current_offset += 1; + assert!( + dcid_len_on_wire as usize <= QUIC_MAX_CID_LEN, + "DCID length exceeds max" ); - assert_eq!( - (fb1 & SHORT_RESERVED_BITS_MASK), - 0, - "Reserved bits not zero" + let dcid_data_from_packet = + &packet_bytes[current_offset..current_offset + dcid_len_on_wire as usize]; + hdr.set_dc_id(dcid_data_from_packet); + current_offset += dcid_len_on_wire as usize; + let scid_len_on_wire = packet_bytes[current_offset]; + current_offset += 1; + assert!( + scid_len_on_wire as usize <= QUIC_MAX_CID_LEN, + "SCID length exceeds max" ); + let scid_data_from_packet = + &packet_bytes[current_offset..current_offset + scid_len_on_wire as usize]; + hdr.set_sc_id(scid_data_from_packet) + .expect("Set SCID failed for Long Header"); + current_offset += scid_len_on_wire as usize; + assert!(hdr.is_long_header()); + assert_eq!(hdr.fixed_bit(), 1); + assert_eq!(hdr.long_packet_type(), Ok(0b00)); + assert_eq!(hdr.reserved_bits_long(), Ok(0b00)); + assert_eq!(hdr.pn_length_bits_long(), Ok(0b01)); + assert_eq!(hdr.packet_number_length_long(), Ok(2)); + assert_eq!(hdr.version(), Ok(0x00000001)); + assert_eq!(hdr.dc_id_len_on_wire(), Ok(8)); assert_eq!( - (fb1 & SHORT_KEY_PHASE_BIT_MASK) >> SHORT_KEY_PHASE_BIT_SHIFT, - 0, - "Key phase incorrect" + hdr.dc_id(), + &[0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, 0x08] ); + assert_eq!(hdr.sc_id_len_on_wire(), Ok(8)); assert_eq!( - (fb1 & PN_LENGTH_BITS_MASK), - 0b01, - "PN length bits incorrect" + hdr.sc_id().unwrap(), + &[0x76, 0x05, 0x96, 0x95, 0xC0, 0x9A, 0x58, 0x57] ); - let fb2 = QuicHdr::new_short_header_first_byte(false, true, 0b11); - assert_eq!(fb2, 0b01000111, "Expected 0x47"); - assert_eq!((fb2 & SHORT_SPIN_BIT_MASK) >> SHORT_SPIN_BIT_SHIFT, 0); assert_eq!( - (fb2 & SHORT_KEY_PHASE_BIT_MASK) >> SHORT_KEY_PHASE_BIT_SHIFT, - 1 + current_offset, + 1 // first_byte + + 4 // version + + 1 // dcil + + 8 // dcid + + 1 // scil + + 8 // scid ); - assert_eq!((fb2 & PN_LENGTH_BITS_MASK), 0b11); } #[test] - fn test_short_header_setters_and_getters() { - let mut hdr = QuicHdr::default(); - hdr.set_header_form(false); - hdr.set_fixed_bit(1); - hdr.set_short_spin_bit(true); - assert!(hdr.short_spin_bit()); - assert_eq!(hdr.first_byte & SHORT_SPIN_BIT_MASK, SHORT_SPIN_BIT_MASK); - hdr.set_short_spin_bit(false); - assert!(!hdr.short_spin_bit()); - assert_eq!(hdr.first_byte & SHORT_SPIN_BIT_MASK, 0); - hdr.first_byte |= SHORT_RESERVED_BITS_MASK; - assert_eq!(hdr.short_reserved_bits(), 0b11); - hdr.set_short_reserved_bits(0b10); + fn test_parse_realistic_short_header_1rtt_packet() { + // Based on RFC 9000 Appendix A.4 (1-RTT) + // First Byte: Short Header (0), Fixed Bit (1), Spin Bit (0), Reserved (00), Key Phase (1), PN Len (01 -> 2 bytes) + // 01000101 = 0x45 + let dcid_from_connection_context = [0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, 0x08]; + let contextual_dcid_len = dcid_from_connection_context.len() as u8; + let packet_bytes: &[u8] = &[ + 0x45, // First Byte + // DCID (actual bytes, length is from context, not on wire here) + 0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, + 0x08, + // Packet number (e.g., 0x00, 0x01) would follow. + ]; + let mut current_offset = 0; + let first_byte = packet_bytes[current_offset]; + current_offset += 1; assert_eq!( - hdr.short_reserved_bits(), - 0b00, - "Short reserved bits should be forced to 0" + (first_byte & HEADER_FORM_BIT), + 0, + "Should be Short Header according to first byte" ); - assert_eq!(hdr.first_byte & SHORT_RESERVED_BITS_MASK, 0); - hdr.set_short_key_phase(true); - assert!(hdr.short_key_phase()); + let mut hdr = QuicHdr::new(QuicHeaderType::QuicShort { + dc_id_len: contextual_dcid_len, + }); + hdr.set_first_byte(first_byte); // Set the entire first byte + let dcid_data_from_packet = + &packet_bytes[current_offset..current_offset + contextual_dcid_len as usize]; + hdr.set_dc_id(dcid_data_from_packet); // This sets data and updates internal length fields + current_offset += contextual_dcid_len as usize; // <<< THIS LINE WAS THE FIX (uncommented and placed correctly) + assert!(!hdr.is_long_header()); // Confirmed by first_byte set earlier + assert_eq!(hdr.fixed_bit(), 1); + assert_eq!(hdr.short_spin_bit(), Ok(false)); + assert_eq!(hdr.short_reserved_bits(), Ok(0b00)); + assert_eq!(hdr.short_key_phase(), Ok(true)); + assert_eq!(hdr.short_pn_length_bits(), Ok(0b01)); // Encoded: 2 bytes actual length + assert_eq!(hdr.short_packet_number_length(), Ok(2)); + assert_eq!(hdr.dc_id_effective_len(), contextual_dcid_len); + assert_eq!(hdr.dc_id(), &dcid_from_connection_context); + assert_eq!(hdr.version(), Err(QuicHdrError::InvalidHeaderForm)); + assert_eq!(hdr.sc_id(), Err(QuicHdrError::InvalidHeaderForm)); assert_eq!( - hdr.first_byte & SHORT_KEY_PHASE_BIT_MASK, - SHORT_KEY_PHASE_BIT_MASK + hdr.dc_id_len_on_wire(), + Err(QuicHdrError::InvalidHeaderForm) ); - hdr.set_short_key_phase(false); - assert!(!hdr.short_key_phase()); - assert_eq!(hdr.first_byte & SHORT_KEY_PHASE_BIT_MASK, 0); - hdr.set_short_pn_length_bits(0b10); // 3 bytes - assert_eq!(hdr.short_pn_length_bits(), 0b10); - assert_eq!(hdr.short_packet_number_length(), 3); - hdr.set_short_packet_number_length(1); - assert_eq!(hdr.short_pn_length_bits(), 0b00); - assert_eq!(hdr.short_packet_number_length(), 1); - hdr.set_short_packet_number_length(4); - assert_eq!(hdr.short_pn_length_bits(), 0b11); - assert_eq!(hdr.short_packet_number_length(), 4); - hdr.set_short_packet_number_length(0); - assert_eq!(hdr.short_pn_length_bits(), 0b00); - assert_eq!(hdr.short_packet_number_length(), 1); - hdr.set_short_packet_number_length(5); - assert_eq!(hdr.short_pn_length_bits(), 0b11); - assert_eq!(hdr.short_packet_number_length(), 4); - hdr.set_header_form(false); - hdr.set_fixed_bit(1); - hdr.set_short_spin_bit(true); - hdr.set_short_reserved_bits(0); - hdr.set_short_key_phase(true); - hdr.set_short_pn_length_bits(0b01); assert_eq!( - hdr.first_byte(), - 0b01100101, - "Constructed short header byte mismatch" + current_offset, + 1 /* first_byte */ + contextual_dcid_len as usize /* dcid */ ); } #[test] - fn test_raw_first_byte_combined_set_and_readback() { - let mut hdr = QuicHdr::new(1, 0, 0, 0); - // Construct first_byte: Long Header (1), Fixed (1), Type (Retry=11), Reserved (00), PN Len (2 bytes actual = 01 encoded) - // Bit: 7 6 5 4 3 2 1 0 - // Value: 1 1 1 1 0 0 0 1 => 0xF1 - let first_byte_val = 0xF1; - hdr.set_first_byte(first_byte_val); - assert_eq!(hdr.first_byte(), first_byte_val); - assert!(hdr.is_long_header()); - assert_eq!(hdr.fixed_bit(), 1); - assert_eq!(hdr.long_packet_type(), 0b11); - assert_eq!(hdr.reserved_bits_long(), 0b00); - assert_eq!(hdr.pn_length_bits_long(), 0b01); - assert_eq!(hdr.packet_number_length_long(), 2); + fn test_ebpf_like_agent_parsing_long_header() { + let packet_bytes: &[u8] = &[ + 0xC1, // First Byte (Long, Fixed, Type=Initial, Res=0, PNLEN=2bytes) + 0x00, 0x00, 0x00, 0x01, // Version (QUIC v1) + 0x08, // DCID Len + 0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, 0x08, // DCID + 0x05, // SCID Len + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, // SCID + 0xAB, 0xCD, // Example Packet Number (2 bytes), not parsed by QuicHdr itself + ]; + let total_packet_data_len = packet_bytes.len(); // Simulates ctx.data_end() or available length + let mut parse_ptr_offset = 0; // Simulates current read position in packet buffer + let mut quic_hdr_on_stack: QuicHdr; // Represents a stack-allocated struct + if parse_ptr_offset + 1 > total_packet_data_len { + panic!("Packet too short for first_byte"); + } + let first_byte = packet_bytes[parse_ptr_offset]; + parse_ptr_offset += 1; + if (first_byte & HEADER_FORM_BIT) != 0 { + // Is Long Header + quic_hdr_on_stack = QuicHdr::new(QuicHeaderType::QuicLong); + quic_hdr_on_stack.set_first_byte(first_byte); // Apply all bits from packet's first_byte + if parse_ptr_offset + 4 > total_packet_data_len { + panic!("Packet too short for version"); + } + let mut ver_buf = [0u8; 4]; + ver_buf.copy_from_slice(&packet_bytes[parse_ptr_offset..parse_ptr_offset + 4]); + quic_hdr_on_stack + .set_version(u32::from_be_bytes(ver_buf)) + .unwrap(); + parse_ptr_offset += 4; + if parse_ptr_offset + 1 > total_packet_data_len { + panic!("Packet too short for DCIL byte"); + } + let dcid_len_from_pkt = packet_bytes[parse_ptr_offset]; + parse_ptr_offset += 1; + if dcid_len_from_pkt as usize > QUIC_MAX_CID_LEN { + panic!("DCIL too large"); + } + if parse_ptr_offset + dcid_len_from_pkt as usize > total_packet_data_len { + panic!("Packet too short for DCID data"); + } + let mut dcid_temp_buf = [0u8; QUIC_MAX_CID_LEN]; + if dcid_len_from_pkt > 0 { + dcid_temp_buf[..dcid_len_from_pkt as usize].copy_from_slice( + &packet_bytes[parse_ptr_offset..parse_ptr_offset + dcid_len_from_pkt as usize], + ); + } + quic_hdr_on_stack.set_dc_id(&dcid_temp_buf[..dcid_len_from_pkt as usize]); + parse_ptr_offset += dcid_len_from_pkt as usize; + if parse_ptr_offset + 1 > total_packet_data_len { + panic!("Packet too short for SCIL byte"); + } + let scid_len_from_pkt = packet_bytes[parse_ptr_offset]; + parse_ptr_offset += 1; + if scid_len_from_pkt as usize > QUIC_MAX_CID_LEN { + panic!("SCIL too large"); + } + if parse_ptr_offset + scid_len_from_pkt as usize > total_packet_data_len { + panic!("Packet too short for SCID data"); + } + let mut scid_temp_buf = [0u8; QUIC_MAX_CID_LEN]; + if scid_len_from_pkt > 0 { + scid_temp_buf[..scid_len_from_pkt as usize].copy_from_slice( + &packet_bytes[parse_ptr_offset..parse_ptr_offset + scid_len_from_pkt as usize], + ); + } + quic_hdr_on_stack + .set_sc_id(&scid_temp_buf[..scid_len_from_pkt as usize]) + .unwrap(); + parse_ptr_offset += scid_len_from_pkt as usize; + assert!(quic_hdr_on_stack.is_long_header()); + assert_eq!(quic_hdr_on_stack.long_packet_type(), Ok(0b00)); // Initial + assert_eq!(quic_hdr_on_stack.packet_number_length_long(), Ok(2)); + assert_eq!(quic_hdr_on_stack.version(), Ok(0x00000001)); + assert_eq!( + quic_hdr_on_stack.dc_id(), + &[0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, 0x08] + ); + assert_eq!( + quic_hdr_on_stack.sc_id().unwrap(), + &[0xAA, 0xBB, 0xCC, 0xDD, 0xEE] + ); + let pn_actual_len = quic_hdr_on_stack.packet_number_length_long().unwrap(); + assert_eq!( + &packet_bytes[parse_ptr_offset..parse_ptr_offset + pn_actual_len], + &[0xAB, 0xCD] + ); + } else { + panic!("Test logic assumes Long Header based on first byte of test data."); + } + } + + #[test] + fn test_ebpf_like_agent_parsing_short_header() { + let known_dcid_value_from_context = [0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]; + let known_dcid_len_from_context = known_dcid_value_from_context.len() as u8; + let packet_bytes: &[u8] = &[ + 0x45, // First Byte (Short, Fixed, Spin=0, Res=0, KeyPhase=1, PNLEN=2bytes) + // DCID bytes follow immediately; length is known from context. + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0xBE, + 0xEF, // Example Packet Number (2 bytes), not parsed by QuicHdr itself + ]; + let total_packet_data_len = packet_bytes.len(); + let mut parse_ptr_offset = 0; + let mut quic_hdr_on_stack: QuicHdr; + if parse_ptr_offset + 1 > total_packet_data_len { + panic!("Packet too short for first_byte"); + } + let first_byte = packet_bytes[parse_ptr_offset]; + parse_ptr_offset += 1; + if (first_byte & HEADER_FORM_BIT) == 0 { + // Is Short Header + quic_hdr_on_stack = QuicHdr::new(QuicHeaderType::QuicShort { + dc_id_len: known_dcid_len_from_context, + }); + quic_hdr_on_stack.set_first_byte(first_byte); + if known_dcid_len_from_context as usize > QUIC_MAX_CID_LEN { + panic!("Contextual DCID len too large"); + } + if parse_ptr_offset + known_dcid_len_from_context as usize > total_packet_data_len { + panic!("Packet too short for DCID data"); + } + let mut dcid_temp_buf = [0u8; QUIC_MAX_CID_LEN]; + if known_dcid_len_from_context > 0 { + dcid_temp_buf[..known_dcid_len_from_context as usize].copy_from_slice( + &packet_bytes + [parse_ptr_offset..parse_ptr_offset + known_dcid_len_from_context as usize], + ); + } + quic_hdr_on_stack.set_dc_id(&dcid_temp_buf[..known_dcid_len_from_context as usize]); + parse_ptr_offset += known_dcid_len_from_context as usize; + assert!(!quic_hdr_on_stack.is_long_header()); + assert_eq!(quic_hdr_on_stack.short_spin_bit(), Ok(false)); + assert_eq!(quic_hdr_on_stack.short_key_phase(), Ok(true)); + assert_eq!(quic_hdr_on_stack.short_packet_number_length(), Ok(2)); + assert_eq!( + quic_hdr_on_stack.dc_id_effective_len(), + known_dcid_len_from_context + ); + assert_eq!(quic_hdr_on_stack.dc_id(), &known_dcid_value_from_context); + let pn_actual_len = quic_hdr_on_stack.short_packet_number_length().unwrap(); + assert_eq!( + &packet_bytes[parse_ptr_offset..parse_ptr_offset + pn_actual_len], + &[0xBE, 0xEF] + ); + } else { + panic!("Test logic assumes Short Header based on first byte of test data."); + } } } diff --git a/tests/quic_hdr_integration.rs b/tests/quic_hdr_integration.rs new file mode 100644 index 0000000..ba18268 --- /dev/null +++ b/tests/quic_hdr_integration.rs @@ -0,0 +1,171 @@ +// --------------------------------------------------------------------------- +// tests/quic_hdr_integration.rs +// +// End‑to‑end integration tests for `network-types/src/quic.rs` +// +// • Guarantees the header code compiles in `#![no_std]` mode (because the +// library itself is rebuilt that way for integration tests). +// • Exercises realistic QUIC Initial (long) and 1‑RTT (short) headers. +// • Uses the public kernel‑style signature: +// +// fn parse_quic_header(ctx: &TcContext, parser: &mut Parser) +// +// • Pulls `aya_ebpf::*` exactly like production code. For host builds where +// the real crate is unavailable, a minimal stub is provided automatically. +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +use aya_ebpf::programs::TcContext; + +use network_types::quic::{QuicHdr, QuicHdrError, QuicHeaderType, QUIC_MAX_CID_LEN}; + +#[derive(Default)] +struct Parser { + offset: usize, +} + +/// eBPF-facing wrapper that extracts data from the context. +fn parse_quic_header(ctx: &TcContext, parser: &mut Parser) -> Result { + // This unsafe block is the integration point with the eBPF infrastructure. + let data = unsafe { + core::slice::from_raw_parts(ctx.data() as *const u8, ctx.data_end() - ctx.data()) + }; + // The actual parsing is delegated to a pure, testable function. + parse_quic_header_logic(data, parser) +} + +/// Pure parsing logic that operates on a byte slice. This is easily testable. +fn parse_quic_header_logic(data: &[u8], parser: &mut Parser) -> Result { + if parser.offset >= data.len() { + return Err(()); + } + let first = data[parser.offset]; + let slice = &data[parser.offset..]; + // Long Header + if first & 0x80 != 0 { + if slice.len() < QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE { + return Err(()); + } + let mut idx = 1usize; // after first_byte + let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); + hdr.set_first_byte(first); + let ver_bytes = <[u8; 4]>::try_from(&slice[idx..idx + 4]).unwrap(); + hdr.set_version(u32::from_be_bytes(ver_bytes)) + .map_err(|_| ())?; + idx += 4; + let dc_len = slice[idx] as usize; + idx += 1; + if dc_len > QUIC_MAX_CID_LEN || slice.len() < idx + dc_len { + return Err(()); + } + hdr.set_dc_id(&slice[idx..idx + dc_len]); + idx += dc_len; + let sc_len = slice[idx] as usize; + idx += 1; + if sc_len > QUIC_MAX_CID_LEN || slice.len() < idx + sc_len { + return Err(()); + } + hdr.set_sc_id(&slice[idx..idx + sc_len]).map_err(|_| ())?; + idx += sc_len; + parser.offset += idx; + return Ok(hdr); + } + // Short Header + let dcid_len_hint = (slice.len() - 1).min(QUIC_MAX_CID_LEN) as u8; + let dc_len = dcid_len_hint as usize; + let mut hdr = QuicHdr::new(QuicHeaderType::QuicShort { + dc_id_len: dcid_len_hint, + }); + hdr.set_first_byte(first); + if dc_len > 0 { + hdr.set_dc_id(&slice[1..1 + dc_len]); + } + parser.offset += 1 + dc_len; + Ok(hdr) +} + +const LONG_HDR_BYTES: [u8; 19] = [ + 0xC3, // Long, Fixed=1, Type=Initial(0), PNLEN=4 + 0x00, 0x00, 0x00, 0x01, // Version 1 + 0x08, // DCID len = 8 + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // DCID + 0x04, // SCID len = 4 + 0xAA, 0xBB, 0xCC, 0xDD, // SCID +]; + +const SHORT_HDR_BYTES: [u8; 9] = [ + 0x64, // Short, Fixed=1, Spin=1, KP=1, PNLEN=1 + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // DCID (8) +]; + +#[test] +fn long_header_full_parse() { + let mut parser = Parser::default(); + let hdr = parse_quic_header_logic(&LONG_HDR_BYTES, &mut parser).expect("parse failed"); + assert_eq!(parser.offset, LONG_HDR_BYTES.len()); + assert!(hdr.is_long_header()); + assert_eq!(hdr.version(), Ok(0x0000_0001)); + assert_eq!(hdr.long_packet_type(), Ok(0)); + assert_eq!(hdr.packet_number_length_long(), Ok(4)); + assert_eq!(hdr.dc_id_len_on_wire(), Ok(8)); + assert_eq!(hdr.dc_id(), &LONG_HDR_BYTES[6..14]); + assert_eq!(hdr.sc_id_len_on_wire(), Ok(4)); + assert_eq!(hdr.sc_id().unwrap(), &LONG_HDR_BYTES[15..19]); +} + +#[test] +fn short_header_full_parse() { + let mut parser = Parser::default(); + let hdr = parse_quic_header_logic(&SHORT_HDR_BYTES, &mut parser).expect("parse failed"); + assert_eq!(parser.offset, SHORT_HDR_BYTES.len()); + assert!(!hdr.is_long_header()); + assert_eq!(hdr.short_spin_bit(), Ok(true)); + assert_eq!(hdr.short_key_phase(), Ok(true)); + assert_eq!(hdr.short_packet_number_length(), Ok(1)); + assert_eq!(hdr.dc_id_effective_len(), 8); + assert_eq!(hdr.dc_id(), &SHORT_HDR_BYTES[1..]); +} + +#[test] +fn roundtrip_long_header_serialize_parse() { + // Build programmatically + let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); + hdr.set_first_byte(0xC3); + hdr.set_version(0x0000_0001).unwrap(); + hdr.set_dc_id(&LONG_HDR_BYTES[6..14]); + hdr.set_sc_id(&LONG_HDR_BYTES[15..19]).unwrap(); + // Manual serialisation (header only) + let mut wire = [0u8; 19]; + let mut idx = 0; + wire[idx] = hdr.first_byte(); + idx += 1; + wire[idx..idx + 4].copy_from_slice(&0x0000_0001u32.to_be_bytes()); + idx += 4; + wire[idx] = 8; + idx += 1; + wire[idx..idx + 8].copy_from_slice(hdr.dc_id()); + idx += 8; + wire[idx] = 4; + idx += 1; + wire[idx..idx + 4].copy_from_slice(hdr.sc_id().unwrap()); + assert_eq!(&wire, &LONG_HDR_BYTES); + // Parse back through the real parser + let mut parser = Parser::default(); + let reparsed = parse_quic_header_logic(&wire, &mut parser).unwrap(); + assert_eq!(reparsed.first_byte(), hdr.first_byte()); + assert_eq!(reparsed.version(), hdr.version()); + assert_eq!(reparsed.dc_id(), hdr.dc_id()); + assert_eq!(reparsed.sc_id().unwrap(), hdr.sc_id().unwrap()); +} + +#[test] +fn accessor_errors_are_correct() { + let mut parser = Parser::default(); + let long_hdr = parse_quic_header_logic(&LONG_HDR_BYTES, &mut parser).unwrap(); + parser.offset = 0; // reset + let short_hdr = parse_quic_header_logic(&SHORT_HDR_BYTES, &mut parser).unwrap(); + assert_eq!( + long_hdr.short_spin_bit(), + Err(QuicHdrError::InvalidHeaderForm) + ); + assert_eq!(short_hdr.version(), Err(QuicHdrError::InvalidHeaderForm)); +} From 4dde29176a5190a94a9f5b26b9073550aea454be Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Thu, 12 Jun 2025 18:55:36 -0500 Subject: [PATCH 03/14] Fixed `src/quic.rs` serde errors --- Cargo.toml | 7 +- src/quic.rs | 305 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 194 insertions(+), 118 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8eb6c10..db13036 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,13 @@ edition = "2021" [dependencies] serde = { version = "1", default-features = false, features = ["derive"], optional = true } +serde_bytes = { version = "0.11", optional = true } [dev-dependencies] -lazy_static = "1.5.0" aya-ebpf = { git = "https://github.com/aya-rs/aya", branch = "main" } aya-log-ebpf = { git = "https://github.com/aya-rs/aya", branch = "main" } -bincode = { version = "2.0.0-rc.3", default-features = false, features = ["serde","alloc"] } +bincode = { version = "2.0.0-rc.3", default-features = false, features = ["serde", "alloc"] } +serde_test = { version = "1.0", default-features = false } +[features] +serde = ["dep:serde", "dep:serde_bytes"] diff --git a/src/quic.rs b/src/quic.rs index 409ef86..b56defc 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -5,26 +5,23 @@ //! Designed for use inside eBPF (`aya`) programs → `#![no_std]`, //! fixed‑capacity buffers, no heap, packed layouts. -#![cfg_attr(not(test), no_std)] - use core::{cmp, fmt, hash, ptr}; pub const QUIC_MAX_CID_LEN: usize = 20; - -const HEADER_FORM_BIT: u8 = 0x80; // Bit 7: 1 for Long Header, 0 for Short Header -const FIXED_BIT_MASK: u8 = 0x40; // Bit 6: Must be 1 in QUIC v1 (except Version Negotiation) - -const LONG_PACKET_TYPE_MASK: u8 = 0x30; // Bits 5-4: Packet Type +const HEADER_FORM_BIT: u8 = 0x80; +const FIXED_BIT_MASK: u8 = 0x40; +const LONG_PACKET_TYPE_MASK: u8 = 0x30; const LONG_PACKET_TYPE_SHIFT: u8 = 4; -const RESERVED_BITS_LONG_MASK: u8 = 0x0C; // Bits 3-2: Reserved (Type-Specific in some cases like Retry) +const RESERVED_BITS_LONG_MASK: u8 = 0x0C; const RESERVED_BITS_LONG_SHIFT: u8 = 2; - -const SHORT_SPIN_BIT_MASK: u8 = 0x20; // Bit 5: Spin Bit -const SHORT_RESERVED_BITS_MASK: u8 = 0x18; // Bits 4-3: Reserved +const SHORT_SPIN_BIT_MASK: u8 = 0x20; +const SHORT_RESERVED_BITS_MASK: u8 = 0x18; const SHORT_RESERVED_BITS_SHIFT: u8 = 3; -const SHORT_KEY_PHASE_BIT_MASK: u8 = 0x04; // Bit 2: Key Phase +const SHORT_KEY_PHASE_BIT_MASK: u8 = 0x04; +const PN_LENGTH_BITS_MASK: u8 = 0x03; -const PN_LENGTH_BITS_MASK: u8 = 0x03; // Bits 1-0: Encoded Packet Number Length (actual_length - 1) +#[cfg(feature = "serde")] +use serde_bytes::{ByteBuf, Bytes}; /// Error type for safe getter/setter operations on `QuicHdr`. #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -48,6 +45,49 @@ impl fmt::Display for QuicHdrError { } } +#[cfg(feature = "serde")] +mod cid_serde_helpers { + use core::fmt; + use serde::de::Expected; + + pub struct ExpectedCidBytesInfo { + pub len: u8, + } + impl fmt::Display for ExpectedCidBytesInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CID bytes for explicit length {}", self.len) + } + } + + impl Expected for ExpectedCidBytesInfo { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, formatter) + } + } + + pub struct LengthExceedsMaxError { + pub value: usize, + pub max: usize, + pub field_name: &'static str, + } + + impl fmt::Display for LengthExceedsMaxError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} {} exceeds max {}", + self.field_name, self.value, self.max + ) + } + } + + impl Expected for LengthExceedsMaxError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, formatter) + } + } +} + /// Macro to implement common functionality for Connection ID wrapper structs. macro_rules! impl_cid_common { ($t:ident, $doc_name:literal, $with_len_on_wire:tt) => { @@ -121,9 +161,8 @@ macro_rules! impl_cid_common { } impl $t { - pub const LEN: usize = core::mem::size_of::(); - + /// Construct a new (empty) ID – all bytes zero, length 0. /// /// # Returns @@ -237,33 +276,20 @@ macro_rules! impl_cid_common { } #[cfg(feature = "serde")] - mod serde_cid_impl { - use super::*; + const _: () = { + // Create a new scope for each expansion use serde::{ de::{self, Visitor}, ser::SerializeStruct, Deserializer, Serializer, }; - - impl serde::Serialize for $t { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - if $with_len_on_wire { - let mut st = serializer.serialize_struct(stringify!($t), 2)?; - st.serialize_field("len", &self.len)?; - st.serialize_field("bytes", &serde_bytes::Bytes::new(self.as_slice()))?; - st.end() - } else { - serializer.serialize_bytes(self.as_slice()) - } - } - } + // Assumes `use serde_bytes::{ByteBuf, Bytes};` is at the top of the file. + // Assumes helper structs are in `crate::quic::cid_serde_helpers` struct CidVisitor; impl<'de> Visitor<'de> for CidVisitor { type Value = $t; + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { if $with_len_on_wire { write!(f, "length-prefixed QUIC Connection-ID (len, bytes)") @@ -279,26 +305,29 @@ macro_rules! impl_cid_common { let len: u8 = seq.next_element()?.ok_or_else(|| { de::Error::invalid_length(0, &"Missing CID len in sequence") })?; - let bytes_buf: serde_bytes::ByteBuf = seq.next_element()?.ok_or_else(|| { + let bytes_buf: ByteBuf = seq.next_element()?.ok_or_else(|| { de::Error::invalid_length(1, &"Missing CID bytes in sequence") })?; if bytes_buf.len() != len as usize { return Err(de::Error::invalid_value( de::Unexpected::Bytes(bytes_buf.as_ref()), - &format!("CID bytes for explicit length {}", len).as_ref(), + &crate::quic::cid_serde_helpers::ExpectedCidBytesInfo { len }, )); } if len > QUIC_MAX_CID_LEN as u8 { return Err(de::Error::invalid_length( len as usize, - &format!("CID length {} exceeds max {}", len, QUIC_MAX_CID_LEN) - .as_ref(), + &crate::quic::cid_serde_helpers::LengthExceedsMaxError { + value: len as usize, + max: QUIC_MAX_CID_LEN, + field_name: "CID length", + }, )); } let mut id = $t::new(); id.set(bytes_buf.as_ref()); - id.len = len; // Ensure `len` is set correctly after `set` + id.len = len; Ok(id) } @@ -309,8 +338,11 @@ macro_rules! impl_cid_common { if v.len() > QUIC_MAX_CID_LEN { return Err(de::Error::invalid_length( v.len(), - &format!("CID length {} exceeds max {}", v.len(), QUIC_MAX_CID_LEN) - .as_ref(), + &crate::quic::cid_serde_helpers::LengthExceedsMaxError { + value: v.len(), + max: QUIC_MAX_CID_LEN, + field_name: "CID length", + }, )); } let mut id = $t::new(); @@ -319,21 +351,35 @@ macro_rules! impl_cid_common { } } + impl serde::Serialize for $t { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if $with_len_on_wire { + let mut st = serializer.serialize_struct(stringify!($t), 2)?; + st.serialize_field("len", &self.len)?; + st.serialize_field("bytes", &Bytes::new(self.as_slice()))?; + st.end() + } else { + serializer.serialize_bytes(self.as_slice()) + } + } + } + impl<'de> serde::Deserialize<'de> for $t { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { if $with_len_on_wire { - // For length-prefixed, expect a sequence of (len, bytes) deserializer.deserialize_tuple(2, CidVisitor) } else { - // For raw bytes, expect just the bytes deserializer.deserialize_bytes(CidVisitor) } } } - } + }; }; } @@ -393,9 +439,8 @@ pub struct QuicHdrLong { pub src: QuicSrcConnLong, } impl QuicHdrLong { - pub const LEN: usize = core::mem::size_of::(); - + /// The minimum length of a Long Header on the wire, consisting of: /// 1 (First Byte, part of `QuicHdr`) + 4 (Version) + 1 (DCID Len Byte) + 1 (SCID Len Byte) = 7 bytes. /// This does not include any actual CID data bytes. @@ -473,11 +518,10 @@ pub struct QuicHdrShort { /// `QuicDstConnShort` internally tracks its `len` for `as_slice` but this `len` /// is not part of the on-wire format for the Short Header DCID itself. pub dst: QuicDstConnShort, - // No Source CID field in QUIC v1 Short Headers on the wire. } impl QuicHdrShort { pub const LEN: usize = core::mem::size_of::(); - + /// The minimum length of a Short Header on the wire, consisting of: /// 1 (First Byte, part of `QuicHdr`) + 0 (DCID bytes, if DCID len is 0) = 1 byte. pub const MIN_LEN_ON_WIRE: usize = 1; @@ -507,16 +551,16 @@ pub union QuicHdrUn { /// Data for a Short Header. pub short: QuicHdrShort, } + impl Default for QuicHdrUn { fn default() -> Self { Self { - long: QuicHdrLong::default(), // Default to long for safety, though QuicHdr::new manages this. + long: QuicHdrLong::default(), } } } impl fmt::Debug for QuicHdrUn { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Actual data is debugged via QuicHdr, which knows the active variant. f.write_str("QuicHdrUn { ... }") } } @@ -759,13 +803,12 @@ pub enum QuicHeaderType { pub struct QuicHdr { first_byte: u8, inner: QuicHdrUn, - header_type: QuicHeaderType, // Logical field, not on wire. Crucial for interpretation. + header_type: QuicHeaderType, } impl QuicHdr { - pub const LEN: usize = core::mem::size_of::(); - + /// Minimum on-wire size of a QUIC Long Header (1 (flags/type) + 4 (version) + 1 (DCIL byte) + 1 (SCIL byte) = 7 bytes), /// This excludes any actual CID data bytes. pub const MIN_LONG_HDR_LEN_ON_WIRE: usize = QuicHdrLong::MIN_LEN_ON_WIRE; @@ -801,9 +844,8 @@ impl QuicHdr { // dc_id_len captured here let first_byte = FIXED_BIT_MASK; // Set Fixed bit (Form bit is 0) let mut short_data = QuicHdrShort::default(); - // Initialize the length of the dst CID within short_data if needed, - // though `set_dc_id` will manage this. - // The dc_id_len from header_type is the primary source of truth for effective length. + // The dc_id_len from header_type is the primary source of truth + // for effective length. short_data.dst.len = cmp::min(dc_id_len, QUIC_MAX_CID_LEN as u8); Self { first_byte, @@ -843,7 +885,8 @@ impl QuicHdr { // via set_header_type() to match, which also reinitializes self.inner. } - /// Checks if the Header Form bit (most significant bit of `first_byte`) indicates a Long Header. + /// Checks if the Header Form bit (the most significant bit of `first_byte`) + /// indicates a Long Header. /// /// # Returns /// `true` if bit 7 is 1 (Long Header), `false` if 0 (Short Header). @@ -952,7 +995,7 @@ impl QuicHdr { #[inline] pub fn short_reserved_bits(&self) -> Result { if !self.is_long_header() { - // Safety: !is_long_header() ensures conditions for unchecked_short_reserved_bits are met. + // Safety: !is_long_header() ensures conditions are met. Ok(unsafe { self.unchecked_short_reserved_bits() }) } else { Err(QuicHdrError::InvalidHeaderForm) @@ -1133,7 +1176,7 @@ impl QuicHdr { #[inline] pub fn set_long_packet_type(&mut self, lptype: u8) -> Result<(), QuicHdrError> { if self.is_long_header() { - // Safety: `is_long_header()` check ensures conditions for `unchecked_set_long_packet_type` are met. + // Safety: `is_long_header()` check ensures conditions for are met. unsafe { self.unchecked_set_long_packet_type(lptype) }; Ok(()) } else { @@ -1392,8 +1435,7 @@ impl QuicHdr { pub fn set_header_type(&mut self, new_type: QuicHeaderType) { let other_bits = self.first_byte & !(HEADER_FORM_BIT | FIXED_BIT_MASK); // Safety: This method correctly manages union transitions and first_byte consistency. - unsafe { self.unchecked_set_header_type(new_type) }; // This sets self.header_type and Form/Fixed bits in first_byte. - // Re-apply the other bits that were not part of Form/Fixed. + unsafe { self.unchecked_set_header_type(new_type) }; self.first_byte |= other_bits; } } @@ -1702,8 +1744,6 @@ impl QuicHdr { pub unsafe fn unchecked_dc_id(&self) -> &[u8] { let len = self.unchecked_dc_id_effective_len() as usize; match self.header_type { - // Use .bytes[] directly with computed length to avoid as_slice() using its own internal len - // that might not match dc_id_len from QuicHeaderType::QuicShort context. QuicHeaderType::QuicLong => &self.inner.long.dst.bytes[..len], QuicHeaderType::QuicShort { .. } => &self.inner.short.dst.bytes[..len], } @@ -1792,12 +1832,11 @@ impl QuicHdr { /// Caller must repopulate CID data if it needs to be preserved across such a type change. /// Caller should ensure other bits in `first_byte` are appropriate for `new_type`. pub unsafe fn unchecked_set_header_type(&mut self, new_type: QuicHeaderType) { - let current_is_long = self.is_long_header(); // Based on current first_byte + let current_is_long = self.is_long_header(); let new_is_long = match new_type { QuicHeaderType::QuicLong => true, QuicHeaderType::QuicShort { .. } => false, }; - if current_is_long != new_is_long { // Form is changing, reinitialize union and update first_byte's Form bit if new_is_long { @@ -1805,7 +1844,6 @@ impl QuicHdr { long: QuicHdrLong::default(), }; self.first_byte = (self.first_byte | HEADER_FORM_BIT) | FIXED_BIT_MASK; - // Long + Fixed } else { let dc_id_len_for_short = if let QuicHeaderType::QuicShort { dc_id_len } = new_type { @@ -1817,23 +1855,21 @@ impl QuicHdr { short_data.dst.len = cmp::min(dc_id_len_for_short, QUIC_MAX_CID_LEN as u8); self.inner = QuicHdrUn { short: short_data }; self.first_byte = (self.first_byte & !HEADER_FORM_BIT) | FIXED_BIT_MASK; - // Short + Fixed } } else { // Form is not changing, but QuicShort's dc_id_len might be. - // Ensure fixed bit is correct for the form. + // Ensure the fixed bit is correct for the form. if new_is_long { - // And current_is_long is also true self.first_byte |= HEADER_FORM_BIT | FIXED_BIT_MASK; } else { self.first_byte = (self.first_byte & !HEADER_FORM_BIT) | FIXED_BIT_MASK; - // If type is QuicShort, update inner.short.dst.len if dc_id_len changes + // If the type is QuicShort, update inner.short.dst.len if dc_id_len changes if let QuicHeaderType::QuicShort { dc_id_len } = new_type { self.inner.short.dst.len = cmp::min(dc_id_len, QUIC_MAX_CID_LEN as u8); } } } - self.header_type = new_type; // Set the new logical type + self.header_type = new_type; } /// Sets the Header Form bit in `first_byte` without performing structural changes. @@ -1873,6 +1909,7 @@ impl fmt::Debug for QuicHdr { s.field( "first_byte", &format_args!( + // format_args! is core-friendly "{:#04x} (Form: {}, Fixed: {}, TypeSpecific: {:#04x})", self.first_byte, if self.is_long_header() { @@ -1891,7 +1928,6 @@ impl fmt::Debug for QuicHdr { s.field("version", &self.version().ok()); s.field("dc_id", &self.dc_id()); s.field("sc_id", &self.sc_id().ok()); - // For more detail, could print packet_type, pn_len_bits etc. } QuicHeaderType::QuicShort { .. } => { s.field("dc_id", &self.dc_id()); @@ -1906,24 +1942,56 @@ impl fmt::Debug for QuicHdr { #[cfg(feature = "serde")] mod serde_header_impl { use super::*; + use core::fmt; use serde::{ - de::{self, Error as SerdeError, Visitor}, + de::{self, Visitor}, Deserializer, Serializer, }; + struct TruncatedHeaderError { + name: &'static str, + got: usize, + min: usize, + } + impl fmt::Display for TruncatedHeaderError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Truncated {}: got {} bytes, need at least {}", + self.name, self.got, self.min + ) + } + } + + struct HeaderFieldLengthExceedsMaxError { + value: usize, + max: usize, + field_name: &'static str, + } + impl fmt::Display for HeaderFieldLengthExceedsMaxError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} {} from wire exceeds max {}", + self.field_name, self.value, self.max + ) + } + } + impl serde::Serialize for QuicHdr { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // Max possible size: 1 (first_byte) + 4 (version) + 1 (DCIL) + MAX_CID + 1 (SCIL) + MAX_CID - let mut buf = [0u8; 1 + 4 + 1 + QUIC_MAX_CID_LEN + 1 + QUIC_MAX_CID_LEN]; + let mut buf = [0u8; 7 + QUIC_MAX_CID_LEN + QUIC_MAX_CID_LEN]; let mut current_idx = 0usize; buf[current_idx] = self.first_byte; current_idx += 1; match self.header_type { QuicHeaderType::QuicLong => { - let long_hdr = unsafe { &self.inner.long }; // Safe due to header_type check + // Safety: header_type check + let long_hdr = unsafe { &self.inner.long }; buf[current_idx..current_idx + 4].copy_from_slice(&long_hdr.version); current_idx += 4; let dc_len = long_hdr.dst.len() as usize; // Length from QuicDstConnLong @@ -1969,18 +2037,17 @@ mod serde_header_impl { return Err(E::custom("QUIC header cannot be empty")); } let first_byte = v[0]; - let mut current_idx = 1usize; // Start parsing after the first byte + let mut current_idx = 1usize; if (first_byte & HEADER_FORM_BIT) != 0 { // Long Header if v.len() < QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE { // Basic check for minimal parts - return Err(E::custom(format!( - "Truncated long header: got {} bytes, need at least {}", - v.len(), - QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE - ))); + return Err(E::custom(TruncatedHeaderError { + name: "long header", + got: v.len(), + min: QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE, + })); } - // Create with QuicLong type, then populate. first_byte set at the end. let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); // Version if v.len() < current_idx + 4 { @@ -1989,42 +2056,42 @@ mod serde_header_impl { let mut version_bytes = [0u8; 4]; version_bytes.copy_from_slice(&v[current_idx..current_idx + 4]); hdr.set_version(u32::from_be_bytes(version_bytes)) - .map_err(E::custom)?; + .map_err(|e| E::custom(e))?; current_idx += 4; - // DCID if v.len() < current_idx + 1 { return Err(E::custom("Missing DCIL in long header")); } let dc_len_on_wire = v[current_idx] as usize; current_idx += 1; if dc_len_on_wire > QUIC_MAX_CID_LEN { - return Err(E::custom(format!( - "DCID length {} from wire exceeds max {}", - dc_len_on_wire, QUIC_MAX_CID_LEN - ))); + return Err(E::custom(HeaderFieldLengthExceedsMaxError { + value: dc_len_on_wire, + max: QUIC_MAX_CID_LEN, + field_name: "DCID length", + })); } if v.len() < current_idx + dc_len_on_wire { return Err(E::custom("Truncated DCID in long header")); } - hdr.set_dc_id(&v[current_idx..current_idx + dc_len_on_wire]); // This also sets length in .inner.long.dst + hdr.set_dc_id(&v[current_idx..current_idx + dc_len_on_wire]); current_idx += dc_len_on_wire; - // SCID if v.len() < current_idx + 1 { return Err(E::custom("Missing SCIL in long header")); } let sc_len_on_wire = v[current_idx] as usize; current_idx += 1; if sc_len_on_wire > QUIC_MAX_CID_LEN { - return Err(E::custom(format!( - "SCID length {} from wire exceeds max {}", - sc_len_on_wire, QUIC_MAX_CID_LEN - ))); + return Err(E::custom(HeaderFieldLengthExceedsMaxError { + value: sc_len_on_wire, + max: QUIC_MAX_CID_LEN, + field_name: "SCID length", + })); } if v.len() < current_idx + sc_len_on_wire { return Err(E::custom("Truncated SCID in long header")); } hdr.set_sc_id(&v[current_idx..current_idx + sc_len_on_wire]) - .map_err(E::custom)?; + .map_err(|e| E::custom(e))?; // Pass the error that implements Display hdr.set_first_byte(first_byte); // Set the original first_byte Ok(hdr) } else { @@ -2058,6 +2125,8 @@ mod serde_header_impl { mod tests { use super::*; #[cfg(feature = "serde")] + use bincode; + #[cfg(feature = "serde")] use serde_test::{assert_tokens, Token}; #[test] @@ -2089,7 +2158,7 @@ mod tests { assert_eq!(hdr.dc_id(), &dcid_data); assert_eq!(hdr.sc_id_len_on_wire(), Ok(4)); assert_eq!(hdr.sc_id().unwrap(), &scid_data); - // Check internal consistency of CIDs within the QuicHdrLong part + // Check the internal consistency of CIDs within the QuicHdrLong part unsafe { assert_eq!(hdr.inner.long.dst.len(), 8); assert_eq!(hdr.inner.long.dst.as_slice(), &dcid_data); @@ -2107,7 +2176,6 @@ mod tests { }); assert!(!hdr.is_long_header()); assert_eq!(hdr.first_byte() & 0xC0, FIXED_BIT_MASK); // Form bit 0, Fixed bit 1 - assert!(hdr.set_short_spin_bit(true).is_ok()); assert_eq!(hdr.short_spin_bit(), Ok(true)); assert!(hdr.set_short_reserved_bits(0b00).is_ok()); @@ -2156,10 +2224,10 @@ mod tests { let short_first_byte_bits = hdr.first_byte() & 0x3F; hdr.set_header_type(QuicHeaderType::QuicLong); assert!(hdr.is_long_header()); - assert_eq!(hdr.version(), Ok(0)); - assert_eq!(hdr.dc_id_effective_len(), 0); + assert_eq!(hdr.version(), Ok(0)); // Version is reset to default + assert_eq!(hdr.dc_id_effective_len(), 0); // DCID is reset assert_eq!(hdr.dc_id(), &[]); - assert_eq!(hdr.sc_id_len_on_wire(), Ok(0)); + assert_eq!(hdr.sc_id_len_on_wire(), Ok(0)); // SCID is reset assert_eq!(hdr.first_byte() & HEADER_FORM_BIT, HEADER_FORM_BIT); assert_eq!(hdr.first_byte() & FIXED_BIT_MASK, FIXED_BIT_MASK); assert_eq!(hdr.first_byte() & 0x3F, short_first_byte_bits); @@ -2176,7 +2244,10 @@ mod tests { assert!(hdr.set_sc_id(&[0xBB; 4]).is_ok()); let expected_first_byte = hdr.first_byte(); // Should be 0xC0 | 0b01 = 0xC1 assert_eq!(expected_first_byte, 0xC1); - let bytes = bincode::serialize(&hdr).expect("Serialization failed"); + + let config = bincode::config::standard(); + let bytes = bincode::serde::encode_to_vec(&hdr, config).expect("Serialization failed"); + assert_eq!(bytes[0], expected_first_byte); assert_eq!(&bytes[1..5], &0x01020304u32.to_be_bytes()); // Version assert_eq!(bytes[5], 8); // DCIL @@ -2184,7 +2255,9 @@ mod tests { assert_eq!(bytes[14], 4); // SCIL assert_eq!(&bytes[15..19], &[0xBB; 4]); // SCID assert_eq!(bytes.len(), 19); // Total length - let de: QuicHdr = bincode::deserialize(&bytes).expect("Deserialization failed"); + let (de, len): (QuicHdr, usize) = + bincode::serde::decode_from_slice(&bytes, config).expect("Deserialization failed"); + assert_eq!(len, bytes.len()); assert_eq!(de.first_byte(), expected_first_byte); assert!(de.is_long_header()); assert_eq!(de.header_type(), QuicHeaderType::QuicLong); // Deserializer sets this @@ -2205,15 +2278,18 @@ mod tests { let mut hdr = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: dcid_data.len() as u8, }); - hdr.set_first_byte(0x40 | SHORT_SPIN_BIT_MASK | SHORT_KEY_PHASE_BIT_MASK | 0b00); + hdr.set_first_byte(0x40 | SHORT_SPIN_BIT_MASK | SHORT_KEY_PHASE_BIT_MASK | 0b00); // Fixed | Spin | KeyPhase | PNLEN=1 (00) hdr.set_dc_id(&dcid_data); let expected_first_byte = hdr.first_byte(); - assert_eq!(expected_first_byte, 0x64); - let bytes = bincode::serialize(&hdr).expect("Serialization failed"); + assert_eq!(expected_first_byte, 0x40 | 0x20 | 0x04 | 0b00); // 0x64 + let config = bincode::config::standard(); + let bytes = bincode::serde::encode_to_vec(&hdr, config).expect("Serialization failed"); assert_eq!(bytes[0], expected_first_byte); assert_eq!(&bytes[1..], &dcid_data); // DCID directly follows assert_eq!(bytes.len(), 1 + dcid_data.len()); - let de: QuicHdr = bincode::deserialize(&bytes).expect("Deserialization failed"); + let (de, len): (QuicHdr, usize) = + bincode::serde::decode_from_slice(&bytes, config).expect("Deserialization failed"); + assert_eq!(len, bytes.len()); assert_eq!(de.first_byte(), expected_first_byte); assert!(!de.is_long_header()); if let QuicHeaderType::QuicShort { dc_id_len } = de.header_type() { @@ -2224,7 +2300,7 @@ mod tests { assert_eq!(de.dc_id_effective_len(), dcid_data.len() as u8); assert_eq!(de.dc_id(), &dcid_data); assert_eq!(de.short_spin_bit(), Ok(true)); - assert_eq!(de.short_reserved_bits(), Ok(0b00)); + assert_eq!(de.short_reserved_bits(), Ok(0b00)); // Reserved bits are 00 assert_eq!(de.short_key_phase(), Ok(true)); assert_eq!(de.short_pn_length_bits(), Ok(0b00)); // PNLEN=1 } @@ -2337,11 +2413,11 @@ mod tests { assert_eq!( current_offset, 1 // first_byte - + 4 // version - + 1 // dcil - + 8 // dcid - + 1 // scil - + 8 // scid + + 4 // version + + 1 // dcil + + 8 // dcid + + 1 // scil + + 8 // scid ); } @@ -2374,7 +2450,7 @@ mod tests { let dcid_data_from_packet = &packet_bytes[current_offset..current_offset + contextual_dcid_len as usize]; hdr.set_dc_id(dcid_data_from_packet); // This sets data and updates internal length fields - current_offset += contextual_dcid_len as usize; // <<< THIS LINE WAS THE FIX (uncommented and placed correctly) + current_offset += contextual_dcid_len as usize; assert!(!hdr.is_long_header()); // Confirmed by first_byte set earlier assert_eq!(hdr.fixed_bit(), 1); assert_eq!(hdr.short_spin_bit(), Ok(false)); @@ -2390,10 +2466,7 @@ mod tests { hdr.dc_id_len_on_wire(), Err(QuicHdrError::InvalidHeaderForm) ); - assert_eq!( - current_offset, - 1 /* first_byte */ + contextual_dcid_len as usize /* dcid */ - ); + assert_eq!(current_offset, 1 + contextual_dcid_len as usize); } #[test] From 438cef82f3dbb89695e0690b0945084258aef76d Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Fri, 13 Jun 2025 13:52:53 -0500 Subject: [PATCH 04/14] check-in --- src/quic.rs | 335 ++++++++++++++----- tests/quic_hdr_integration.rs | 602 +++++++++++++++++++++++++--------- 2 files changed, 712 insertions(+), 225 deletions(-) diff --git a/src/quic.rs b/src/quic.rs index b56defc..22c6075 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -5,7 +5,7 @@ //! Designed for use inside eBPF (`aya`) programs → `#![no_std]`, //! fixed‑capacity buffers, no heap, packed layouts. -use core::{cmp, fmt, hash, ptr}; +use core::{cmp, convert::TryFrom, fmt, hash, ptr}; pub const QUIC_MAX_CID_LEN: usize = 20; const HEADER_FORM_BIT: u8 = 0x80; @@ -30,6 +30,8 @@ pub enum QuicHdrError { InvalidHeaderForm, /// Provided length for a field (e.g. CID) is invalid or exceeds maximum allowed. InvalidLength, + /// Invalid QUIC packet type bits encountered during parsing. + InvalidPacketTypeBits, } impl fmt::Display for QuicHdrError { @@ -41,10 +43,49 @@ impl fmt::Display for QuicHdrError { QuicHdrError::InvalidLength => { write!(f, "Invalid length provided for a QUIC header field") } + QuicHdrError::InvalidPacketTypeBits => { + write!(f, "Invalid packet type bits for QUIC header") + } } } } +/// Represents different types of QUIC packets. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[repr(u8)] +pub enum QuicPacketType { + Initial = 0x00, + ZeroRTT = 0x01, + Handshake = 0x02, + Retry = 0x03, + // OneRTT for conceptual clarity, though not a direct mapping from packet type bits. + // Short headers are identified by header form, not these type bits. +} + +/// Represents QUIC transport errors. +/// See RFC 9000, Section 22. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[repr(u16)] +pub enum QuicTransportError { + NoError = 0x0, + InternalError = 0x1, + ConnectionRefused = 0x2, + FlowControlError = 0x3, + StreamLimitError = 0x4, + StreamStateError = 0x5, + FinalSizeError = 0x6, + FrameFormatError = 0x7, + TransportParameterError = 0x8, + ConnectionIdLimitError = 0x9, + ProtocolViolation = 0xA, + InvalidToken = 0xB, + ApplicationError = 0xC, + CryptoBufferExceeded = 0xD, + KeyUpdateError = 0xE, + AeadLimitReached = 0xF, + NoViablePath = 0x10, +} + #[cfg(feature = "serde")] mod cid_serde_helpers { use core::fmt; @@ -110,7 +151,7 @@ macro_rules! impl_cid_common { /// ``` /// // This example is generic. See specific types like QuicDstConnLong or QuicDstConnShort /// // for more tailored examples. - /// use network_types::quic::{QuicDstConnLong, QUIC_MAX_CID_LEN}; // Using QuicDstConnLong as an example + /// use network_types::quic::{QuicDstConnLong, QUIC_MAX_CID_LEN}; /// /// // Create a new, empty CID /// let mut cid = QuicDstConnLong::new(); @@ -131,7 +172,7 @@ macro_rules! impl_cid_common { /// # #[repr(C, packed)] struct MyPacketPart { a_cid: QuicDstConnLong } /// # let base_ptr: *const MyPacketPart = ptr::null(); // Ptr to packet data part /// // let cid_from_packet = unsafe { ptr::read_volatile(&((*base_ptr).a_cid)) }; - /// // assert!(cid_from_packet.len() <= QUIC_MAX_CID_LEN as u8); + /// // assert!(cid_from_packet.len() <= network_types::quic::QUIC_MAX_CID_LEN as u8); /// // Process `cid_from_packet.as_slice()`... /// // ``` /// // @@ -139,7 +180,7 @@ macro_rules! impl_cid_common { /// // You would determine 'known_cid_len' from context, read 'known_cid_len' bytes, /// // then use `cid.set()` to populate a stack instance of QuicDstConnShort. /// // ```no_run - /// # use network_types::quic::{QuicDstConnShort}; + /// # use network_types::quic::QuicDstConnShort; /// # let packet_cid_bytes_ptr: *const u8 = core::ptr::null(); // Ptr to raw CID bytes in packet /// # let known_cid_len: usize = 8; // Length from eBPF map or connection state /// let mut my_cid_short = QuicDstConnShort::new(); @@ -151,8 +192,7 @@ macro_rules! impl_cid_common { /// my_cid_short.set(&cid_buffer[..known_cid_len]); /// // assert_eq!(my_cid_short.len(), known_cid_len as u8); /// } - /// // ``` - /// ``` + /// // ``` /// ``` #[repr(C, packed)] #[derive(Copy, Clone)] pub struct $t { @@ -327,7 +367,7 @@ macro_rules! impl_cid_common { } let mut id = $t::new(); id.set(bytes_buf.as_ref()); - id.len = len; + id.len = len; // Ensure length is set after data. Ok(id) } @@ -373,6 +413,8 @@ macro_rules! impl_cid_common { D: Deserializer<'de>, { if $with_len_on_wire { + // For tuple, it means we expect fixed structure [len, bytes_obj] + // This matches how serialize_struct behaves for tuple-like struct. deserializer.deserialize_tuple(2, CidVisitor) } else { deserializer.deserialize_bytes(CidVisitor) @@ -579,24 +621,51 @@ pub enum QuicHeaderType { QuicShort { dc_id_len: u8 }, } -/// Represents a QUIC packet header, capable of handling both Long and Short forms -/// with variable-length Connection IDs. +/// Represents a QUIC header. +/// +/// This struct is intended to provide access to QUIC header fields. +/// The actual parsing and interpretation of variable-length fields (like CIDs, +/// Packet Number, Token, Length) often require sequential reading and context. /// -/// The `header_type` field is crucial for interpreting the `inner` union correctly -/// and for proper serialization/deserialization, especially for Short Headers where -/// the Destination CID length is not on the wire. +/// For Long Headers (RFC 9000, Section 17.2): +/// +-+-+-+-+-+-+-+-+ +/// |1|1|T T|X X X X| Header Form (1), Fixed Bit (1), Long Packet Type (2), Type-Specific (4) +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Version (32) | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | DCID Len (8) | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Destination Connection ID (0..160) ... +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | SCID Len (8) | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Source Connection ID (0..160) ... +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// ... Type-specific fields (e.g., Token Length, Token for Initial; Length for others) ... +/// ... Packet Number (8, 16, 24, or 32 bits) ... +/// +/// For Short Headers (RFC 9000, Section 17.3): +/// +-+-+-+-+-+-+-+-+ +/// |0|1|S|R R|K K|P P| Header Form (0), Fixed Bit (1), Spin Bit (S), Reserved (R R), Key Phase (K K), Packet Number Length (P P) +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Destination Connection ID (0..160) ... -> Optional, length implicit from context +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Packet Number (8, 16, 24, or 32 bits) ... +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Protected Payload (*) ... +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// # Examples /// /// ## Creating and using a Long Header /// ``` -/// use network_types::quic::{QuicHdr, QuicHeaderType, QUIC_MAX_CID_LEN}; +/// use network_types::quic::{QuicHdr, QuicHeaderType, QuicPacketType, QUIC_MAX_CID_LEN}; /// /// // Create a new Long Header (e.g., for an Initial packet) /// let mut long_hdr = QuicHdr::new(QuicHeaderType::QuicLong); /// /// // Set Long Header specific fields -/// assert!(long_hdr.set_long_packet_type(0b00).is_ok()); // Initial packet type +/// assert!(long_hdr.set_long_packet_type(QuicPacketType::Initial).is_ok()); /// assert!(long_hdr.set_version(0x00000001).is_ok()); // QUIC v1 /// assert!(long_hdr.set_packet_number_length_long(4).is_ok()); // 4-byte packet number /// @@ -606,7 +675,7 @@ pub enum QuicHeaderType { /// assert!(long_hdr.set_sc_id(&scid_data).is_ok()); // Sets SCID and its length (4) /// /// assert!(long_hdr.is_long_header()); -/// assert_eq!(long_hdr.long_packet_type(), Ok(0b00)); +/// assert_eq!(long_hdr.long_packet_type(), Ok(QuicPacketType::Initial)); /// assert_eq!(long_hdr.version(), Ok(0x00000001)); /// assert_eq!(long_hdr.dc_id(), &dcid_data); /// assert_eq!(long_hdr.sc_id().unwrap(), &scid_data); @@ -657,7 +726,7 @@ pub enum QuicHeaderType { /// use network_types::ip::Ipv4Hdr; // Assuming IPv4 for simplicity /// // Placeholder for UDP header length if not using a full UdpHdr type /// const UDP_HDR_LEN: usize = 8; -/// use network_types::quic::{QuicHdr, QuicHeaderType, QUIC_MAX_CID_LEN, QuicHdrError}; +/// use network_types::quic::{QuicHdr, QuicHeaderType, QUIC_MAX_CID_LEN, QuicHdrError, QuicPacketType}; /// /// fn handle_quic_packet(ctx: &TcContext) -> Result { /// let data_start = ctx.data(); @@ -739,7 +808,7 @@ pub enum QuicHeaderType { /// // Long header successfully parsed into hdr_on_stack /// if let Ok(pkt_type) = hdr_on_stack.long_packet_type() { /// aya_ebpf_debug!(ctx, "QUIC Long: Type={}, Ver={}, DCID_len={}, SCID_len={}", -/// pkt_type, hdr_on_stack.version().unwrap_or(0), dcil, scil); +/// pkt_type as u8, hdr_on_stack.version().unwrap_or(0), dcil, scil); /// } /// } else { // Short Header /// // For Short Headers, DCID length must be known from context (e.g., connection tracking via eBPF map) @@ -882,7 +951,7 @@ impl QuicHdr { self.first_byte = b; // Developer note: After calling this, ensure self.header_type is still valid. // For example, if 'b' flips the HEADER_FORM_BIT, self.header_type should be updated - // via set_header_type() to match, which also reinitializes self.inner. + // via set_header_type() to match, which also reinitialized self.inner. } /// Checks if the Header Form bit (the most significant bit of `first_byte`) @@ -906,20 +975,23 @@ impl QuicHdr { } /// Gets the Long Packet Type (bits 5-4 of `first_byte`) if this is a Long Header. - /// Common types for QUIC v1 (RFC 9000) are: - /// * `0b00` (0): Initial - /// * `0b01` (1): 0-RTT - /// * `0b10` (2): Handshake - /// * `0b11` (3): Retry /// /// # Returns - /// `Ok(u8)` with the packet type (0-3) if Long Header, - /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. + /// `Ok(QuicPacketType)` with the packet type if Long Header and type is valid, + /// `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header, + /// `Err(QuicHdrError::InvalidPacketTypeBits)` if type bits are unrecognized. #[inline] - pub fn long_packet_type(&self) -> Result { + pub fn long_packet_type(&self) -> Result { if self.is_long_header() { - // Safety: is_long_header() ensures conditions for unchecked_long_packet_type are met. - Ok(unsafe { self.unchecked_long_packet_type() }) + // Safety: is_long_header() ensures conditions for unchecked_long_packet_type_bits are met. + let type_bits = unsafe { self.unchecked_long_packet_type_bits() }; + match type_bits { + 0x00 => Ok(QuicPacketType::Initial), + 0x01 => Ok(QuicPacketType::ZeroRTT), + 0x02 => Ok(QuicPacketType::Handshake), + 0x03 => Ok(QuicPacketType::Retry), + _ => Err(QuicHdrError::InvalidPacketTypeBits), // Should not happen with 2 bits + } } else { Err(QuicHdrError::InvalidHeaderForm) } @@ -1168,16 +1240,16 @@ impl QuicHdr { /// Sets the Long Packet Type (bits 5-4 of `first_byte`) if this is a Long Header. /// /// # Parameters - /// * `lptype`: The Long Packet Type (0-3). Input is masked to 2 bits. + /// * `lptype`: The `QuicPacketType` enum value. /// /// # Returns /// `Ok(())` if the operation is applicable and successful, /// `Err(QuicHdrError::InvalidHeaderForm)` if called on a Short Header. #[inline] - pub fn set_long_packet_type(&mut self, lptype: u8) -> Result<(), QuicHdrError> { + pub fn set_long_packet_type(&mut self, lptype: QuicPacketType) -> Result<(), QuicHdrError> { if self.is_long_header() { // Safety: `is_long_header()` check ensures conditions for are met. - unsafe { self.unchecked_set_long_packet_type(lptype) }; + unsafe { self.unchecked_set_long_packet_type_bits(lptype as u8) }; Ok(()) } else { Err(QuicHdrError::InvalidHeaderForm) @@ -1438,33 +1510,53 @@ impl QuicHdr { unsafe { self.unchecked_set_header_type(new_type) }; self.first_byte |= other_bits; } + + /// Parses the Connection ID Length byte. + /// + /// In QUIC Long Headers, the DCID Len and SCID Len bytes directly specify the length + /// of their respective Connection IDs. This function validates that the encoded length + /// does not exceed the maximum allowed Connection ID length (`QUIC_MAX_CID_LEN`). + /// + /// # Parameters + /// * `len_byte`: The byte read from the packet that encodes the Connection ID length. + /// + /// # Returns + /// A `Result` containing the parsed Connection ID length as `usize` if valid, + /// or a `QuicHdrError::InvalidLength` if the length exceeds `QUIC_MAX_CID_LEN`. + pub fn parse_cid_len(len_byte: u8) -> Result { + if len_byte > QUIC_MAX_CID_LEN as u8 { + Err(QuicHdrError::InvalidLength) + } else { + Ok(len_byte as usize) + } + } } // Unsafe (unchecked) methods impl QuicHdr { - /// Gets the Long Packet Type from `first_byte` without checking a header form. + /// Gets the Long Packet Type bits from `first_byte` without checking a header form. /// /// # Returns - /// The Long Packet Type value (0-3). + /// The Long Packet Type bits value (0-3). /// /// # Safety /// Caller must ensure `self.is_long_header()` is true. #[inline] - pub unsafe fn unchecked_long_packet_type(&self) -> u8 { + unsafe fn unchecked_long_packet_type_bits(&self) -> u8 { (self.first_byte & LONG_PACKET_TYPE_MASK) >> LONG_PACKET_TYPE_SHIFT } - /// Sets the Long Packet Type in `first_byte` without checking header form. + /// Sets the Long Packet Type bits in `first_byte` without checking header form. /// /// # Parameters - /// * `lptype`: The Long Packet Type (0-3). + /// * `lptype_bits`: The Long Packet Type bits (0-3). /// /// # Safety /// Caller must ensure `self.is_long_header()` is true. #[inline] - pub unsafe fn unchecked_set_long_packet_type(&mut self, lptype: u8) { + unsafe fn unchecked_set_long_packet_type_bits(&mut self, lptype_bits: u8) { self.first_byte = (self.first_byte & !LONG_PACKET_TYPE_MASK) - | ((lptype & 0x03) << LONG_PACKET_TYPE_SHIFT); + | ((lptype_bits & 0x03) << LONG_PACKET_TYPE_SHIFT); } /// Gets the Reserved Bits (Long Header) from `first_byte` without checking header form. @@ -1475,7 +1567,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `self.is_long_header()` is true. #[inline] - pub unsafe fn unchecked_reserved_bits_long(&self) -> u8 { + unsafe fn unchecked_reserved_bits_long(&self) -> u8 { (self.first_byte & RESERVED_BITS_LONG_MASK) >> RESERVED_BITS_LONG_SHIFT } @@ -1487,7 +1579,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `self.is_long_header()` is true. #[inline] - pub unsafe fn unchecked_set_reserved_bits_long(&mut self, val: u8) { + unsafe fn unchecked_set_reserved_bits_long(&mut self, val: u8) { self.first_byte = (self.first_byte & !RESERVED_BITS_LONG_MASK) | ((val & 0x03) << RESERVED_BITS_LONG_SHIFT); } @@ -1500,7 +1592,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `self.is_long_header()` is true and the packet type includes a PN. #[inline] - pub unsafe fn unchecked_pn_length_bits_long(&self) -> u8 { + unsafe fn unchecked_pn_length_bits_long(&self) -> u8 { self.first_byte & PN_LENGTH_BITS_MASK } @@ -1512,7 +1604,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `self.is_long_header()` is true. #[inline] - pub unsafe fn unchecked_set_pn_length_bits_long(&mut self, val: u8) { + unsafe fn unchecked_set_pn_length_bits_long(&mut self, val: u8) { self.first_byte = (self.first_byte & !PN_LENGTH_BITS_MASK) | (val & PN_LENGTH_BITS_MASK); } @@ -1523,7 +1615,7 @@ impl QuicHdr { /// /// # Safety /// Caller must ensure `self.is_long_header()` is true. - pub unsafe fn unchecked_set_packet_number_length_long(&mut self, len_bytes: usize) { + unsafe fn unchecked_set_packet_number_length_long(&mut self, len_bytes: usize) { let encoded_val = match len_bytes { 1 => 0b00, 2 => 0b01, @@ -1542,7 +1634,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub unsafe fn unchecked_short_spin_bit(&self) -> bool { + unsafe fn unchecked_short_spin_bit(&self) -> bool { (self.first_byte & SHORT_SPIN_BIT_MASK) != 0 } @@ -1554,7 +1646,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub unsafe fn unchecked_set_short_spin_bit(&mut self, spin: bool) { + unsafe fn unchecked_set_short_spin_bit(&mut self, spin: bool) { if spin { self.first_byte |= SHORT_SPIN_BIT_MASK; } else { @@ -1570,7 +1662,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub unsafe fn unchecked_short_reserved_bits(&self) -> u8 { + unsafe fn unchecked_short_reserved_bits(&self) -> u8 { (self.first_byte & SHORT_RESERVED_BITS_MASK) >> SHORT_RESERVED_BITS_SHIFT } @@ -1582,7 +1674,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub unsafe fn unchecked_set_short_reserved_bits(&mut self, val: u8) { + unsafe fn unchecked_set_short_reserved_bits(&mut self, val: u8) { self.first_byte = (self.first_byte & !SHORT_RESERVED_BITS_MASK) | ((val & 0x03) << SHORT_RESERVED_BITS_SHIFT); } @@ -1595,7 +1687,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub unsafe fn unchecked_short_key_phase(&self) -> bool { + unsafe fn unchecked_short_key_phase(&self) -> bool { (self.first_byte & SHORT_KEY_PHASE_BIT_MASK) != 0 } @@ -1607,7 +1699,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub unsafe fn unchecked_set_short_key_phase(&mut self, key_phase: bool) { + unsafe fn unchecked_set_short_key_phase(&mut self, key_phase: bool) { if key_phase { self.first_byte |= SHORT_KEY_PHASE_BIT_MASK; } else { @@ -1623,7 +1715,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub unsafe fn unchecked_short_pn_length_bits(&self) -> u8 { + unsafe fn unchecked_short_pn_length_bits(&self) -> u8 { self.first_byte & PN_LENGTH_BITS_MASK } @@ -1635,7 +1727,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `!self.is_long_header()` is true. #[inline] - pub unsafe fn unchecked_set_short_pn_length_bits(&mut self, val: u8) { + unsafe fn unchecked_set_short_pn_length_bits(&mut self, val: u8) { self.first_byte = (self.first_byte & !PN_LENGTH_BITS_MASK) | (val & PN_LENGTH_BITS_MASK); } @@ -1646,7 +1738,7 @@ impl QuicHdr { /// /// # Safety /// Caller must ensure `!self.is_long_header()` is true. - pub unsafe fn unchecked_set_short_packet_number_length(&mut self, len_bytes: usize) { + unsafe fn unchecked_set_short_packet_number_length(&mut self, len_bytes: usize) { let encoded_val = match len_bytes { 1 => 0b00, 2 => 0b01, @@ -1665,7 +1757,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. #[inline] - pub unsafe fn unchecked_version(&self) -> u32 { + unsafe fn unchecked_version(&self) -> u32 { self.inner.long.version() } @@ -1677,7 +1769,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. #[inline] - pub unsafe fn unchecked_set_version(&mut self, v: u32) { + unsafe fn unchecked_set_version(&mut self, v: u32) { self.inner.long.set_version(v); } @@ -1689,7 +1781,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. #[inline] - pub unsafe fn unchecked_dc_id_len_on_wire(&self) -> u8 { + unsafe fn unchecked_dc_id_len_on_wire(&self) -> u8 { self.inner.long.dst.len() } @@ -1702,7 +1794,7 @@ impl QuicHdr { /// Caller must ensure `self.header_type` is consistent with the active union variant /// and that the corresponding variant's CID `len` field or `dc_id_len` context is correctly set. #[inline] - pub unsafe fn unchecked_dc_id_effective_len(&self) -> u8 { + unsafe fn unchecked_dc_id_effective_len(&self) -> u8 { match self.header_type { QuicHeaderType::QuicLong => self.inner.long.dst.len(), QuicHeaderType::QuicShort { dc_id_len } => dc_id_len, @@ -1717,7 +1809,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `self.header_type` is consistent with the active union variant. /// This updates lengths in both `header_type` (for Short) and the CID struct itself. - pub unsafe fn unchecked_set_dc_id_effective_len(&mut self, new_len: u8) { + unsafe fn unchecked_set_dc_id_effective_len(&mut self, new_len: u8) { let validated_len = cmp::min(new_len, QUIC_MAX_CID_LEN as u8); match &mut self.header_type { QuicHeaderType::QuicLong => { @@ -1741,7 +1833,7 @@ impl QuicHdr { /// Caller must ensure `self.header_type` is consistent with the active union variant and /// `unchecked_dc_id_effective_len()` returns a valid length for the active variant's buffer. #[inline] - pub unsafe fn unchecked_dc_id(&self) -> &[u8] { + unsafe fn unchecked_dc_id(&self) -> &[u8] { let len = self.unchecked_dc_id_effective_len() as usize; match self.header_type { QuicHeaderType::QuicLong => &self.inner.long.dst.bytes[..len], @@ -1757,7 +1849,7 @@ impl QuicHdr { /// # Safety /// Caller must ensure `self.header_type` is consistent with the active union variant. /// This updates lengths in `header_type` (for Short) and the CID struct itself via `set()`. - pub unsafe fn unchecked_set_dc_id(&mut self, data: &[u8]) { + unsafe fn unchecked_set_dc_id(&mut self, data: &[u8]) { match &mut self.header_type { QuicHeaderType::QuicLong => { self.inner.long.dst.set(data); @@ -1779,22 +1871,22 @@ impl QuicHdr { /// # Safety /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. #[inline] - pub unsafe fn unchecked_sc_id_len_on_wire(&self) -> u8 { + unsafe fn unchecked_sc_id_len_on_wire(&self) -> u8 { self.inner.long.src.len() } - /// Sets the on-wire SCID length (Long Header) without checking header form. CID bytes are not changed. + /// Sets the on-wire SCID length (Long Header) without checking a header form. CID bytes are not changed. /// /// # Parameters /// * `len`: The new SCID length. Clamped to `QUIC_MAX_CID_LEN`. /// /// # Safety /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. - pub unsafe fn unchecked_set_sc_id_len_on_wire(&mut self, len: u8) { + unsafe fn unchecked_set_sc_id_len_on_wire(&mut self, len: u8) { self.inner.long.src.len = cmp::min(len, QUIC_MAX_CID_LEN as u8); } - /// Gets a slice to the SCID (Long Header) without checking header form. + /// Gets a slice to the SCID (Long Header) without checking a header form. /// /// # Returns /// A slice to the SCID bytes. @@ -1803,18 +1895,18 @@ impl QuicHdr { /// Caller must ensure `self.header_type` is `QuicLong`, `self.inner.long` is initialized, /// and `self.inner.long.src.len()` is a valid length for `self.inner.long.src.bytes`. #[inline] - pub unsafe fn unchecked_sc_id(&self) -> &[u8] { + unsafe fn unchecked_sc_id(&self) -> &[u8] { self.inner.long.src.as_slice() } - /// Sets the SCID (Long Header) without checking header form. + /// Sets the SCID (Long Header) without checking a header form. /// /// # Parameters /// * `data`: Slice containing the new SCID. Length clamped by `set()`. /// /// # Safety /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. - pub unsafe fn unchecked_set_sc_id(&mut self, data: &[u8]) { + unsafe fn unchecked_set_sc_id(&mut self, data: &[u8]) { self.inner.long.src.set(data); } @@ -1831,7 +1923,7 @@ impl QuicHdr { /// of stale bytes. /// Caller must repopulate CID data if it needs to be preserved across such a type change. /// Caller should ensure other bits in `first_byte` are appropriate for `new_type`. - pub unsafe fn unchecked_set_header_type(&mut self, new_type: QuicHeaderType) { + unsafe fn unchecked_set_header_type(&mut self, new_type: QuicHeaderType) { let current_is_long = self.is_long_header(); let new_is_long = match new_type { QuicHeaderType::QuicLong => true, @@ -1849,7 +1941,7 @@ impl QuicHdr { { dc_id_len } else { - 0 + 0 // Should not happen if new_is_long is false }; let mut short_data = QuicHdrShort::default(); short_data.dst.len = cmp::min(dc_id_len_for_short, QUIC_MAX_CID_LEN as u8); @@ -1860,8 +1952,10 @@ impl QuicHdr { // Form is not changing, but QuicShort's dc_id_len might be. // Ensure the fixed bit is correct for the form. if new_is_long { + // This implies new_type is QuicLong self.first_byte |= HEADER_FORM_BIT | FIXED_BIT_MASK; } else { + // This implies new_type is QuicShort self.first_byte = (self.first_byte & !HEADER_FORM_BIT) | FIXED_BIT_MASK; // If the type is QuicShort, update inner.short.dst.len if dc_id_len changes if let QuicHeaderType::QuicShort { dc_id_len } = new_type { @@ -1882,7 +1976,7 @@ impl QuicHdr { /// the active `inner` union variant are consistent with this change. Prefer using /// the safe `set_header_type` for structural changes. #[inline] - pub unsafe fn unchecked_set_header_form_bit(&mut self, is_long: bool) { + unsafe fn unchecked_set_header_form_bit(&mut self, is_long: bool) { if is_long { self.first_byte |= HEADER_FORM_BIT; } else { @@ -1890,7 +1984,7 @@ impl QuicHdr { } } - /// Sets the Fixed Bit in `first_byte` without checking header form. + /// Sets the Fixed Bit in `first_byte` without checking a header form. /// /// # Parameters /// * `val`: The new value for the Fixed Bit (0 or 1). Input masked to 1 bit. @@ -1898,7 +1992,7 @@ impl QuicHdr { /// # Safety /// This is a direct bitwise operation on `first_byte`. #[inline] - pub unsafe fn unchecked_set_fixed_bit(&mut self, val: u8) { + unsafe fn unchecked_set_fixed_bit(&mut self, val: u8) { self.first_byte = (self.first_byte & !FIXED_BIT_MASK) | ((val & 1) << 6); } } @@ -1928,6 +2022,7 @@ impl fmt::Debug for QuicHdr { s.field("version", &self.version().ok()); s.field("dc_id", &self.dc_id()); s.field("sc_id", &self.sc_id().ok()); + s.field("long_packet_type", &self.long_packet_type().ok()); } QuicHeaderType::QuicShort { .. } => { s.field("dc_id", &self.dc_id()); @@ -1939,6 +2034,27 @@ impl fmt::Debug for QuicHdr { } } +// Implement TryFrom for QuicPacketType to u8 and vice-versa if needed for C interop or matching +impl TryFrom for QuicPacketType { + type Error = QuicHdrError; // Using QuicHdrError for consistency with other methods + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(QuicPacketType::Initial), + 0x01 => Ok(QuicPacketType::ZeroRTT), + 0x02 => Ok(QuicPacketType::Handshake), + 0x03 => Ok(QuicPacketType::Retry), + _ => Err(QuicHdrError::InvalidPacketTypeBits), + } + } +} + +impl From for u8 { + fn from(ptype: QuicPacketType) -> Self { + ptype as u8 + } +} + #[cfg(feature = "serde")] mod serde_header_impl { use super::*; @@ -2011,7 +2127,6 @@ mod serde_header_impl { // self.inner.short.dst.len() should match dc_id_len if consistent. let short_hdr_dst_bytes = unsafe { &self.inner.short.dst.bytes }; // Safe due to header_type let actual_dc_len = cmp::min(dc_id_len as usize, QUIC_MAX_CID_LEN); - if actual_dc_len > 0 { buf[current_idx..current_idx + actual_dc_len] .copy_from_slice(&short_hdr_dst_bytes[..actual_dc_len]); @@ -2140,8 +2255,8 @@ mod tests { let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); assert!(hdr.is_long_header()); assert_eq!(hdr.first_byte() & 0xC0, HEADER_FORM_BIT | FIXED_BIT_MASK); // Form and Fixed bits - assert!(hdr.set_long_packet_type(0b01).is_ok()); // 0-RTT - assert_eq!(hdr.long_packet_type(), Ok(0b01)); + assert!(hdr.set_long_packet_type(QuicPacketType::ZeroRTT).is_ok()); + assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::ZeroRTT)); assert!(hdr.set_reserved_bits_long(0b00).is_ok()); assert_eq!(hdr.reserved_bits_long(), Ok(0b00)); assert!(hdr.set_packet_number_length_long(4).is_ok()); // 4 bytes PN @@ -2266,7 +2381,7 @@ mod tests { assert_eq!(de.dc_id(), &[0xAA; 8]); assert_eq!(de.sc_id_len_on_wire().unwrap(), 4); assert_eq!(de.sc_id().unwrap(), &[0xBB; 4]); - assert_eq!(de.long_packet_type(), Ok(0b00)); + assert_eq!(de.long_packet_type(), Ok(QuicPacketType::Initial)); assert_eq!(de.reserved_bits_long(), Ok(0b00)); assert_eq!(de.pn_length_bits_long(), Ok(0b01)); // PNLEN=2 } @@ -2395,7 +2510,7 @@ mod tests { current_offset += scid_len_on_wire as usize; assert!(hdr.is_long_header()); assert_eq!(hdr.fixed_bit(), 1); - assert_eq!(hdr.long_packet_type(), Ok(0b00)); + assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::Initial)); assert_eq!(hdr.reserved_bits_long(), Ok(0b00)); assert_eq!(hdr.pn_length_bits_long(), Ok(0b01)); assert_eq!(hdr.packet_number_length_long(), Ok(2)); @@ -2542,7 +2657,10 @@ mod tests { .unwrap(); parse_ptr_offset += scid_len_from_pkt as usize; assert!(quic_hdr_on_stack.is_long_header()); - assert_eq!(quic_hdr_on_stack.long_packet_type(), Ok(0b00)); // Initial + assert_eq!( + quic_hdr_on_stack.long_packet_type(), + Ok(QuicPacketType::Initial) + ); assert_eq!(quic_hdr_on_stack.packet_number_length_long(), Ok(2)); assert_eq!(quic_hdr_on_stack.version(), Ok(0x00000001)); assert_eq!( @@ -2620,4 +2738,67 @@ mod tests { panic!("Test logic assumes Short Header based on first byte of test data."); } } + + // New tests for parse_cid_len + #[test] + fn test_quic_hdr_parse_cid_len_valid() { + assert_eq!(QuicHdr::parse_cid_len(0).unwrap(), 0); + assert_eq!(QuicHdr::parse_cid_len(8).unwrap(), 8); + assert_eq!( + QuicHdr::parse_cid_len(QUIC_MAX_CID_LEN as u8).unwrap(), + QUIC_MAX_CID_LEN + ); + } + + #[test] + fn test_quic_hdr_parse_cid_len_invalid_too_long() { + assert_eq!( + QuicHdr::parse_cid_len((QUIC_MAX_CID_LEN + 1) as u8), + Err(QuicHdrError::InvalidLength) + ); + assert_eq!( + QuicHdr::parse_cid_len(255), // Max u8 value, likely > QUIC_MAX_CID_LEN + Err(QuicHdrError::InvalidLength) + ); + } + + // New tests for updated long_packet_type + #[test] + fn test_long_packet_type_enum() { + let mut hdr_initial = QuicHdr::new(QuicHeaderType::QuicLong); + hdr_initial.set_first_byte(0xC0); // Initial + assert_eq!(hdr_initial.long_packet_type(), Ok(QuicPacketType::Initial)); + + let mut hdr_0rtt = QuicHdr::new(QuicHeaderType::QuicLong); + hdr_0rtt.set_first_byte(0xD0); // 0-RTT + assert_eq!(hdr_0rtt.long_packet_type(), Ok(QuicPacketType::ZeroRTT)); + + let mut hdr_handshake = QuicHdr::new(QuicHeaderType::QuicLong); + hdr_handshake.set_first_byte(0xE0); // Handshake + assert_eq!( + hdr_handshake.long_packet_type(), + Ok(QuicPacketType::Handshake) + ); + + let mut hdr_retry = QuicHdr::new(QuicHeaderType::QuicLong); + hdr_retry.set_first_byte(0xF0); // Retry + assert_eq!(hdr_retry.long_packet_type(), Ok(QuicPacketType::Retry)); + } + + #[test] + fn test_long_packet_type_invalid_bits() { + let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); + // Header Form = 1, Fixed Bit = 1, Type Bits = 11 (Retry), Reserved Bits = 11, PN Len = 11 + // This is valid as 0xFF is a Long Header + hdr.set_first_byte(0xFF); + assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::Retry)); + + // Short header, should fail + let mut short_hdr = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: 0 }); + short_hdr.set_first_byte(0x40); + assert_eq!( + short_hdr.long_packet_type(), + Err(QuicHdrError::InvalidHeaderForm) + ); + } } diff --git a/tests/quic_hdr_integration.rs b/tests/quic_hdr_integration.rs index ba18268..aad6656 100644 --- a/tests/quic_hdr_integration.rs +++ b/tests/quic_hdr_integration.rs @@ -1,171 +1,477 @@ -// --------------------------------------------------------------------------- -// tests/quic_hdr_integration.rs -// -// End‑to‑end integration tests for `network-types/src/quic.rs` -// -// • Guarantees the header code compiles in `#![no_std]` mode (because the -// library itself is rebuilt that way for integration tests). -// • Exercises realistic QUIC Initial (long) and 1‑RTT (short) headers. -// • Uses the public kernel‑style signature: -// -// fn parse_quic_header(ctx: &TcContext, parser: &mut Parser) -// -// • Pulls `aya_ebpf::*` exactly like production code. For host builds where -// the real crate is unavailable, a minimal stub is provided automatically. -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- -use aya_ebpf::programs::TcContext; - -use network_types::quic::{QuicHdr, QuicHdrError, QuicHeaderType, QUIC_MAX_CID_LEN}; - -#[derive(Default)] +#![allow(dead_code)] // Allow dead code for elements that are part of the setup + +use network_types::quic::{ + QuicHdr, QuicPacketType, QuicHeaderType, + QUIC_MAX_CID_LEN, // Used in parse_quic_header +}; +use network_types::eth::{EthHdr, EtherType}; +use network_types::ip::{IpProto, Ipv4Hdr}; +use network_types::udp::UdpHdr; + +use aya_ebpf::{ + programs::TcContext, + EbpfContext, // Trait for ctx.load(), used by parser functions + bindings::__sk_buff // For creating TcContext +}; +// core::ptr was removed as it's not used. + +// Constants for QUIC parsing logic, mirrored from src/quic.rs as they are private there. +const HDR_FORM_BIT_TEST: u8 = 0x80; // Not used directly in this file after changes, but kept for reference +const LONG_PACKET_TYPE_MASK_TEST: u8 = 0x30; +const LONG_PACKET_TYPE_SHIFT_TEST: u8 = 4; +const PN_LENGTH_BITS_MASK_TEST: u8 = 0x03; + + +// --- Parser Struct and HeaderType Enum (remains the same) --- +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum HeaderType { + Ethernet, + Ipv4, + // Ipv6, // Not used in current tests, can be re-added if needed + Udp, + Quic, + StopProcessing, + ErrorOccurred, +} + struct Parser { offset: usize, + next_hdr: HeaderType, +} + +impl Parser { + fn new() -> Self { + Parser { + offset: 0, + next_hdr: HeaderType::Ethernet, + } + } } -/// eBPF-facing wrapper that extracts data from the context. -fn parse_quic_header(ctx: &TcContext, parser: &mut Parser) -> Result { - // This unsafe block is the integration point with the eBPF infrastructure. - let data = unsafe { - core::slice::from_raw_parts(ctx.data() as *const u8, ctx.data_end() - ctx.data()) +fn read_quic_varint(ctx: &TcContext, offset: usize) -> Result<(u64, usize), ()> { + // Ensure we can read the first byte to determine the length of the varint. + let read_end = offset + 1; + if read_end > ctx.data_end() { + return Err(()); + } + let first_byte: u8 = unsafe { ctx.load(offset).map_err(|_| ())? }; + + // The length of the varint (1, 2, 4, or 8 bytes) is encoded in the first two bits. + let len = 1 << (first_byte >> 6); + + // Ensure the full varint is within packet bounds before reading it. + let read_end = offset + len; + if read_end > ctx.data_end() { + return Err(()); + } + + // Read the varint value based on its determined length. + let val = match len { + 1 => (first_byte & 0x3F) as u64, + 2 => { + let raw: u16 = unsafe { ctx.load(offset).map_err(|_| ())? }; + (u16::from_be(raw) & 0x3FFF) as u64 + } + 4 => { + let raw: u32 = unsafe { ctx.load(offset).map_err(|_| ())? }; + (u32::from_be(raw) & 0x3FFFFFFF) as u64 + } + 8 => { + let raw: u64 = unsafe { ctx.load(offset).map_err(|_| ())? }; + u64::from_be(raw) & 0x3FFFFFFFFFFFFFFF + } + _ => return Err(()), // Should be unreachable }; - // The actual parsing is delegated to a pure, testable function. - parse_quic_header_logic(data, parser) + + Ok((val, len)) } -/// Pure parsing logic that operates on a byte slice. This is easily testable. -fn parse_quic_header_logic(data: &[u8], parser: &mut Parser) -> Result { - if parser.offset >= data.len() { +fn parse_quic_header(ctx: &TcContext, parser: &mut Parser) -> Result<(), ()> { + let mut current_offset = parser.offset; + + // Boundary check for the first byte of the QUIC header. + if current_offset + 1 > ctx.data_end() { return Err(()); } - let first = data[parser.offset]; - let slice = &data[parser.offset..]; - // Long Header - if first & 0x80 != 0 { - if slice.len() < QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE { + let first_byte: u8 = unsafe { ctx.load(current_offset).map_err(|_| ())? }; + current_offset += 1; + + let is_long_header = (first_byte & HDR_FORM_BIT_TEST) != 0; + + if is_long_header { + // --- Long Header Parsing --- + + // Boundary check for Version (4 bytes). + if current_offset + 4 > ctx.data_end() { return Err(()); } - let mut idx = 1usize; // after first_byte - let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); - hdr.set_first_byte(first); - let ver_bytes = <[u8; 4]>::try_from(&slice[idx..idx + 4]).unwrap(); - hdr.set_version(u32::from_be_bytes(ver_bytes)) - .map_err(|_| ())?; - idx += 4; - let dc_len = slice[idx] as usize; - idx += 1; - if dc_len > QUIC_MAX_CID_LEN || slice.len() < idx + dc_len { + current_offset += 4; // Skip Version + + // Boundary check for DCID Len (1 byte) and DCID. + if current_offset + 1 > ctx.data_end() { return Err(()); } - hdr.set_dc_id(&slice[idx..idx + dc_len]); - idx += dc_len; - let sc_len = slice[idx] as usize; - idx += 1; - if sc_len > QUIC_MAX_CID_LEN || slice.len() < idx + sc_len { + let dcid_len: u8 = unsafe { ctx.load(current_offset).map_err(|_| ())? }; + current_offset += 1; + if current_offset + (dcid_len as usize) > ctx.data_end() { return Err(()); } - hdr.set_sc_id(&slice[idx..idx + sc_len]).map_err(|_| ())?; - idx += sc_len; - parser.offset += idx; - return Ok(hdr); - } - // Short Header - let dcid_len_hint = (slice.len() - 1).min(QUIC_MAX_CID_LEN) as u8; - let dc_len = dcid_len_hint as usize; - let mut hdr = QuicHdr::new(QuicHeaderType::QuicShort { - dc_id_len: dcid_len_hint, - }); - hdr.set_first_byte(first); - if dc_len > 0 { - hdr.set_dc_id(&slice[1..1 + dc_len]); - } - parser.offset += 1 + dc_len; - Ok(hdr) -} + current_offset += dcid_len as usize; // Skip DCID + + // Boundary check for SCID Len (1 byte) and SCID. + if current_offset + 1 > ctx.data_end() { + return Err(()); + } + let scid_len: u8 = unsafe { ctx.load(current_offset).map_err(|_| ())? }; + current_offset += 1; + if current_offset + (scid_len as usize) > ctx.data_end() { + return Err(()); + } + current_offset += scid_len as usize; // Skip SCID + + // For Initial packets, parse Token Length and Token. + let long_packet_type = (first_byte & LONG_PACKET_TYPE_MASK_TEST) >> LONG_PACKET_TYPE_SHIFT_TEST; + if long_packet_type == 0 { // Type 0 -> Initial Packet + let (token_len_val, token_len_bytes) = read_quic_varint(ctx, current_offset)?; + current_offset += token_len_bytes; + if current_offset + (token_len_val as usize) > ctx.data_end() { + return Err(()); + } + current_offset += token_len_val as usize; // Skip Token + } -const LONG_HDR_BYTES: [u8; 19] = [ - 0xC3, // Long, Fixed=1, Type=Initial(0), PNLEN=4 - 0x00, 0x00, 0x00, 0x01, // Version 1 - 0x08, // DCID len = 8 - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // DCID - 0x04, // SCID len = 4 - 0xAA, 0xBB, 0xCC, 0xDD, // SCID -]; - -const SHORT_HDR_BYTES: [u8; 9] = [ - 0x64, // Short, Fixed=1, Spin=1, KP=1, PNLEN=1 - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // DCID (8) -]; - -#[test] -fn long_header_full_parse() { - let mut parser = Parser::default(); - let hdr = parse_quic_header_logic(&LONG_HDR_BYTES, &mut parser).expect("parse failed"); - assert_eq!(parser.offset, LONG_HDR_BYTES.len()); - assert!(hdr.is_long_header()); - assert_eq!(hdr.version(), Ok(0x0000_0001)); - assert_eq!(hdr.long_packet_type(), Ok(0)); - assert_eq!(hdr.packet_number_length_long(), Ok(4)); - assert_eq!(hdr.dc_id_len_on_wire(), Ok(8)); - assert_eq!(hdr.dc_id(), &LONG_HDR_BYTES[6..14]); - assert_eq!(hdr.sc_id_len_on_wire(), Ok(4)); - assert_eq!(hdr.sc_id().unwrap(), &LONG_HDR_BYTES[15..19]); + // Read payload length using our safe varint reader. + let (_payload_len, len_bytes) = read_quic_varint(ctx, current_offset)?; + current_offset += len_bytes; + + // Boundary check for the Packet Number. Its length is in the first byte. + let pn_len = (first_byte & PN_LENGTH_BITS_MASK_TEST) as usize + 1; + if current_offset + pn_len > ctx.data_end() { + return Err(()); + } + current_offset += pn_len; // Skip Packet Number + } else { + // --- Short Header Parsing --- + // For short headers, the DCID length is implicit from the connection context. + // For a stateless parser, we must assume a fixed length. 8 bytes is a common choice. + const SHORT_HEADER_DCID_LEN: usize = 8; + if current_offset + SHORT_HEADER_DCID_LEN > ctx.data_end() { + return Err(()); + } + current_offset += SHORT_HEADER_DCID_LEN; // Skip DCID + + // Packet Number length is also implicit and protected. We assume a max length of 4 bytes. + const SHORT_HEADER_PN_LEN: usize = 4; + if current_offset + SHORT_HEADER_PN_LEN > ctx.data_end() { + return Err(()); + } + current_offset += SHORT_HEADER_PN_LEN; // Skip Packet Number + } + + parser.offset = current_offset; + parser.next_hdr = HeaderType::StopProcessing; + Ok(()) } -#[test] -fn short_header_full_parse() { - let mut parser = Parser::default(); - let hdr = parse_quic_header_logic(&SHORT_HDR_BYTES, &mut parser).expect("parse failed"); - assert_eq!(parser.offset, SHORT_HDR_BYTES.len()); - assert!(!hdr.is_long_header()); - assert_eq!(hdr.short_spin_bit(), Ok(true)); - assert_eq!(hdr.short_key_phase(), Ok(true)); - assert_eq!(hdr.short_packet_number_length(), Ok(1)); - assert_eq!(hdr.dc_id_effective_len(), 8); - assert_eq!(hdr.dc_id(), &SHORT_HDR_BYTES[1..]); +// --- Simplified Layer Parsers for Test Orchestration (using TcContext) --- +fn parse_ethernet_header_dummy(ctx: &TcContext, parser: &mut Parser) -> Result<(), ()> { + let _eth_hdr: EthHdr = ctx.load(parser.offset).map_err(|_| ())?; + parser.offset += EthHdr::LEN; + parser.next_hdr = HeaderType::Ipv4; + Ok(()) } -#[test] -fn roundtrip_long_header_serialize_parse() { - // Build programmatically - let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); - hdr.set_first_byte(0xC3); - hdr.set_version(0x0000_0001).unwrap(); - hdr.set_dc_id(&LONG_HDR_BYTES[6..14]); - hdr.set_sc_id(&LONG_HDR_BYTES[15..19]).unwrap(); - // Manual serialisation (header only) - let mut wire = [0u8; 19]; - let mut idx = 0; - wire[idx] = hdr.first_byte(); - idx += 1; - wire[idx..idx + 4].copy_from_slice(&0x0000_0001u32.to_be_bytes()); - idx += 4; - wire[idx] = 8; - idx += 1; - wire[idx..idx + 8].copy_from_slice(hdr.dc_id()); - idx += 8; - wire[idx] = 4; - idx += 1; - wire[idx..idx + 4].copy_from_slice(hdr.sc_id().unwrap()); - assert_eq!(&wire, &LONG_HDR_BYTES); - // Parse back through the real parser - let mut parser = Parser::default(); - let reparsed = parse_quic_header_logic(&wire, &mut parser).unwrap(); - assert_eq!(reparsed.first_byte(), hdr.first_byte()); - assert_eq!(reparsed.version(), hdr.version()); - assert_eq!(reparsed.dc_id(), hdr.dc_id()); - assert_eq!(reparsed.sc_id().unwrap(), hdr.sc_id().unwrap()); +fn parse_ipv4_header_dummy(ctx: &TcContext, parser: &mut Parser) -> Result<(), ()> { + let ipv4_hdr: Ipv4Hdr = ctx.load(parser.offset).map_err(|_| ())?; + let ip_hdr_len = (ipv4_hdr.vihl & 0x0F) as usize * 4; + if ip_hdr_len < Ipv4Hdr::LEN { return Err(()); } + parser.offset += ip_hdr_len; + parser.next_hdr = HeaderType::Udp; // Assuming UDP next for QUIC + Ok(()) } -#[test] -fn accessor_errors_are_correct() { - let mut parser = Parser::default(); - let long_hdr = parse_quic_header_logic(&LONG_HDR_BYTES, &mut parser).unwrap(); - parser.offset = 0; // reset - let short_hdr = parse_quic_header_logic(&SHORT_HDR_BYTES, &mut parser).unwrap(); - assert_eq!( - long_hdr.short_spin_bit(), - Err(QuicHdrError::InvalidHeaderForm) - ); - assert_eq!(short_hdr.version(), Err(QuicHdrError::InvalidHeaderForm)); +fn parse_udp_header_dummy(ctx: &TcContext, parser: &mut Parser) -> Result<(), ()> { + let _udp_hdr: UdpHdr = ctx.load(parser.offset).map_err(|_| ())?; + parser.offset += UdpHdr::LEN; + parser.next_hdr = HeaderType::Quic; + Ok(()) } + + +#[cfg(test)] +mod tests { + use super::*; + use core::mem::MaybeUninit; + + // --- Packet Building Helper --- + fn build_quic_packet(quic_payload_and_header: &[u8]) -> Vec { + let mut buf = Vec::new(); + let eth = EthHdr { + dst_addr: [0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + src_addr: [0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + ether_type: EtherType::Ipv4.into(), // Use .into() for u16 conversion + }; + // Manual serialization for EthHdr as it might not be Pod + buf.extend_from_slice(ð.dst_addr); // Corrected: was ð.dst_addr + buf.extend_from_slice(ð.src_addr); // Corrected: was ð.src_addr + buf.extend_from_slice(&u16::from(eth.ether_type).to_be_bytes()); + + + let l3_offset = buf.len(); + let mut ipv4 = Ipv4Hdr { + vihl: (4 << 4) | 5, // IPv4, 5 * 32-bit words header length + tos: 0, + tot_len: 0u16.to_be_bytes(), // Placeholder, will be updated + id: 0x1234u16.to_be_bytes(), + frags: 0u16.to_be_bytes(), + ttl: 64, + proto: IpProto::Udp, + check: 0u16.to_be_bytes(), // Checksum placeholder + src_addr: [192, 168, 1, 1].into(), + dst_addr: [192, 168, 1, 2].into(), + }; + let ipv4_hdr_len = (ipv4.vihl & 0x0F) as usize * 4; + // Directly extend with a known-size struct + let ipv4_bytes_slice = unsafe { + core::slice::from_raw_parts(&ipv4 as *const _ as *const u8, ipv4_hdr_len) + }; + buf.extend_from_slice(ipv4_bytes_slice); + + + let l4_offset = buf.len(); + let mut udp = UdpHdr { + src: 12345u16.to_be_bytes(), // Corrected: was source + dst: 443u16.to_be_bytes(), // Corrected: was dest + len: 0u16.to_be_bytes(), // Placeholder + check: 0u16.to_be_bytes(), // Checksum placeholder + }; + let udp_bytes_slice = unsafe { + core::slice::from_raw_parts(&udp as *const _ as *const u8, UdpHdr::LEN) + }; + buf.extend_from_slice(udp_bytes_slice); + buf.extend_from_slice(quic_payload_and_header); + + // Update IPv4 total length + let ipv4_total_len = (buf.len() - l3_offset) as u16; + ipv4.tot_len = ipv4_total_len.to_be_bytes(); + let updated_ipv4_bytes = unsafe { + core::slice::from_raw_parts(&ipv4 as *const _ as *const u8, ipv4_hdr_len) + }; + buf[l3_offset..l3_offset + ipv4_hdr_len].copy_from_slice(updated_ipv4_bytes); + + // Update UDP total length + let udp_total_len = (buf.len() - l4_offset) as u16; + udp.len = udp_total_len.to_be_bytes(); + let updated_udp_bytes = unsafe { + core::slice::from_raw_parts(&udp as *const _ as *const u8, UdpHdr::LEN) + }; + buf[l4_offset..l4_offset + UdpHdr::LEN].copy_from_slice(updated_udp_bytes); + buf + } + + // --- Test Orchestration Function (using TcContext) --- + fn run_parser_on_packet(ctx: &TcContext, parser: &mut Parser) -> Result<(), ()> { + // Increased loop count to handle more headers if necessary, 4 is Eth+IP+UDP+QUIC + for _ in 0..4 { + match parser.next_hdr { + HeaderType::Ethernet => parse_ethernet_header_dummy(ctx, parser)?, + HeaderType::Ipv4 => parse_ipv4_header_dummy(ctx, parser)?, + HeaderType::Udp => parse_udp_header_dummy(ctx, parser)?, + HeaderType::Quic => parse_quic_header(ctx, parser)?, + HeaderType::StopProcessing => break, + HeaderType::ErrorOccurred => return Err(()), + // _ => return Err(()), // Catch-all for unexpected HeaderTypes + } + } + if parser.next_hdr != HeaderType::StopProcessing { + // If it didn't stop, but also didn't error, it might be an issue if we expected it to fully parse. + Err(()) + } else { + Ok(()) + } + } + + // Helper to create __sk_buff and TcContext for tests + // This function is unsafe because TcContext::new is unsafe. + // It returns the skb_metadata so it can be kept alive by the caller. + unsafe fn create_tc_context_with_skb( + packet_slice: &mut [u8] + ) -> (TcContext, __sk_buff) { + let mut skb_metadata = MaybeUninit::<__sk_buff>::uninit(); + let skb_ptr = skb_metadata.as_mut_ptr(); + + // Zero out the struct + core::ptr::write_bytes(skb_ptr, 0, 1); + let mut skb_metadata = skb_metadata.assume_init(); + + skb_metadata.data = packet_slice.as_mut_ptr() as usize as u32; // Corrected: was `as *mut _ as u32` + skb_metadata.data_end = (packet_slice.as_mut_ptr() as usize + packet_slice.len()) as u32; // Corrected: removed `as *mut _` + skb_metadata.len = packet_slice.len() as u32; + // NOTE: For TcContext, skb.data usually points to the network header. + // Here, our parser expects offset 0 to be the start of Ethernet. + // So, skb.data pointing to the start of packet_slice is correct for these tests. + + let ctx = TcContext::new(&mut skb_metadata as *mut _); + (ctx, skb_metadata) + } + + + // --- New Integration Tests --- + #[test] + fn test_parse_full_packet_valid_quic_initial() { + let quic_data = vec![ + // Initial Packet (Long Header) + 0xC0, // Type: Initial, Fixed Bit: 1, Packet Number Length: 1 + 0x00, 0x00, 0x00, 0x01, // Version: 1 + 0x00, // DCID Len: 0 + 0x00, // SCID Len: 0 + 0x00, // Token Length: 0 + 0x01, // Length (payload + PN): 1 (for PN) + 0xAB, // Packet Number (dummy) + ]; + let mut packet_bytes_vec = build_quic_packet(&quic_data); + + let (ctx, mut _skb_metadata_guard) = unsafe { create_tc_context_with_skb(&mut packet_bytes_vec) }; + let mut parser = Parser::new(); + + let result = run_parser_on_packet(&ctx, &mut parser); + assert!(result.is_ok(), "Full packet parsing failed for QUIC Initial: {:?}", result.err()); + + // Expected offset: Eth (14) + IPv4 (20, default) + UDP (8) + QUIC data len + let expected_offset = EthHdr::LEN + (5 * 4) + UdpHdr::LEN + quic_data.len(); + assert_eq!(parser.offset, expected_offset); + assert_eq!(parser.next_hdr, HeaderType::StopProcessing); + } + + #[test] + fn test_parse_full_packet_valid_quic_short_header() { + let quic_data = vec![ + // Short Header Packet + 0x40, // Type: Short, Fixed Bit: 1, Spin Bit: 0, Key Phase: 0, PN Len: 1 + // (No DCID included in this minimal example, assumes 0 length for parser) + 0xBC, // Packet Number (dummy) + ]; + let mut packet_bytes_vec = build_quic_packet(&quic_data); + let (ctx, mut _skb_metadata_guard) = unsafe { create_tc_context_with_skb(&mut packet_bytes_vec) }; + let mut parser = Parser::new(); + + let result = run_parser_on_packet(&ctx, &mut parser); + assert!(result.is_ok(), "Full packet parsing failed for QUIC Short: {:?}", result.err()); + let expected_offset = EthHdr::LEN + (5 * 4) + UdpHdr::LEN + quic_data.len(); + assert_eq!(parser.offset, expected_offset); + assert_eq!(parser.next_hdr, HeaderType::StopProcessing); + } + + #[test] + fn test_parse_full_packet_quic_too_short_for_header() { + let quic_data = vec![0xC0]; // Valid first byte for Initial, but not enough data for rest of header + let mut packet_bytes_vec = build_quic_packet(&quic_data); + let (ctx, mut _skb_metadata_guard) = unsafe { create_tc_context_with_skb(&mut packet_bytes_vec) }; + let mut parser = Parser::new(); + + let result = run_parser_on_packet(&ctx, &mut parser); + assert!(result.is_err(), "Full packet parsing should fail if QUIC part is malformed/too short"); + // Parser should stop at QUIC header parsing, offset remains at start of QUIC + assert_eq!(parser.offset, EthHdr::LEN + (5*4) + UdpHdr::LEN); + assert_eq!(parser.next_hdr, HeaderType::Quic); // Still expecting QUIC, but failed + } + + // This function returns __sk_buff to ensure its lifetime is tied to the caller's scope, + // preventing the TcContext from holding a dangling pointer. + fn setup_direct_quic_test(packet_slice: &mut [u8], initial_offset: usize) -> (TcContext, Parser, __sk_buff) { + let (ctx, skb_metadata) = unsafe { create_tc_context_with_skb(packet_slice) }; + + let mut parser = Parser::new(); + parser.offset = initial_offset; + parser.next_hdr = HeaderType::Quic; + (ctx, parser, skb_metadata) + } + + + #[test] + fn test_parse_quic_header_short_minimal_direct() { + let mut packet_bytes = vec![0x40, 0x01]; // Short header, PN Len 1, PN 0x01 + let (ctx, mut parser, _skb_guard) = setup_direct_quic_test(&mut packet_bytes, 0); + let result = parse_quic_header(&ctx, &mut parser); + assert!(result.is_ok(), "QUIC short minimal direct parse failed: {:?}", result.err()); + assert_eq!(parser.offset, 2); // 1 byte header flags + 1 byte PN + } + + #[test] + fn test_parse_quic_header_initial_basic_direct() { + let mut packet_bytes = vec![ + // Initial Packet (Long Header) + 0xC0, // Type: Initial (00), Fixed Bit: 1, Packet Number Length: 1 (00) + 0x00, 0x00, 0x00, 0x01, // Version: 1 + 0x00, // DCID Len: 0 + 0x00, // SCID Len: 0 + 0x00, // Token Length (VarInt): 0 + 0x01, // Length (VarInt): 1 (covers 1 byte PN) + 0xAB, // Packet Number: 0xAB (1 byte) + ]; + let (ctx, mut parser, _skb_guard) = setup_direct_quic_test(&mut packet_bytes, 0); + let result = parse_quic_header(&ctx, &mut parser); + assert!(result.is_ok(), "QUIC initial basic direct parse failed: {:?}", result.err()); + // Expected: 1 (type) + 4 (ver) + 1 (dcidlen) + 1 (scidlen) + 1 (tokenlen) + 1 (len) + 1 (pn) = 10 + assert_eq!(parser.offset, 10); + } + + #[test] + fn test_parse_quic_initial_with_cids_token_longer_length_direct() { + // Long header initial packet: Type (Initial) + PN Len (2 bytes from 0b01) + let mut packet_bytes = vec![ + 0xC0 | 0b01, // First byte: Long Header (1), Fixed Bit (1), Type (Initial 00), Reserved (0), PN Len (2 -> 01) + // Version (4 bytes) + 0x00, 0x00, 0x00, 0x01, + // DCID Len (1 byte) + DCID (4 bytes) + 0x04, // DCID Len = 4 + 0x01, 0x02, 0x03, 0x04, // DCID + // SCID Len (1 byte) + SCID (4 bytes) + 0x04, // SCID Len = 4 + 0x05, 0x06, 0x07, 0x08, // SCID + // Token Length (varint, 1 byte for value 2) + Token (2 bytes) + 0x02, // Token Length = 2 + 0xAB, 0xCD, // Token + // Length (varint, 1 byte for value 5 -> payload 3 + PN 2) + Payload (variable) + PN (2 bytes) + // The problem statement defines Length as "Length (varint): 1 (covers 1 byte PN)" for basic initial + // This test case's Length is 0x05. If PN Len is 2 (from 0xC0 | 0b01), this means payload part is 3. + // The previous PN was 0xAB. Here it's 0x12, 0x34 + 0x05, // Length = 5 (meaning 3 bytes of "payload" if PN takes 2) + 0x12, 0x34, // Packet Number (2 bytes due to PN Len 0b01) + ]; + // Total expected length: + // 1 (Flags) + 4 (Version) + 1 (DCID Len) + 4 (DCID) + 1 (SCID Len) + 4 (SCID) + // + 1 (Token Varint Len for value 2) + 2 (Token actual bytes) + // + 1 (Length Varint Len for value 5) + 2 (PN actual bytes) + // = 1+4+1+4+1+4+1+2+1+2 = 21 bytes + let (ctx, mut parser, _skb_guard) = setup_direct_quic_test(&mut packet_bytes, 0); + let result = parse_quic_header(&ctx, &mut parser); + assert!(result.is_ok(), "Parsing QUIC Initial with CIDs/Token failed: {:?}", result.err()); + assert_eq!(parser.offset, 21); + } + + #[test] + fn test_packet_too_short_for_long_header_version_direct() { + let mut packet_bytes = vec![0xC0, 0x00, 0x00]; // Not enough for version + let (ctx, mut parser, _skb_guard) = setup_direct_quic_test(&mut packet_bytes, 0); + assert!(parse_quic_header(&ctx, &mut parser).is_err()); + } + + #[test] + fn test_packet_too_short_for_short_header_pkt_num_direct() { + let mut packet_bytes = vec![0x41]; // Short header, PN len 2 (0b01), but no PN bytes + let (ctx, mut parser, _skb_guard) = setup_direct_quic_test(&mut packet_bytes, 0); + assert!(parse_quic_header(&ctx, &mut parser).is_err()); + } + + #[test] + fn test_quic_hdr_struct_methods_basic() { + let first_byte = 0xc0; // Long header, Initial, PN Len 1 + let mut hdr_for_test = QuicHdr::new(QuicHeaderType::QuicLong); // Type is for context + hdr_for_test.set_first_byte(first_byte); + assert!(hdr_for_test.is_long_header()); + assert_eq!(hdr_for_test.long_packet_type().unwrap(), QuicPacketType::Initial); + // Corrected method name: + assert_eq!(hdr_for_test.packet_number_length_long().unwrap(), 1); + } +} \ No newline at end of file From a6fd78a2e09260acc9f5020dae30455733b3aced Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Mon, 16 Jun 2025 04:07:58 -0500 Subject: [PATCH 05/14] check-in --- Cargo.toml | 3 +- src/quic.rs | 19 +- tests/quic_hdr_integration.rs | 477 ---------------------------------- 3 files changed, 9 insertions(+), 490 deletions(-) delete mode 100644 tests/quic_hdr_integration.rs diff --git a/Cargo.toml b/Cargo.toml index db13036..a4ab299 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ edition = "2021" [dependencies] serde = { version = "1", default-features = false, features = ["derive"], optional = true } -serde_bytes = { version = "0.11", optional = true } [dev-dependencies] aya-ebpf = { git = "https://github.com/aya-rs/aya", branch = "main" } @@ -20,4 +19,4 @@ bincode = { version = "2.0.0-rc.3", default-features = false, features = [ serde_test = { version = "1.0", default-features = false } [features] -serde = ["dep:serde", "dep:serde_bytes"] +serde = ["dep:serde"] diff --git a/src/quic.rs b/src/quic.rs index 22c6075..78d5c04 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -20,9 +20,6 @@ const SHORT_RESERVED_BITS_SHIFT: u8 = 3; const SHORT_KEY_PHASE_BIT_MASK: u8 = 0x04; const PN_LENGTH_BITS_MASK: u8 = 0x03; -#[cfg(feature = "serde")] -use serde_bytes::{ByteBuf, Bytes}; - /// Error type for safe getter/setter operations on `QuicHdr`. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum QuicHdrError { @@ -323,9 +320,7 @@ macro_rules! impl_cid_common { ser::SerializeStruct, Deserializer, Serializer, }; - // Assumes `use serde_bytes::{ByteBuf, Bytes};` is at the top of the file. // Assumes helper structs are in `crate::quic::cid_serde_helpers` - struct CidVisitor; impl<'de> Visitor<'de> for CidVisitor { type Value = $t; @@ -345,13 +340,13 @@ macro_rules! impl_cid_common { let len: u8 = seq.next_element()?.ok_or_else(|| { de::Error::invalid_length(0, &"Missing CID len in sequence") })?; - let bytes_buf: ByteBuf = seq.next_element()?.ok_or_else(|| { + let bytes_slice: &[u8] = seq.next_element()?.ok_or_else(|| { de::Error::invalid_length(1, &"Missing CID bytes in sequence") })?; - if bytes_buf.len() != len as usize { + if bytes_slice.len() != len as usize { return Err(de::Error::invalid_value( - de::Unexpected::Bytes(bytes_buf.as_ref()), + de::Unexpected::Bytes(bytes_slice), &crate::quic::cid_serde_helpers::ExpectedCidBytesInfo { len }, )); } @@ -366,7 +361,8 @@ macro_rules! impl_cid_common { )); } let mut id = $t::new(); - id.set(bytes_buf.as_ref()); + // REPLACED: Use the slice directly. + id.set(bytes_slice); id.len = len; // Ensure length is set after data. Ok(id) } @@ -399,7 +395,8 @@ macro_rules! impl_cid_common { if $with_len_on_wire { let mut st = serializer.serialize_struct(stringify!($t), 2)?; st.serialize_field("len", &self.len)?; - st.serialize_field("bytes", &Bytes::new(self.as_slice()))?; + // REPLACED: `&Bytes::new(...)` with the raw slice. `serde` handles it correctly. + st.serialize_field("bytes", self.as_slice())?; st.end() } else { serializer.serialize_bytes(self.as_slice()) @@ -2801,4 +2798,4 @@ mod tests { Err(QuicHdrError::InvalidHeaderForm) ); } -} +} \ No newline at end of file diff --git a/tests/quic_hdr_integration.rs b/tests/quic_hdr_integration.rs deleted file mode 100644 index aad6656..0000000 --- a/tests/quic_hdr_integration.rs +++ /dev/null @@ -1,477 +0,0 @@ -#![allow(dead_code)] // Allow dead code for elements that are part of the setup - -use network_types::quic::{ - QuicHdr, QuicPacketType, QuicHeaderType, - QUIC_MAX_CID_LEN, // Used in parse_quic_header -}; -use network_types::eth::{EthHdr, EtherType}; -use network_types::ip::{IpProto, Ipv4Hdr}; -use network_types::udp::UdpHdr; - -use aya_ebpf::{ - programs::TcContext, - EbpfContext, // Trait for ctx.load(), used by parser functions - bindings::__sk_buff // For creating TcContext -}; -// core::ptr was removed as it's not used. - -// Constants for QUIC parsing logic, mirrored from src/quic.rs as they are private there. -const HDR_FORM_BIT_TEST: u8 = 0x80; // Not used directly in this file after changes, but kept for reference -const LONG_PACKET_TYPE_MASK_TEST: u8 = 0x30; -const LONG_PACKET_TYPE_SHIFT_TEST: u8 = 4; -const PN_LENGTH_BITS_MASK_TEST: u8 = 0x03; - - -// --- Parser Struct and HeaderType Enum (remains the same) --- -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -enum HeaderType { - Ethernet, - Ipv4, - // Ipv6, // Not used in current tests, can be re-added if needed - Udp, - Quic, - StopProcessing, - ErrorOccurred, -} - -struct Parser { - offset: usize, - next_hdr: HeaderType, -} - -impl Parser { - fn new() -> Self { - Parser { - offset: 0, - next_hdr: HeaderType::Ethernet, - } - } -} - -fn read_quic_varint(ctx: &TcContext, offset: usize) -> Result<(u64, usize), ()> { - // Ensure we can read the first byte to determine the length of the varint. - let read_end = offset + 1; - if read_end > ctx.data_end() { - return Err(()); - } - let first_byte: u8 = unsafe { ctx.load(offset).map_err(|_| ())? }; - - // The length of the varint (1, 2, 4, or 8 bytes) is encoded in the first two bits. - let len = 1 << (first_byte >> 6); - - // Ensure the full varint is within packet bounds before reading it. - let read_end = offset + len; - if read_end > ctx.data_end() { - return Err(()); - } - - // Read the varint value based on its determined length. - let val = match len { - 1 => (first_byte & 0x3F) as u64, - 2 => { - let raw: u16 = unsafe { ctx.load(offset).map_err(|_| ())? }; - (u16::from_be(raw) & 0x3FFF) as u64 - } - 4 => { - let raw: u32 = unsafe { ctx.load(offset).map_err(|_| ())? }; - (u32::from_be(raw) & 0x3FFFFFFF) as u64 - } - 8 => { - let raw: u64 = unsafe { ctx.load(offset).map_err(|_| ())? }; - u64::from_be(raw) & 0x3FFFFFFFFFFFFFFF - } - _ => return Err(()), // Should be unreachable - }; - - Ok((val, len)) -} - -fn parse_quic_header(ctx: &TcContext, parser: &mut Parser) -> Result<(), ()> { - let mut current_offset = parser.offset; - - // Boundary check for the first byte of the QUIC header. - if current_offset + 1 > ctx.data_end() { - return Err(()); - } - let first_byte: u8 = unsafe { ctx.load(current_offset).map_err(|_| ())? }; - current_offset += 1; - - let is_long_header = (first_byte & HDR_FORM_BIT_TEST) != 0; - - if is_long_header { - // --- Long Header Parsing --- - - // Boundary check for Version (4 bytes). - if current_offset + 4 > ctx.data_end() { - return Err(()); - } - current_offset += 4; // Skip Version - - // Boundary check for DCID Len (1 byte) and DCID. - if current_offset + 1 > ctx.data_end() { - return Err(()); - } - let dcid_len: u8 = unsafe { ctx.load(current_offset).map_err(|_| ())? }; - current_offset += 1; - if current_offset + (dcid_len as usize) > ctx.data_end() { - return Err(()); - } - current_offset += dcid_len as usize; // Skip DCID - - // Boundary check for SCID Len (1 byte) and SCID. - if current_offset + 1 > ctx.data_end() { - return Err(()); - } - let scid_len: u8 = unsafe { ctx.load(current_offset).map_err(|_| ())? }; - current_offset += 1; - if current_offset + (scid_len as usize) > ctx.data_end() { - return Err(()); - } - current_offset += scid_len as usize; // Skip SCID - - // For Initial packets, parse Token Length and Token. - let long_packet_type = (first_byte & LONG_PACKET_TYPE_MASK_TEST) >> LONG_PACKET_TYPE_SHIFT_TEST; - if long_packet_type == 0 { // Type 0 -> Initial Packet - let (token_len_val, token_len_bytes) = read_quic_varint(ctx, current_offset)?; - current_offset += token_len_bytes; - if current_offset + (token_len_val as usize) > ctx.data_end() { - return Err(()); - } - current_offset += token_len_val as usize; // Skip Token - } - - // Read payload length using our safe varint reader. - let (_payload_len, len_bytes) = read_quic_varint(ctx, current_offset)?; - current_offset += len_bytes; - - // Boundary check for the Packet Number. Its length is in the first byte. - let pn_len = (first_byte & PN_LENGTH_BITS_MASK_TEST) as usize + 1; - if current_offset + pn_len > ctx.data_end() { - return Err(()); - } - current_offset += pn_len; // Skip Packet Number - } else { - // --- Short Header Parsing --- - // For short headers, the DCID length is implicit from the connection context. - // For a stateless parser, we must assume a fixed length. 8 bytes is a common choice. - const SHORT_HEADER_DCID_LEN: usize = 8; - if current_offset + SHORT_HEADER_DCID_LEN > ctx.data_end() { - return Err(()); - } - current_offset += SHORT_HEADER_DCID_LEN; // Skip DCID - - // Packet Number length is also implicit and protected. We assume a max length of 4 bytes. - const SHORT_HEADER_PN_LEN: usize = 4; - if current_offset + SHORT_HEADER_PN_LEN > ctx.data_end() { - return Err(()); - } - current_offset += SHORT_HEADER_PN_LEN; // Skip Packet Number - } - - parser.offset = current_offset; - parser.next_hdr = HeaderType::StopProcessing; - Ok(()) -} - -// --- Simplified Layer Parsers for Test Orchestration (using TcContext) --- -fn parse_ethernet_header_dummy(ctx: &TcContext, parser: &mut Parser) -> Result<(), ()> { - let _eth_hdr: EthHdr = ctx.load(parser.offset).map_err(|_| ())?; - parser.offset += EthHdr::LEN; - parser.next_hdr = HeaderType::Ipv4; - Ok(()) -} - -fn parse_ipv4_header_dummy(ctx: &TcContext, parser: &mut Parser) -> Result<(), ()> { - let ipv4_hdr: Ipv4Hdr = ctx.load(parser.offset).map_err(|_| ())?; - let ip_hdr_len = (ipv4_hdr.vihl & 0x0F) as usize * 4; - if ip_hdr_len < Ipv4Hdr::LEN { return Err(()); } - parser.offset += ip_hdr_len; - parser.next_hdr = HeaderType::Udp; // Assuming UDP next for QUIC - Ok(()) -} - -fn parse_udp_header_dummy(ctx: &TcContext, parser: &mut Parser) -> Result<(), ()> { - let _udp_hdr: UdpHdr = ctx.load(parser.offset).map_err(|_| ())?; - parser.offset += UdpHdr::LEN; - parser.next_hdr = HeaderType::Quic; - Ok(()) -} - - -#[cfg(test)] -mod tests { - use super::*; - use core::mem::MaybeUninit; - - // --- Packet Building Helper --- - fn build_quic_packet(quic_payload_and_header: &[u8]) -> Vec { - let mut buf = Vec::new(); - let eth = EthHdr { - dst_addr: [0x11, 0x11, 0x11, 0x11, 0x11, 0x11], - src_addr: [0x22, 0x22, 0x22, 0x22, 0x22, 0x22], - ether_type: EtherType::Ipv4.into(), // Use .into() for u16 conversion - }; - // Manual serialization for EthHdr as it might not be Pod - buf.extend_from_slice(ð.dst_addr); // Corrected: was ð.dst_addr - buf.extend_from_slice(ð.src_addr); // Corrected: was ð.src_addr - buf.extend_from_slice(&u16::from(eth.ether_type).to_be_bytes()); - - - let l3_offset = buf.len(); - let mut ipv4 = Ipv4Hdr { - vihl: (4 << 4) | 5, // IPv4, 5 * 32-bit words header length - tos: 0, - tot_len: 0u16.to_be_bytes(), // Placeholder, will be updated - id: 0x1234u16.to_be_bytes(), - frags: 0u16.to_be_bytes(), - ttl: 64, - proto: IpProto::Udp, - check: 0u16.to_be_bytes(), // Checksum placeholder - src_addr: [192, 168, 1, 1].into(), - dst_addr: [192, 168, 1, 2].into(), - }; - let ipv4_hdr_len = (ipv4.vihl & 0x0F) as usize * 4; - // Directly extend with a known-size struct - let ipv4_bytes_slice = unsafe { - core::slice::from_raw_parts(&ipv4 as *const _ as *const u8, ipv4_hdr_len) - }; - buf.extend_from_slice(ipv4_bytes_slice); - - - let l4_offset = buf.len(); - let mut udp = UdpHdr { - src: 12345u16.to_be_bytes(), // Corrected: was source - dst: 443u16.to_be_bytes(), // Corrected: was dest - len: 0u16.to_be_bytes(), // Placeholder - check: 0u16.to_be_bytes(), // Checksum placeholder - }; - let udp_bytes_slice = unsafe { - core::slice::from_raw_parts(&udp as *const _ as *const u8, UdpHdr::LEN) - }; - buf.extend_from_slice(udp_bytes_slice); - buf.extend_from_slice(quic_payload_and_header); - - // Update IPv4 total length - let ipv4_total_len = (buf.len() - l3_offset) as u16; - ipv4.tot_len = ipv4_total_len.to_be_bytes(); - let updated_ipv4_bytes = unsafe { - core::slice::from_raw_parts(&ipv4 as *const _ as *const u8, ipv4_hdr_len) - }; - buf[l3_offset..l3_offset + ipv4_hdr_len].copy_from_slice(updated_ipv4_bytes); - - // Update UDP total length - let udp_total_len = (buf.len() - l4_offset) as u16; - udp.len = udp_total_len.to_be_bytes(); - let updated_udp_bytes = unsafe { - core::slice::from_raw_parts(&udp as *const _ as *const u8, UdpHdr::LEN) - }; - buf[l4_offset..l4_offset + UdpHdr::LEN].copy_from_slice(updated_udp_bytes); - buf - } - - // --- Test Orchestration Function (using TcContext) --- - fn run_parser_on_packet(ctx: &TcContext, parser: &mut Parser) -> Result<(), ()> { - // Increased loop count to handle more headers if necessary, 4 is Eth+IP+UDP+QUIC - for _ in 0..4 { - match parser.next_hdr { - HeaderType::Ethernet => parse_ethernet_header_dummy(ctx, parser)?, - HeaderType::Ipv4 => parse_ipv4_header_dummy(ctx, parser)?, - HeaderType::Udp => parse_udp_header_dummy(ctx, parser)?, - HeaderType::Quic => parse_quic_header(ctx, parser)?, - HeaderType::StopProcessing => break, - HeaderType::ErrorOccurred => return Err(()), - // _ => return Err(()), // Catch-all for unexpected HeaderTypes - } - } - if parser.next_hdr != HeaderType::StopProcessing { - // If it didn't stop, but also didn't error, it might be an issue if we expected it to fully parse. - Err(()) - } else { - Ok(()) - } - } - - // Helper to create __sk_buff and TcContext for tests - // This function is unsafe because TcContext::new is unsafe. - // It returns the skb_metadata so it can be kept alive by the caller. - unsafe fn create_tc_context_with_skb( - packet_slice: &mut [u8] - ) -> (TcContext, __sk_buff) { - let mut skb_metadata = MaybeUninit::<__sk_buff>::uninit(); - let skb_ptr = skb_metadata.as_mut_ptr(); - - // Zero out the struct - core::ptr::write_bytes(skb_ptr, 0, 1); - let mut skb_metadata = skb_metadata.assume_init(); - - skb_metadata.data = packet_slice.as_mut_ptr() as usize as u32; // Corrected: was `as *mut _ as u32` - skb_metadata.data_end = (packet_slice.as_mut_ptr() as usize + packet_slice.len()) as u32; // Corrected: removed `as *mut _` - skb_metadata.len = packet_slice.len() as u32; - // NOTE: For TcContext, skb.data usually points to the network header. - // Here, our parser expects offset 0 to be the start of Ethernet. - // So, skb.data pointing to the start of packet_slice is correct for these tests. - - let ctx = TcContext::new(&mut skb_metadata as *mut _); - (ctx, skb_metadata) - } - - - // --- New Integration Tests --- - #[test] - fn test_parse_full_packet_valid_quic_initial() { - let quic_data = vec![ - // Initial Packet (Long Header) - 0xC0, // Type: Initial, Fixed Bit: 1, Packet Number Length: 1 - 0x00, 0x00, 0x00, 0x01, // Version: 1 - 0x00, // DCID Len: 0 - 0x00, // SCID Len: 0 - 0x00, // Token Length: 0 - 0x01, // Length (payload + PN): 1 (for PN) - 0xAB, // Packet Number (dummy) - ]; - let mut packet_bytes_vec = build_quic_packet(&quic_data); - - let (ctx, mut _skb_metadata_guard) = unsafe { create_tc_context_with_skb(&mut packet_bytes_vec) }; - let mut parser = Parser::new(); - - let result = run_parser_on_packet(&ctx, &mut parser); - assert!(result.is_ok(), "Full packet parsing failed for QUIC Initial: {:?}", result.err()); - - // Expected offset: Eth (14) + IPv4 (20, default) + UDP (8) + QUIC data len - let expected_offset = EthHdr::LEN + (5 * 4) + UdpHdr::LEN + quic_data.len(); - assert_eq!(parser.offset, expected_offset); - assert_eq!(parser.next_hdr, HeaderType::StopProcessing); - } - - #[test] - fn test_parse_full_packet_valid_quic_short_header() { - let quic_data = vec![ - // Short Header Packet - 0x40, // Type: Short, Fixed Bit: 1, Spin Bit: 0, Key Phase: 0, PN Len: 1 - // (No DCID included in this minimal example, assumes 0 length for parser) - 0xBC, // Packet Number (dummy) - ]; - let mut packet_bytes_vec = build_quic_packet(&quic_data); - let (ctx, mut _skb_metadata_guard) = unsafe { create_tc_context_with_skb(&mut packet_bytes_vec) }; - let mut parser = Parser::new(); - - let result = run_parser_on_packet(&ctx, &mut parser); - assert!(result.is_ok(), "Full packet parsing failed for QUIC Short: {:?}", result.err()); - let expected_offset = EthHdr::LEN + (5 * 4) + UdpHdr::LEN + quic_data.len(); - assert_eq!(parser.offset, expected_offset); - assert_eq!(parser.next_hdr, HeaderType::StopProcessing); - } - - #[test] - fn test_parse_full_packet_quic_too_short_for_header() { - let quic_data = vec![0xC0]; // Valid first byte for Initial, but not enough data for rest of header - let mut packet_bytes_vec = build_quic_packet(&quic_data); - let (ctx, mut _skb_metadata_guard) = unsafe { create_tc_context_with_skb(&mut packet_bytes_vec) }; - let mut parser = Parser::new(); - - let result = run_parser_on_packet(&ctx, &mut parser); - assert!(result.is_err(), "Full packet parsing should fail if QUIC part is malformed/too short"); - // Parser should stop at QUIC header parsing, offset remains at start of QUIC - assert_eq!(parser.offset, EthHdr::LEN + (5*4) + UdpHdr::LEN); - assert_eq!(parser.next_hdr, HeaderType::Quic); // Still expecting QUIC, but failed - } - - // This function returns __sk_buff to ensure its lifetime is tied to the caller's scope, - // preventing the TcContext from holding a dangling pointer. - fn setup_direct_quic_test(packet_slice: &mut [u8], initial_offset: usize) -> (TcContext, Parser, __sk_buff) { - let (ctx, skb_metadata) = unsafe { create_tc_context_with_skb(packet_slice) }; - - let mut parser = Parser::new(); - parser.offset = initial_offset; - parser.next_hdr = HeaderType::Quic; - (ctx, parser, skb_metadata) - } - - - #[test] - fn test_parse_quic_header_short_minimal_direct() { - let mut packet_bytes = vec![0x40, 0x01]; // Short header, PN Len 1, PN 0x01 - let (ctx, mut parser, _skb_guard) = setup_direct_quic_test(&mut packet_bytes, 0); - let result = parse_quic_header(&ctx, &mut parser); - assert!(result.is_ok(), "QUIC short minimal direct parse failed: {:?}", result.err()); - assert_eq!(parser.offset, 2); // 1 byte header flags + 1 byte PN - } - - #[test] - fn test_parse_quic_header_initial_basic_direct() { - let mut packet_bytes = vec![ - // Initial Packet (Long Header) - 0xC0, // Type: Initial (00), Fixed Bit: 1, Packet Number Length: 1 (00) - 0x00, 0x00, 0x00, 0x01, // Version: 1 - 0x00, // DCID Len: 0 - 0x00, // SCID Len: 0 - 0x00, // Token Length (VarInt): 0 - 0x01, // Length (VarInt): 1 (covers 1 byte PN) - 0xAB, // Packet Number: 0xAB (1 byte) - ]; - let (ctx, mut parser, _skb_guard) = setup_direct_quic_test(&mut packet_bytes, 0); - let result = parse_quic_header(&ctx, &mut parser); - assert!(result.is_ok(), "QUIC initial basic direct parse failed: {:?}", result.err()); - // Expected: 1 (type) + 4 (ver) + 1 (dcidlen) + 1 (scidlen) + 1 (tokenlen) + 1 (len) + 1 (pn) = 10 - assert_eq!(parser.offset, 10); - } - - #[test] - fn test_parse_quic_initial_with_cids_token_longer_length_direct() { - // Long header initial packet: Type (Initial) + PN Len (2 bytes from 0b01) - let mut packet_bytes = vec![ - 0xC0 | 0b01, // First byte: Long Header (1), Fixed Bit (1), Type (Initial 00), Reserved (0), PN Len (2 -> 01) - // Version (4 bytes) - 0x00, 0x00, 0x00, 0x01, - // DCID Len (1 byte) + DCID (4 bytes) - 0x04, // DCID Len = 4 - 0x01, 0x02, 0x03, 0x04, // DCID - // SCID Len (1 byte) + SCID (4 bytes) - 0x04, // SCID Len = 4 - 0x05, 0x06, 0x07, 0x08, // SCID - // Token Length (varint, 1 byte for value 2) + Token (2 bytes) - 0x02, // Token Length = 2 - 0xAB, 0xCD, // Token - // Length (varint, 1 byte for value 5 -> payload 3 + PN 2) + Payload (variable) + PN (2 bytes) - // The problem statement defines Length as "Length (varint): 1 (covers 1 byte PN)" for basic initial - // This test case's Length is 0x05. If PN Len is 2 (from 0xC0 | 0b01), this means payload part is 3. - // The previous PN was 0xAB. Here it's 0x12, 0x34 - 0x05, // Length = 5 (meaning 3 bytes of "payload" if PN takes 2) - 0x12, 0x34, // Packet Number (2 bytes due to PN Len 0b01) - ]; - // Total expected length: - // 1 (Flags) + 4 (Version) + 1 (DCID Len) + 4 (DCID) + 1 (SCID Len) + 4 (SCID) - // + 1 (Token Varint Len for value 2) + 2 (Token actual bytes) - // + 1 (Length Varint Len for value 5) + 2 (PN actual bytes) - // = 1+4+1+4+1+4+1+2+1+2 = 21 bytes - let (ctx, mut parser, _skb_guard) = setup_direct_quic_test(&mut packet_bytes, 0); - let result = parse_quic_header(&ctx, &mut parser); - assert!(result.is_ok(), "Parsing QUIC Initial with CIDs/Token failed: {:?}", result.err()); - assert_eq!(parser.offset, 21); - } - - #[test] - fn test_packet_too_short_for_long_header_version_direct() { - let mut packet_bytes = vec![0xC0, 0x00, 0x00]; // Not enough for version - let (ctx, mut parser, _skb_guard) = setup_direct_quic_test(&mut packet_bytes, 0); - assert!(parse_quic_header(&ctx, &mut parser).is_err()); - } - - #[test] - fn test_packet_too_short_for_short_header_pkt_num_direct() { - let mut packet_bytes = vec![0x41]; // Short header, PN len 2 (0b01), but no PN bytes - let (ctx, mut parser, _skb_guard) = setup_direct_quic_test(&mut packet_bytes, 0); - assert!(parse_quic_header(&ctx, &mut parser).is_err()); - } - - #[test] - fn test_quic_hdr_struct_methods_basic() { - let first_byte = 0xc0; // Long header, Initial, PN Len 1 - let mut hdr_for_test = QuicHdr::new(QuicHeaderType::QuicLong); // Type is for context - hdr_for_test.set_first_byte(first_byte); - assert!(hdr_for_test.is_long_header()); - assert_eq!(hdr_for_test.long_packet_type().unwrap(), QuicPacketType::Initial); - // Corrected method name: - assert_eq!(hdr_for_test.packet_number_length_long().unwrap(), 1); - } -} \ No newline at end of file From e12cca6a0f5fed81339628bb8f1ee68ba903b218 Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Wed, 18 Jun 2025 02:15:58 -0500 Subject: [PATCH 06/14] Improved QUIC header documentation and refactored `impl_cid_common!` macro for better usability and safety and optimized `QuicHdr` for Kernel Space. --- src/quic.rs | 3044 ++++++++++++++++++++------------------------------- 1 file changed, 1160 insertions(+), 1884 deletions(-) diff --git a/src/quic.rs b/src/quic.rs index 78d5c04..ed4625f 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -1,12 +1,19 @@ //! QUIC header and Connection‑ID types with full support for //! variable‑length IDs, long/short header forms and zero‑allocation -//! (kernel‑friendly) (de)serialisation. +//! (kernel‑friendly) (de)serialization. //! //! Designed for use inside eBPF (`aya`) programs → `#![no_std]`, //! fixed‑capacity buffers, no heap, packed layouts. -use core::{cmp, convert::TryFrom, fmt, hash, ptr}; +use core::{ + cmp, + convert::TryFrom, + fmt, + hash::{self, Hash}, + mem, ptr, +}; +/// The maximum length of a QUIC Connection ID (CID) in bytes, as per RFC 9000. pub const QUIC_MAX_CID_LEN: usize = 20; const HEADER_FORM_BIT: u8 = 0x80; const FIXED_BIT_MASK: u8 = 0x40; @@ -20,75 +27,97 @@ const SHORT_RESERVED_BITS_SHIFT: u8 = 3; const SHORT_KEY_PHASE_BIT_MASK: u8 = 0x04; const PN_LENGTH_BITS_MASK: u8 = 0x03; -/// Error type for safe getter/setter operations on `QuicHdr`. +/// Errors that can occur during QUIC header parsing and manipulation. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum QuicHdrError { - /// Operation is not applicable to the current header form (e.g., asking for Long Packet Type on a Short Header). + /// An operation was attempted that is not valid for the current header form (e.g., reading a version from a Short Header). InvalidHeaderForm, - /// Provided length for a field (e.g. CID) is invalid or exceeds maximum allowed. + /// A length field (e.g., for a Connection ID) was invalid or exceeded the maximum allowed size. InvalidLength, - /// Invalid QUIC packet type bits encountered during parsing. + /// The packet type bits in a Long Header were not one of the valid, known values. InvalidPacketTypeBits, } impl fmt::Display for QuicHdrError { + #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - QuicHdrError::InvalidHeaderForm => { + Self::InvalidHeaderForm => { write!(f, "Operation invalid for current QUIC header form") } - QuicHdrError::InvalidLength => { - write!(f, "Invalid length provided for a QUIC header field") - } - QuicHdrError::InvalidPacketTypeBits => { - write!(f, "Invalid packet type bits for QUIC header") + Self::InvalidLength => write!(f, "invalid length value for QUIC header"), + Self::InvalidPacketTypeBits => { + write!(f, "invalid packet type bits for QUIC long header") } } } } -/// Represents different types of QUIC packets. +/// QUIC Long Header Packet Types, as per RFC 9000 Section 17.2. #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(u8)] pub enum QuicPacketType { + /// Initial packet. Initial = 0x00, + /// 0-RTT packet. ZeroRTT = 0x01, + /// Handshake packet. Handshake = 0x02, + /// Retry packet. Retry = 0x03, - // OneRTT for conceptual clarity, though not a direct mapping from packet type bits. - // Short headers are identified by header form, not these type bits. } -/// Represents QUIC transport errors. -/// See RFC 9000, Section 22. +/// QUIC Transport Error Codes, as per RFC 9000 Section 20. #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(u16)] +#[allow(non_camel_case_types)] pub enum QuicTransportError { + /// No error. This is used when the connection is closed gracefully. NoError = 0x0, + /// An internal error occurred in the endpoint. InternalError = 0x1, + /// The server refused the connection. ConnectionRefused = 0x2, + /// A flow control limit was violated. FlowControlError = 0x3, + /// The number of streams exceeded the negotiated limit. StreamLimitError = 0x4, + /// An operation was attempted on a stream in an invalid state. StreamStateError = 0x5, + /// The final size of a stream is incorrect. FinalSizeError = 0x6, + /// A frame was malformed. FrameFormatError = 0x7, + /// A transport parameter was invalid. TransportParameterError = 0x8, + /// The number of connection IDs exceeded the negotiated limit. ConnectionIdLimitError = 0x9, + /// A general protocol violation was detected. ProtocolViolation = 0xA, + /// A token (e.g., for retry or new token) was invalid. InvalidToken = 0xB, + /// An application-specific error occurred. ApplicationError = 0xC, + /// The crypto buffer was exceeded. CryptoBufferExceeded = 0xD, + /// An error occurred during a key update. KeyUpdateError = 0xE, + /// The AEAD confidentiality or integrity limit was reached. AeadLimitReached = 0xF, + /// The endpoint has no viable network path. NoViablePath = 0x10, } +/// Serde helper types for custom error messages. #[cfg(feature = "serde")] mod cid_serde_helpers { use core::fmt; + use serde::de::Expected; + /// Helper struct for creating a `serde::de::Error` when expecting CID bytes. pub struct ExpectedCidBytesInfo { + /// The expected length of the Connection ID. pub len: u8, } impl fmt::Display for ExpectedCidBytesInfo { @@ -96,19 +125,21 @@ mod cid_serde_helpers { write!(f, "CID bytes for explicit length {}", self.len) } } - impl Expected for ExpectedCidBytesInfo { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, formatter) + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) } } + /// Helper struct for creating a `serde::de::Error` when a length constraint is violated. pub struct LengthExceedsMaxError { + /// The invalid length that was encountered. pub value: usize, + /// The maximum allowed length. pub max: usize, + /// The name of the field with the invalid length. pub field_name: &'static str, } - impl fmt::Display for LengthExceedsMaxError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -118,304 +149,229 @@ mod cid_serde_helpers { ) } } - impl Expected for LengthExceedsMaxError { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, formatter) + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) } } } -/// Macro to implement common functionality for Connection ID wrapper structs. +/// A raw, fast, unchecked memory copy. +/// +/// # Safety +/// Caller must ensure that `src` and `dst` are valid for reads and writes +/// of `n` bytes, respectively, and that they do not overlap. +#[inline(always)] +unsafe fn raw_copy(src: *const u8, dst: *mut u8, n: usize) { + ptr::copy_nonoverlapping(src, dst, n); +} + +/// Generates a struct and associated implementations for a QUIC Connection ID (CID). +/// +/// QUIC CIDs are variable-length identifiers. To handle them in a `no_std`, +/// zero-allocation context like eBPF, this macro creates a struct that pairs +/// a `len` field with a fixed-size byte array. This allows for efficient, +/// direct mapping onto packet data. +/// +/// # Arguments +/// +/// * `$ty:ident`: The name for the new CID struct (e.g., `QuicDstConnLong`). +/// * `$doc:literal`: A string literal describing the CID type, used to generate +/// the struct's documentation (e.g., `"Destination (Long Hdr)"`). +/// * `$with_len_on_wire:tt`: A boolean (`true` or `false`) that controls the +/// `serde` (de)serialization format. +/// - `true`: The serialized format is `[length_byte, ...cid_bytes]`. This is +/// used for CIDs in Long Headers. +/// - `false`: The serialized format is just `[...cid_bytes]`. This is used for +/// the Destination CID in Short Headers, where the length is implicit. +/// +/// # Generated Code +/// +/// This macro generates the following for the given `$ty`: +/// - A `#[repr(C, packed)]` struct containing `len: u8` and `bytes: [u8; QUIC_MAX_CID_LEN]`. +/// - An `impl` block with methods like `new()`, `len()`, `as_slice()`, and `set()`. +/// - Implementations for `Default`, `Debug`, `PartialEq`, `Eq`, and `Hash`. +/// - If the `serde` feature is enabled, implementations for `Serialize` and `Deserialize` +/// that respect the `$with_len_on_wire` argument. macro_rules! impl_cid_common { - ($t:ident, $doc_name:literal, $with_len_on_wire:tt) => { - #[doc = concat!("Wrapper for a QUIC ", $doc_name, " Connection ID.")] - /// - /// Stores the CID bytes in a fixed-size buffer (`QUIC_MAX_CID_LEN`) - /// and tracks its actual current length via an internal `len` field. - /// - /// For CIDs in Long Headers (like `QuicDstConnLong`, `QuicSrcConnLong`), the length byte - /// is also present on the wire preceding the CID bytes. The `len` field in these structs - /// directly corresponds to this on-wire length byte. + ($ty:ident, $doc:literal, $with_len_on_wire:tt) => { + #[doc = concat!("Wrapper for a QUIC ", $doc, " Connection‑ID.")] /// - /// For CIDs in Short Headers (like `QuicDstConnShort`), the length is implicit or contextually - /// known (e.g., from connection state). The internal `len` field in `QuicDstConnShort` - /// still tracks the CID's actual length for consistency and for `as_slice()`, but this - /// `len` field itself is *not* serialized as a separate byte on the wire for Short Header CIDs. - /// - /// # Examples - /// - /// ``` - /// // This example is generic. See specific types like QuicDstConnLong or QuicDstConnShort - /// // for more tailored examples. - /// use network_types::quic::{QuicDstConnLong, QUIC_MAX_CID_LEN}; - /// - /// // Create a new, empty CID - /// let mut cid = QuicDstConnLong::new(); - /// assert_eq!(cid.len(), 0); - /// assert!(cid.is_empty()); - /// - /// // Set the CID value - /// let cid_data = [0x01, 0x02, 0x03, 0x04]; - /// cid.set(&cid_data); - /// assert_eq!(cid.len(), 4); - /// assert_eq!(cid.as_slice(), &cid_data); - /// - /// // Example eBPF context considerations: - /// // If this CID struct (e.g., QuicDstConnLong) matches a part of your packet structure - /// // that includes the length byte on the wire: - /// // ```no_run - /// # use core::ptr; - /// # #[repr(C, packed)] struct MyPacketPart { a_cid: QuicDstConnLong } - /// # let base_ptr: *const MyPacketPart = ptr::null(); // Ptr to packet data part - /// // let cid_from_packet = unsafe { ptr::read_volatile(&((*base_ptr).a_cid)) }; - /// // assert!(cid_from_packet.len() <= network_types::quic::QUIC_MAX_CID_LEN as u8); - /// // Process `cid_from_packet.as_slice()`... - /// // ``` - /// // - /// // If the CID struct is like QuicDstConnShort where 'len' is not on wire with the CID: - /// // You would determine 'known_cid_len' from context, read 'known_cid_len' bytes, - /// // then use `cid.set()` to populate a stack instance of QuicDstConnShort. - /// // ```no_run - /// # use network_types::quic::QuicDstConnShort; - /// # let packet_cid_bytes_ptr: *const u8 = core::ptr::null(); // Ptr to raw CID bytes in packet - /// # let known_cid_len: usize = 8; // Length from eBPF map or connection state - /// let mut my_cid_short = QuicDstConnShort::new(); - /// if known_cid_len > 0 && known_cid_len <= QUIC_MAX_CID_LEN { - /// let mut cid_buffer = [0u8; QUIC_MAX_CID_LEN]; - /// // In eBPF: unsafe { bpf_probe_read_kernel(...) } to copy bytes here - /// // For this example, assume cid_buffer is populated from packet_cid_bytes_ptr - /// // unsafe { core::ptr::copy_nonoverlapping(packet_cid_bytes_ptr, cid_buffer.as_mut_ptr(), known_cid_len); } - /// my_cid_short.set(&cid_buffer[..known_cid_len]); - /// // assert_eq!(my_cid_short.len(), known_cid_len as u8); - /// } - /// // ``` /// ``` + /// This struct holds a variable-length Connection ID in a fixed-size buffer, + /// suitable for use in `no_std` environments like eBPF programs. It is + /// generated by the `impl_cid_common!` macro. #[repr(C, packed)] #[derive(Copy, Clone)] - pub struct $t { + pub struct $ty { len: u8, bytes: [u8; QUIC_MAX_CID_LEN], } - impl $t { - pub const LEN: usize = core::mem::size_of::(); + impl $ty { + /// The total size of the struct in memory. + pub const LEN: usize = mem::size_of::(); - /// Construct a new (empty) ID – all bytes zero, length 0. - /// - /// # Returns - /// A new, zero-initialized CID instance with `len = 0`. - #[inline] + /// Creates a new, empty Connection ID. + #[inline(always)] pub const fn new() -> Self { Self { len: 0, - bytes: [0u8; QUIC_MAX_CID_LEN], + bytes: [0; QUIC_MAX_CID_LEN], } } - /// Current byte‑length of the CID. - /// - /// # Returns - /// The actual length of the CID data stored, from 0 to `QUIC_MAX_CID_LEN`. - #[inline] + + /// Returns the actual length of the Connection ID in bytes. + #[inline(always)] pub const fn len(&self) -> u8 { self.len } - /// True if CID length is zero. - /// - /// # Returns - /// `true` if the CID has a length of 0, `false` otherwise. - #[inline] + + /// Returns `true` if the Connection ID has a length of zero. + #[inline(always)] pub const fn is_empty(&self) -> bool { self.len == 0 } - /// Slice containing only the used part of the CID. - /// - /// # Returns - /// A `&[u8]` slice representing the actual CID bytes. - /// The slice length is determined by `self.len()`. - /// - /// # Safety - /// This method uses `get_unchecked` for performance, relying on the invariant - /// that `self.len` is always `<= QUIC_MAX_CID_LEN`, which is maintained by `set()`. - #[inline] + + /// Returns a slice of the actual Connection ID bytes. + #[inline(always)] pub fn as_slice(&self) -> &[u8] { + // Safety: `self.len` is guaranteed by the `set` method to be <= `QUIC_MAX_CID_LEN`. unsafe { self.bytes.get_unchecked(..self.len as usize) } } - /// Mutable slice containing only the used part of the CID. - /// - /// # Returns - /// A `&mut [u8]` mutable slice representing the actual CID bytes. - /// The slice length is determined by `self.len()`. - /// - /// # Safety - /// This method uses `get_unchecked_mut` for performance, relying on the invariant - /// that `self.len` is always `<= QUIC_MAX_CID_LEN`, which is maintained by `set()`. - #[inline] + + /// Returns a mutable slice of the actual Connection ID bytes. + #[inline(always)] pub fn as_mut_slice(&mut self) -> &mut [u8] { + // Safety: `self.len` is guaranteed by the `set` method to be <= `QUIC_MAX_CID_LEN`. unsafe { self.bytes.get_unchecked_mut(..self.len as usize) } } - /// Set the ID from a byte‑slice. - /// Excess bytes from `data` are truncated if `data.len() > QUIC_MAX_CID_LEN`. - /// Bytes within the internal buffer beyond the new length (`n`) are zeroed - /// to ensure consistent behavior for equality checks and hashing. + + /// Sets the Connection ID from a slice. /// - /// # Parameters - /// * `data`: A byte slice containing the new CID data. + /// The data is truncated if it is longer than `QUIC_MAX_CID_LEN`. + #[inline(always)] pub fn set(&mut self, data: &[u8]) { let n = cmp::min(data.len(), QUIC_MAX_CID_LEN); - // Safety: `n` is guaranteed to be `<= QUIC_MAX_CID_LEN`. - // `self.bytes.as_mut_ptr()` is valid for writes up to `QUIC_MAX_CID_LEN`. - // `data.as_ptr()` is valid for reads up to `data.len()`. - // `copy_nonoverlapping` is safe as `n` respects the shorter of these lengths. - unsafe { - ptr::copy_nonoverlapping(data.as_ptr(), self.bytes.as_mut_ptr(), n); - } - if n < QUIC_MAX_CID_LEN { - // Zero the remaining padding to guarantee deterministic equality & hashing. - // Safety: `n < QUIC_MAX_CID_LEN` ensures `self.bytes.as_mut_ptr().add(n)` is valid - // and `QUIC_MAX_CID_LEN - n` is the correct count of bytes to zero. - unsafe { - ptr::write_bytes(self.bytes.as_mut_ptr().add(n), 0, QUIC_MAX_CID_LEN - n); - } - } + // Safety: `n` is bounded by `QUIC_MAX_CID_LEN`, ensuring no buffer overflow. + unsafe { raw_copy(data.as_ptr(), self.bytes.as_mut_ptr(), n) }; self.len = n as u8; } } - - impl Default for $t { + impl Default for $ty { + #[inline(always)] fn default() -> Self { Self::new() } } - impl fmt::Debug for $t { + impl fmt::Debug for $ty { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}(", stringify!($t))?; - for (i, b_val) in self.as_slice().iter().enumerate() { - if i > 0 { - write!(f, ":")?; + f.write_str(stringify!($ty))?; + f.write_str("(")?; + for (i, b) in self.as_slice().iter().enumerate() { + if i != 0 { + f.write_str(":")?; } - write!(f, "{:02x}", b_val)?; + write!(f, "{:02x}", b)?; } - write!(f, ")") + f.write_str(")") } } - impl cmp::PartialEq for $t { + impl PartialEq for $ty { + #[inline(always)] fn eq(&self, other: &Self) -> bool { - self.len == other.len && self.as_slice() == other.as_slice() + self.as_slice() == other.as_slice() } } - impl Eq for $t {} - impl hash::Hash for $t { - fn hash(&self, state: &mut H) { - self.len.hash(state); - state.write(self.as_slice()); + impl Eq for $ty {} + impl Hash for $ty { + #[inline(always)] + fn hash(&self, h: &mut H) { + self.len.hash(h); + h.write(self.as_slice()); } } #[cfg(feature = "serde")] const _: () = { - // Create a new scope for each expansion use serde::{ de::{self, Visitor}, - ser::SerializeStruct, Deserializer, Serializer, }; - // Assumes helper structs are in `crate::quic::cid_serde_helpers` + + use crate::quic::cid_serde_helpers; + struct CidVisitor; impl<'de> Visitor<'de> for CidVisitor { - type Value = $t; - + type Value = $ty; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - if $with_len_on_wire { - write!(f, "length-prefixed QUIC Connection-ID (len, bytes)") - } else { - write!(f, "raw QUIC Connection-ID bytes") - } - } - - fn visit_seq(self, mut seq: A) -> Result<$t, A::Error> - where - A: de::SeqAccess<'de>, - { - let len: u8 = seq.next_element()?.ok_or_else(|| { - de::Error::invalid_length(0, &"Missing CID len in sequence") - })?; - let bytes_slice: &[u8] = seq.next_element()?.ok_or_else(|| { - de::Error::invalid_length(1, &"Missing CID bytes in sequence") - })?; - - if bytes_slice.len() != len as usize { - return Err(de::Error::invalid_value( - de::Unexpected::Bytes(bytes_slice), - &crate::quic::cid_serde_helpers::ExpectedCidBytesInfo { len }, - )); - } - if len > QUIC_MAX_CID_LEN as u8 { - return Err(de::Error::invalid_length( - len as usize, - &crate::quic::cid_serde_helpers::LengthExceedsMaxError { - value: len as usize, - max: QUIC_MAX_CID_LEN, - field_name: "CID length", - }, - )); - } - let mut id = $t::new(); - // REPLACED: Use the slice directly. - id.set(bytes_slice); - id.len = len; // Ensure length is set after data. - Ok(id) + f.write_str("a byte slice for a QUIC Connection‑ID") } - - fn visit_bytes(self, v: &[u8]) -> Result<$t, E> + fn visit_bytes(self, v: &[u8]) -> Result<$ty, E> where E: de::Error, { - if v.len() > QUIC_MAX_CID_LEN { + let data = if $with_len_on_wire { + if v.is_empty() { + return Err(de::Error::invalid_length(0, &"empty CID")); + } + let l = v[0] as usize; + if v.len() != l + 1 { + return Err(de::Error::invalid_value( + de::Unexpected::Bytes(v), + &"malformed length‑prefixed CID", + )); + } + &v[1..] + } else { + v + }; + + if data.len() > QUIC_MAX_CID_LEN { return Err(de::Error::invalid_length( - v.len(), - &crate::quic::cid_serde_helpers::LengthExceedsMaxError { - value: v.len(), + data.len(), + &cid_serde_helpers::LengthExceedsMaxError { + value: data.len(), max: QUIC_MAX_CID_LEN, field_name: "CID length", }, )); } - let mut id = $t::new(); - id.set(v); + let mut id = $ty::new(); + id.set(data); Ok(id) } } - impl serde::Serialize for $t { - fn serialize(&self, serializer: S) -> Result + impl serde::Serialize for $ty { + fn serialize(&self, ser: S) -> Result where S: Serializer, { if $with_len_on_wire { - let mut st = serializer.serialize_struct(stringify!($t), 2)?; - st.serialize_field("len", &self.len)?; - // REPLACED: `&Bytes::new(...)` with the raw slice. `serde` handles it correctly. - st.serialize_field("bytes", self.as_slice())?; - st.end() + let mut buf = [0u8; 1 + QUIC_MAX_CID_LEN]; + buf[0] = self.len(); + // Safety: `self.len` is always <= `QUIC_MAX_CID_LEN`. + unsafe { + raw_copy( + self.bytes.as_ptr(), + buf[1..].as_mut_ptr(), + self.len as usize, + ) + }; + ser.serialize_bytes(&buf[..1 + self.len as usize]) } else { - serializer.serialize_bytes(self.as_slice()) + ser.serialize_bytes(self.as_slice()) } } } - - impl<'de> serde::Deserialize<'de> for $t { - fn deserialize(deserializer: D) -> Result + impl<'de> serde::Deserialize<'de> for $ty { + fn deserialize(deser: D) -> Result where D: Deserializer<'de>, { - if $with_len_on_wire { - // For tuple, it means we expect fixed structure [len, bytes_obj] - // This matches how serialize_struct behaves for tuple-like struct. - deserializer.deserialize_tuple(2, CidVisitor) - } else { - deserializer.deserialize_bytes(CidVisitor) - } + deser.deserialize_bytes(CidVisitor) } } }; @@ -426,172 +382,64 @@ impl_cid_common!(QuicDstConnLong, "Destination (Long Hdr)", true); impl_cid_common!(QuicSrcConnLong, "Source (Long Hdr)", true); impl_cid_common!(QuicDstConnShort, "Destination (Short Hdr)", false); -/// Invariant fields specific to QUIC **Long Headers**. -/// -/// Contains the QUIC version and variable-length Destination and Source Connection IDs. -/// -/// # Examples -/// -/// ``` -/// use network_types::quic::{QuicHdrLong, QuicDstConnLong, QuicSrcConnLong}; -/// -/// let mut long_hdr_fields = QuicHdrLong::default(); -/// long_hdr_fields.set_version(0x00000001); // QUIC v1 -/// -/// let dcid_data = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]; -/// let scid_data = [0xAA, 0xBB, 0xCC, 0xDD]; -/// -/// long_hdr_fields.dst.set(&dcid_data); -/// long_hdr_fields.src.set(&scid_data); -/// -/// assert_eq!(long_hdr_fields.version(), 0x00000001); -/// assert_eq!(long_hdr_fields.dst.len(), 8); -/// assert_eq!(long_hdr_fields.dst.as_slice(), &dcid_data); -/// assert_eq!(long_hdr_fields.src.len(), 4); -/// assert_eq!(long_hdr_fields.src.as_slice(), &scid_data); -/// -/// // In an eBPF program, this struct would typically be part of a `QuicHdr`'s `inner.long` -/// // field. If `quic_hdr_ptr` is a pointer to a `QuicHdr` (known to be Long Header) -/// // in packet memory: -/// // ```no_run -/// # use network_types::quic::{QuicHdr, QuicHeaderType}; -/// # use core::ptr; -/// # let mut quic_hdr_on_stack = QuicHdr::new(QuicHeaderType::QuicLong); // Example stack instance -/// # let quic_hdr_ptr: *const QuicHdr = &quic_hdr_on_stack; -/// // // Assume quic_hdr_ptr is valid and points to a Long Header QuicHdr. -/// // let long_specific_ptr: *const QuicHdrLong = unsafe { &(*quic_hdr_ptr).inner.long }; -/// // let version = unsafe { (*long_specific_ptr).version() }; -/// // let dcid_len = unsafe { (*long_specific_ptr).dst.len() }; -/// // // Access to CID bytes via: unsafe { (*long_specific_ptr).dst.as_slice() } -/// // // Important: This assumes `quic_hdr_ptr` and its `inner.long` field are correctly -/// // // populated, which requires careful parsing in eBPF. -/// // ``` -/// ``` +/// The inner payload of a QUIC Long Header. #[repr(C, packed)] #[derive(Copy, Clone, Default, Debug)] pub struct QuicHdrLong { - /// QUIC Version (e.g., `0x00000001` for v1). Network byte order. + /// The QUIC protocol version. pub version: [u8; 4], - /// Destination Connection ID, including its on-wire length byte (managed by `QuicDstConnLong`). + /// The Destination Connection ID. pub dst: QuicDstConnLong, - /// Source Connection ID, including its on-wire length byte (managed by `QuicSrcConnLong`). + /// The Source Connection ID. pub src: QuicSrcConnLong, } impl QuicHdrLong { - pub const LEN: usize = core::mem::size_of::(); - - /// The minimum length of a Long Header on the wire, consisting of: - /// 1 (First Byte, part of `QuicHdr`) + 4 (Version) + 1 (DCID Len Byte) + 1 (SCID Len Byte) = 7 bytes. - /// This does not include any actual CID data bytes. + /// The size of the struct in memory. + pub const LEN: usize = mem::size_of::(); + /// The minimum possible size of a Long Header on the wire. + /// (1 byte first_byte + 4-byte version + 1 byte dcil + 1 byte scil) pub const MIN_LEN_ON_WIRE: usize = 7; - /// Gets the QUIC version in host byte order. - /// - /// # Returns - /// The 32-bit QUIC version number. - #[inline] + /// Gets the version as a `u32`. + #[inline(always)] pub fn version(&self) -> u32 { u32::from_be_bytes(self.version) } - /// Sets the QUIC version. - /// - /// # Parameters - /// * `v`: The QUIC version in host byte order. It will be stored in network byte order. - #[inline] + + /// Sets the version from a `u32`. + #[inline(always)] pub fn set_version(&mut self, v: u32) { self.version = v.to_be_bytes(); } } -/// Invariant fields specific to QUIC **Short Headers** (1-RTT packets). -/// -/// Contains the Destination Connection ID. The length of the DCID is not -/// encoded directly in the short header; it must be known from the connection context. -/// QUIC v1 Short Headers do not have a Source Connection ID. -/// -/// # Examples -/// -/// ``` -/// use network_types::quic::{QuicHdrShort, QuicDstConnShort}; -/// -/// let mut short_hdr_fields = QuicHdrShort::default(); -/// -/// let dcid_data = [0x11, 0x22, 0x33, 0x44, 0x55]; -/// // For short headers, the length of the DCID is known contextually. -/// // The `QuicDstConnShort` struct's `set` method will store this data -/// // and internally track its length. -/// short_hdr_fields.dst.set(&dcid_data); -/// -/// assert_eq!(short_hdr_fields.dst.len(), 5); -/// assert_eq!(short_hdr_fields.dst.as_slice(), &dcid_data); -/// -/// // In an eBPF program, `QuicHdrShort` would be part of `QuicHdr.inner.short`. -/// // The DCID bytes would be read from the packet based on a contextually known length. -/// // That length would also be used to initialize `QuicHdr.header_type` -/// // (e.g., `QuicHeaderType::QuicShort { dc_id_len: known_len }`). -/// // -/// // ```no_run -/// # use network_types::quic::{QuicHdr, QuicHeaderType, QUIC_MAX_CID_LEN}; -/// # use core::ptr; -/// # let known_dcid_len_from_context = 5u8; -/// # let mut quic_hdr_on_stack = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: known_dcid_len_from_context }); -/// // // Populate quic_hdr_on_stack.inner.short.dst from packet data... -/// // // For example, if packet_dcid_ptr points to the DCID bytes in the packet: -/// # let packet_dcid_ptr: *const u8 = core::ptr::null(); // Example pointer to DCID data in packet -/// // if known_dcid_len_from_context > 0 { -/// // let mut temp_cid_buf = [0u8; QUIC_MAX_CID_LEN]; -/// // // In eBPF: unsafe { bpf_probe_read_kernel(temp_cid_buf.as_mut_ptr(), known_dcid_len_from_context as u32, packet_dcid_ptr) }; -/// // // For example purposes, assume temp_cid_buf is populated: -/// // // unsafe { ptr::copy_nonoverlapping(packet_dcid_ptr, temp_cid_buf.as_mut_ptr(), known_dcid_len_from_context as usize); } -/// // unsafe { quic_hdr_on_stack.inner.short.dst.set(&temp_cid_buf[..known_dcid_len_from_context as usize]); } -/// // } -/// // -/// // // Accessing via the QuicHdr instance: -/// // assert_eq!(quic_hdr_on_stack.dc_id().len(), known_dcid_len_from_context as usize); -/// // ``` -/// ``` +/// The inner payload of a QUIC Short Header. #[repr(C, packed)] #[derive(Copy, Clone, Default, Debug)] pub struct QuicHdrShort { - /// Destination Connection ID. Its length is determined contextually. - /// `QuicDstConnShort` internally tracks its `len` for `as_slice` but this `len` - /// is not part of the on-wire format for the Short Header DCID itself. + /// The Destination Connection ID. pub dst: QuicDstConnShort, } -impl QuicHdrShort { - pub const LEN: usize = core::mem::size_of::(); - /// The minimum length of a Short Header on the wire, consisting of: - /// 1 (First Byte, part of `QuicHdr`) + 0 (DCID bytes, if DCID len is 0) = 1 byte. +impl QuicHdrShort { + /// The size of the struct in memory. + pub const LEN: usize = mem::size_of::(); + /// The minimum possible size of a Short Header on the wire. + /// (1 byte first_byte) pub const MIN_LEN_ON_WIRE: usize = 1; } -/// Union to hold either Long or Short header-specific data. -/// -/// This allows `QuicHdr` to have a fixed size while representing variable structures. -/// Access must be guarded by checking `QuicHdr::is_long_header()` or `QuicHdr::header_type`. -/// -/// # Note on eBPF Usage -/// -/// In eBPF programs, direct access to union fields (e.g., `hdr.inner.long` or `hdr.inner.short`) -/// must be done with extreme care, ensuring that the `QuicHdr` instance's `header_type` -/// correctly reflects the actual packet type being processed. The `QuicHdr` safe accessor methods -/// (like `version()`, `dc_id()`, etc.) handle these checks. -/// -/// If constructing a `QuicHdr` from packet data in eBPF, the `inner` union would be populated -/// after determining the header type and reading the relevant bytes (version, CIDs, etc.) -/// from the packet. The `QuicHdr::new()` method initializes the union appropriately based -/// on the provided `QuicHeaderType`. +/// A `union` to hold either a Long or Short QUIC header payload. #[repr(C, packed)] #[derive(Copy, Clone)] pub union QuicHdrUn { - /// Data for a Long Header. + /// Long Header variant. pub long: QuicHdrLong, - /// Data for a Short Header. + /// Short Header variant. pub short: QuicHdrShort, } - impl Default for QuicHdrUn { + #[inline(always)] fn default() -> Self { Self { long: QuicHdrLong::default(), @@ -604,21 +452,16 @@ impl fmt::Debug for QuicHdrUn { } } -/// Discriminator for `QuicHdr` to indicate active header form and necessary context. -/// This is a logical field, not directly part of the wire format itself, but aids in (de)serialization -/// and safe access to the `QuicHdrUn` union. +/// The logical type of QUIC header. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum QuicHeaderType { - /// Indicates a Long Header. All length information for CIDs is in the packet. + /// A Long Header. All connection ID lengths are encoded on the wire. QuicLong, - /// Indicates a Short Header. - /// `dc_id_len` is the contextual length of the Destination Connection ID, as this - /// length is not present on the wire for Short Headers. It must be known by the - /// endpoint processing the packet. QUIC v1 Short headers do not have a Source CID. + /// A Short Header. The destination connection ID length must be known from context. QuicShort { dc_id_len: u8 }, } -/// Represents a QUIC header. +/// A raw, on-the-wire representation of a QUIC header. /// /// This struct is intended to provide access to QUIC header fields. /// The actual parsing and interpretation of variable-length fields (like CIDs, @@ -652,908 +495,698 @@ pub enum QuicHeaderType { /// | Protected Payload (*) ... /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// -/// # Examples -/// -/// ## Creating and using a Long Header -/// ``` -/// use network_types::quic::{QuicHdr, QuicHeaderType, QuicPacketType, QUIC_MAX_CID_LEN}; -/// -/// // Create a new Long Header (e.g., for an Initial packet) -/// let mut long_hdr = QuicHdr::new(QuicHeaderType::QuicLong); -/// -/// // Set Long Header specific fields -/// assert!(long_hdr.set_long_packet_type(QuicPacketType::Initial).is_ok()); -/// assert!(long_hdr.set_version(0x00000001).is_ok()); // QUIC v1 -/// assert!(long_hdr.set_packet_number_length_long(4).is_ok()); // 4-byte packet number -/// -/// let dcid_data = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]; -/// let scid_data = [0xAA, 0xBB, 0xCC, 0xDD]; -/// long_hdr.set_dc_id(&dcid_data); // Sets DCID and its length (8) -/// assert!(long_hdr.set_sc_id(&scid_data).is_ok()); // Sets SCID and its length (4) -/// -/// assert!(long_hdr.is_long_header()); -/// assert_eq!(long_hdr.long_packet_type(), Ok(QuicPacketType::Initial)); -/// assert_eq!(long_hdr.version(), Ok(0x00000001)); -/// assert_eq!(long_hdr.dc_id(), &dcid_data); -/// assert_eq!(long_hdr.sc_id().unwrap(), &scid_data); -/// assert_eq!(long_hdr.dc_id_len_on_wire(), Ok(8)); -/// assert_eq!(long_hdr.sc_id_len_on_wire(), Ok(4)); -/// -/// // The first_byte is also partially set by `new` and field setters: -/// // Example: (Form | Fixed | Type | Reserved | PN Len) -/// // (1 | 1 | 00 | 00 | 11) = 0b11000011 = 0xC3 -/// // Actual first_byte depends on reserved bits and PN length settings. -/// // long_hdr.set_reserved_bits_long(0).unwrap(); // Explicitly set reserved bits -/// // assert_eq!(long_hdr.first_byte(), 0xC3); // If PN Len is 4 (encoded 3) and Type 0 -/// ``` +/// This struct is designed to be safely loaded directly from packet data in +/// `no_std` environments like eBPF. After loading, call [`QuicHdr::parse()`] +/// to get a [`ParsedQuicHdr`], which provides a safe API for accessing header fields. /// -/// ## Creating and using a Short Header -/// ``` -/// use network_types::quic::{QuicHdr, QuicHeaderType, QUIC_MAX_CID_LEN}; +/// # Example (in an `aya_ebpf` kernel-space program) /// -/// // For Short Headers, the DCID length is not on the wire, so it must be known. -/// let dcid_len_short: u8 = 8; -/// let mut short_hdr = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: dcid_len_short }); +/// This example demonstrates how to parse a QUIC header from a UDP packet +/// within a TC (Traffic Control) eBPF program. /// -/// // Set Short Header specific fields -/// assert!(short_hdr.set_short_spin_bit(true).is_ok()); -/// assert!(short_hdr.set_short_key_phase(false).is_ok()); -/// assert!(short_hdr.set_short_packet_number_length(2).is_ok()); // 2-byte packet number +/// ```no_run +/// # use aya_ebpf::{programs::TcContext, macros::classifier}; +/// # use network_types::{ +/// # eth::EthHdr, +/// # ip::{IpProto, Ipv4Hdr}, +/// # udp::UdpHdr, +/// # quic::{QuicHdr, QuicHdrError}, +/// # }; +/// # +/// #[classifier] +/// pub fn my_quic_parser(ctx: TcContext) -> i32 { +/// match try_my_quic_parser(ctx) { +/// Ok(ret) => ret, +/// Err(_) => 1, // TC_ACT_SHOT +/// } +/// } /// -/// let dcid_data_short = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]; -/// // Ensure dcid_data_short matches dcid_len_short if providing full slice -/// short_hdr.set_dc_id(&dcid_data_short[..dcid_len_short as usize]); +/// fn try_my_quic_parser(ctx: TcContext) -> Result { +/// // Assume offsets for Eth, IP, and UDP headers are already calculated. +/// // A real program would need to handle dynamic IP header lengths. +/// let udp_payload_offset = EthHdr::LEN + Ipv4Hdr::LEN + UdpHdr::LEN; /// -/// assert!(!short_hdr.is_long_header()); -/// assert_eq!(short_hdr.short_spin_bit(), Ok(true)); -/// assert_eq!(short_hdr.dc_id_effective_len(), dcid_len_short); -/// assert_eq!(short_hdr.dc_id(), &dcid_data_short[..dcid_len_short as usize]); +/// // Load the raw QUIC header. `ctx.load` will handle bounds checks. +/// let mut quic_hdr: QuicHdr = ctx.load(udp_payload_offset).map_err(|_| ())?; /// -/// // Attempting to access Long Header fields will fail -/// assert!(short_hdr.version().is_err()); -/// assert!(short_hdr.sc_id().is_err()); -/// ``` +/// // For Short Headers, the DCID length is not on the wire. The eBPF +/// // program must know it from context (e.g., from connection tracking). +/// // Here, we'll assume a length of 8. +/// const EXPECTED_DCID_LEN_FOR_SHORT_HDR: u8 = 8; /// -/// ## eBPF Context Example Snippet -/// ```no_run -/// use core::{mem, ptr, cmp}; -/// use aya_ebpf::programs::TcContext; -/// use aya_log_ebpf::debug as aya_ebpf_debug; // Import the debug macro appropriately -/// use network_types::eth::EthHdr; -/// use network_types::ip::Ipv4Hdr; // Assuming IPv4 for simplicity -/// // Placeholder for UDP header length if not using a full UdpHdr type -/// const UDP_HDR_LEN: usize = 8; -/// use network_types::quic::{QuicHdr, QuicHeaderType, QUIC_MAX_CID_LEN, QuicHdrError, QuicPacketType}; +/// // Parse the raw header into a safe, logical view. +/// let parsed = quic_hdr.parse(EXPECTED_DCID_LEN_FOR_SHORT_HDR).map_err(|_| ())?; /// -/// fn handle_quic_packet(ctx: &TcContext) -> Result { -/// let data_start = ctx.data(); -/// let data_end = ctx.data_end(); -/// // Calculate offset to where QUIC header might start -/// // This assumes Eth + IPv4 + UDP. Adjust for IPv6 or other encapsulations. -/// let quic_offset = EthHdr::LEN + Ipv4Hdr::LEN + UDP_HDR_LEN; -/// let quic_base_ptr = unsafe { (data_start as *const u8).add(quic_offset) }; -/// // Ensure there's at least one byte for the QUIC header (first_byte) -/// if unsafe { quic_base_ptr.add(1) } > (data_end as *const u8) { -/// aya_ebpf_debug!(ctx, "QUIC packet too short for first_byte"); -/// return Err(()); -/// } -/// let first_byte = unsafe { ptr::read_volatile(quic_base_ptr) }; -/// let mut hdr_on_stack: QuicHdr; -/// let mut current_parse_offset: usize = 1; // Start parsing after the first_byte -/// if (first_byte & 0x80) != 0 { // Long Header -/// hdr_on_stack = QuicHdr::new(QuicHeaderType::QuicLong); -/// hdr_on_stack.set_first_byte(first_byte); // Set first_byte, includes type, PN len etc. -/// // --- Parse Version (4 bytes) --- -/// let version_offset = current_parse_offset; -/// if unsafe { quic_base_ptr.add(version_offset + 4) } > (data_end as *const u8) { -/// aya_ebpf_debug!(ctx, "QUIC Long: Packet too short for version"); -/// return Err(()); -/// } -/// let mut ver_bytes = [0u8; 4]; -/// unsafe { -/// // In real eBPF, use bpf_probe_read_kernel or ctx.read_at helper if available & safe -/// ptr::copy_nonoverlapping(quic_base_ptr.add(version_offset), ver_bytes.as_mut_ptr(), 4); -/// } -/// if hdr_on_stack.set_version(u32::from_be_bytes(ver_bytes)).is_err() { -/// // Should not happen if type is QuicLong, but good practice -/// aya_ebpf_debug!(ctx, "QUIC Long: Failed to set version internally"); return Err(()); -/// } -/// current_parse_offset += 4; -/// // --- Parse DCID Len (1 byte) & DCID --- -/// let dcil_offset = current_parse_offset; -/// if unsafe { quic_base_ptr.add(dcil_offset + 1) } > (data_end as *const u8) { -/// aya_ebpf_debug!(ctx, "QUIC Long: Packet too short for DCIL"); return Err(()); -/// } -/// let dcil = unsafe { ptr::read_volatile(quic_base_ptr.add(dcil_offset)) }; -/// current_parse_offset += 1; -/// if dcil as usize > QUIC_MAX_CID_LEN { -/// aya_ebpf_debug!(ctx, "QUIC Long: DCIL {} exceeds max {}", dcil, QUIC_MAX_CID_LEN); return Err(()); -/// } -/// if unsafe { quic_base_ptr.add(current_parse_offset + dcil as usize) } > (data_end as *const u8) { -/// aya_ebpf_debug!(ctx, "QUIC Long: Packet too short for DCID data (len {})", dcil); return Err(()); -/// } -/// let mut dcid_buf = [0u8; QUIC_MAX_CID_LEN]; // Max buffer -/// if dcil > 0 { -/// unsafe { -/// ptr::copy_nonoverlapping(quic_base_ptr.add(current_parse_offset), dcid_buf.as_mut_ptr(), dcil as usize); -/// } -/// } -/// hdr_on_stack.set_dc_id(&dcid_buf[..dcil as usize]); -/// current_parse_offset += dcil as usize; -/// let scil_offset = current_parse_offset; -/// if unsafe { quic_base_ptr.add(scil_offset + 1) } > (data_end as *const u8) { -/// aya_ebpf_debug!(ctx, "QUIC Long: Packet too short for SCIL"); return Err(()); -/// } -/// let scil = unsafe { ptr::read_volatile(quic_base_ptr.add(scil_offset)) }; -/// current_parse_offset += 1; -/// if scil as usize > QUIC_MAX_CID_LEN { -/// aya_ebpf_debug!(ctx, "QUIC Long: SCIL {} exceeds max {}", scil, QUIC_MAX_CID_LEN); return Err(()); -/// } -/// if unsafe { quic_base_ptr.add(current_parse_offset + scil as usize) } > (data_end as *const u8) { -/// aya_ebpf_debug!(ctx, "QUIC Long: Packet too short for SCID data (len {})", scil); return Err(()); -/// } -/// let mut scid_buf = [0u8; QUIC_MAX_CID_LEN]; // Max buffer -/// if scil > 0 { -/// unsafe { -/// ptr::copy_nonoverlapping(quic_base_ptr.add(current_parse_offset), scid_buf.as_mut_ptr(), scil as usize); -/// } -/// } -/// if hdr_on_stack.set_sc_id(&scid_buf[..scil as usize]).is_err() { -/// aya_ebpf_debug!(ctx, "QUIC Long: Failed to set SCID internally"); return Err(()); -/// } -/// current_parse_offset += scil as usize; -/// // Long header successfully parsed into hdr_on_stack -/// if let Ok(pkt_type) = hdr_on_stack.long_packet_type() { -/// aya_ebpf_debug!(ctx, "QUIC Long: Type={}, Ver={}, DCID_len={}, SCID_len={}", -/// pkt_type as u8, hdr_on_stack.version().unwrap_or(0), dcil, scil); -/// } -/// } else { // Short Header -/// // For Short Headers, DCID length must be known from context (e.g., connection tracking via eBPF map) -/// // For this example, let's assume a fixed length or one fetched from a hypothetical map. -/// let known_dcid_len: u8 = 8; // Example: 8-byte DCID for short header connections -/// hdr_on_stack = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: known_dcid_len }); -/// hdr_on_stack.set_first_byte(first_byte); // Set first_byte, includes spin, key, PN len etc. -/// if known_dcid_len as usize > QUIC_MAX_CID_LEN { -/// aya_ebpf_debug!(ctx, "QUIC Short: Contextual DCID len {} exceeds max", known_dcid_len); return Err(()); -/// } -/// if unsafe { quic_base_ptr.add(current_parse_offset + known_dcid_len as usize) } > (data_end as *const u8) { -/// aya_ebpf_debug!(ctx, "QUIC Short: Packet too short for DCID data (len {})", known_dcid_len); return Err(()); -/// } -/// let mut dcid_buf = [0u8; QUIC_MAX_CID_LEN]; -/// if known_dcid_len > 0 { -/// unsafe { -/// ptr::copy_nonoverlapping(quic_base_ptr.add(current_parse_offset), dcid_buf.as_mut_ptr(), known_dcid_len as usize); -/// } -/// } -/// hdr_on_stack.set_dc_id(&dcid_buf[..known_dcid_len as usize]); -/// // Note: set_dc_id for short header also updates QuicHeaderType's dc_id_len field -/// // to match the actual data set, if it was shorter than `known_dcid_len` due to QUIC_MAX_CID_LEN. -/// current_parse_offset += known_dcid_len as usize; -/// // Short header successfully parsed into hdr_on_stack -/// aya_ebpf_debug!(ctx, "QUIC Short: DCID_len_ctx={}, Spin={}", -/// known_dcid_len, hdr_on_stack.short_spin_bit().unwrap_or(false) as u8); -/// if let Some(dcid_slice_preview) = hdr_on_stack.dc_id().get(..cmp::min(4, hdr_on_stack.dc_id().len())) { -/// // Log first few bytes of DCID for example -/// // aya_log_ebpf::debug! doesn't directly support byte slice formatting easily. -/// // For actual logging of slices, you might log byte by byte or a hex representation if needed. -/// aya_ebpf_debug!(ctx, "QUIC Short: DCID preview len {}", dcid_slice_preview.len()); -/// } -/// } -/// // Now `hdr_on_stack` is populated. You can use its methods to access fields. -/// // For example, to get the Packet Number length (common for both forms, but different bits): -/// let pn_len_result = if hdr_on_stack.is_long_header() { -/// hdr_on_stack.packet_number_length_long() +/// if parsed.is_long_header() { +/// let version = parsed.version().unwrap_or(0); +/// let dc_id = parsed.dc_id(); +/// let sc_id = parsed.sc_id().unwrap_or(&[]); +/// // Do something with the Long Header info... +/// // aya_log_ebpf::info!(&ctx, "QUIC Long: v={} dcid_len={} scid_len={}", +/// // version, dc_id.len(), sc_id.len()); /// } else { -/// hdr_on_stack.short_packet_number_length() -/// }; -/// if let Ok(len) = pn_len_result { -/// aya_ebpf_debug!(ctx, "QUIC Packet Number actual length: {} bytes", len); -/// // Packet number itself would be after the CIDs (for long) or DCID (for short) -/// // and before the payload. Parsing it is beyond this header example. -/// // let _pn_offset = quic_offset + current_parse_offset; +/// let dc_id = parsed.dc_id(); +/// let spin_bit = parsed.short_spin_bit().unwrap_or(false); +/// // Do something with the Short Header info... +/// // aya_log_ebpf::info!(&ctx, "QUIC Short: dcid_len={} spin={}", +/// // dc_id.len(), spin_bit); /// } -/// // The actual packet number bytes follow the header fields parsed so far. -/// // Accessing them requires reading `len` bytes from `quic_base_ptr.add(current_parse_offset)`. -/// Ok(0) +/// +/// Ok(0) // TC_ACT_OK /// } -/// // Important Note: The use of `ptr::copy_nonoverlapping` assumes direct memory access. -/// // In a real eBPF program, especially for TC (sk_buff context), you would typically use -/// // `ctx.load::(offset)` for fixed-size reads or `bpf_probe_read_kernel` / `bpf_probe_read_user` -/// // for reading arbitrary memory into a buffer, ensuring safety and verifier compliance. -/// // This example uses raw pointers for conceptual clarity on structure parsing. -/// // Boundary checks (`if ptr.add(len) > data_end`) are crucial. /// ``` - #[repr(C, packed)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Default)] pub struct QuicHdr { first_byte: u8, inner: QuicHdrUn, +} + +/// A safe, parsed view of a QUIC header. +/// +/// This struct is created by [`QuicHdr::parse()`] and provides methods to +/// safely access and modify the fields of the underlying header. +pub struct ParsedQuicHdr<'a> { + hdr: &'a mut QuicHdr, header_type: QuicHeaderType, } impl QuicHdr { - pub const LEN: usize = core::mem::size_of::(); - - /// Minimum on-wire size of a QUIC Long Header (1 (flags/type) + 4 (version) + 1 (DCIL byte) + 1 (SCIL byte) = 7 bytes), - /// This excludes any actual CID data bytes. + /// The total size of the struct in memory. + pub const LEN: usize = size_of::(); + /// The minimum size of a valid Long Header on the wire. pub const MIN_LONG_HDR_LEN_ON_WIRE: usize = QuicHdrLong::MIN_LEN_ON_WIRE; - - /// Minimum on-wire size of a QUIC Short Header (1 (flags/type) = 1 byte). - /// This excludes any actual DCID data bytes. + /// The minimum size of a valid Short Header on the wire. pub const MIN_SHORT_HDR_LEN_ON_WIRE: usize = QuicHdrShort::MIN_LEN_ON_WIRE; - /// Creates a new `QuicHdr` initialized for the specified `header_type`. - /// The `first_byte` is partially initialized based on the header form (Long/Short) and Fixed Bit. - /// Other bits in `first_byte` (like Packet Type, PN length encoding, Spin Bit, Key Phase) - /// must be set separately by the caller using the provided setter methods. + /// Creates a new `QuicHdr` with a specified logical type. /// /// # Parameters - /// * `header_type`: The type of QUIC header to initialize, providing context for - /// Short Header DCID length. + /// * `ht`: The desired header type (`QuicLong` or `QuicShort`). /// /// # Returns - /// A new `QuicHdr` instance. - pub fn new(header_type: QuicHeaderType) -> Self { - match header_type { - QuicHeaderType::QuicLong => { - let first_byte = HEADER_FORM_BIT | FIXED_BIT_MASK; - Self { - first_byte, - inner: QuicHdrUn { - long: QuicHdrLong::default(), - }, - header_type, - } - } + /// A new `QuicHdr` initialized with default values for the given type. + #[inline(always)] + pub fn new(ht: QuicHeaderType) -> Self { + match ht { + QuicHeaderType::QuicLong => Self { + first_byte: HEADER_FORM_BIT | FIXED_BIT_MASK, + inner: QuicHdrUn::default(), + }, QuicHeaderType::QuicShort { dc_id_len } => { - // dc_id_len captured here - let first_byte = FIXED_BIT_MASK; // Set Fixed bit (Form bit is 0) - let mut short_data = QuicHdrShort::default(); - // The dc_id_len from header_type is the primary source of truth - // for effective length. - short_data.dst.len = cmp::min(dc_id_len, QUIC_MAX_CID_LEN as u8); + let mut s = QuicHdrShort::default(); + s.dst.len = cmp::min(dc_id_len, QUIC_MAX_CID_LEN as u8); Self { - first_byte, - inner: QuicHdrUn { short: short_data }, - header_type, + first_byte: FIXED_BIT_MASK, + inner: QuicHdrUn { short: s }, } } } } - /// Gets the raw `first_byte` of the header. This byte contains several bit-packed fields. + /// Checks if the header is a Long Header based on the first bit. + #[inline(always)] + pub fn is_long_header(&self) -> bool { + (self.first_byte & HEADER_FORM_BIT) != 0 + } + + /// Parses the raw header data into a safe, logical view. + /// + /// # Parameters + /// * `dcid_len_for_short`: The expected length of the Destination Connection ID + /// if this is a Short Header. This value must be known from the connection's context, + /// as it is not encoded on the wire in Short Headers. It is ignored for Long Headers. + /// + /// # Returns + /// `Ok(ParsedQuicHdr)` on success, or a `QuicHdrError` if the header contains invalid lengths. + #[inline(always)] + pub fn parse(&mut self, dcid_len_for_short: u8) -> Result, QuicHdrError> { + let header_type = if self.is_long_header() { + // Safety: We have checked that this is a Long Header, so accessing `inner.long` is valid. + let long = unsafe { &self.inner.long }; + if long.dst.len() > QUIC_MAX_CID_LEN as u8 || long.src.len() > QUIC_MAX_CID_LEN as u8 { + return Err(QuicHdrError::InvalidLength); + } + QuicHeaderType::QuicLong + } else { + if dcid_len_for_short > QUIC_MAX_CID_LEN as u8 { + return Err(QuicHdrError::InvalidLength); + } + QuicHeaderType::QuicShort { + dc_id_len: dcid_len_for_short, + } + }; + Ok(ParsedQuicHdr { + hdr: self, + header_type, + }) + } + + /// Validates and returns a CID length. + /// + /// # Parameters + /// * `b`: The length byte to check. + /// + /// # Returns + /// `Ok(usize)` if the length is valid, `Err(QuicHdrError::InvalidLength)` otherwise. + #[inline(always)] + pub fn parse_cid_len(b: u8) -> Result { + if b as usize > QUIC_MAX_CID_LEN { + Err(QuicHdrError::InvalidLength) + } else { + Ok(b as usize) + } + } +} + +impl<'a> ParsedQuicHdr<'a> { + /// Gets the raw first byte of the QUIC header. /// /// # Returns /// The `u8` value of the first byte. - #[inline] + #[inline(always)] pub fn first_byte(&self) -> u8 { - self.first_byte + self.hdr.first_byte } - /// Sets the raw `first_byte` of the header. + /// Sets the raw first byte of the QUIC header. /// /// # Parameters - /// * `b`: The new `u8` value for the first byte. - /// - /// # Safety - /// Caller must ensure consistency of the `first_byte`'s Header Form bit - /// with the `self.header_type` discriminator. Use `set_header_type` for - /// safe structural changes if the header form bit is altered by this call. - /// It's generally safer to use specific setters like `set_long_packet_type`, etc. - /// after `QuicHdr::new()`, and then call this if you need to set the *entire* byte - /// from a pre-calculated value (e.g. from a packet). - #[inline] + /// * `b`: The `u8` value to set as the first byte. + #[inline(always)] pub fn set_first_byte(&mut self, b: u8) { - self.first_byte = b; - // Developer note: After calling this, ensure self.header_type is still valid. - // For example, if 'b' flips the HEADER_FORM_BIT, self.header_type should be updated - // via set_header_type() to match, which also reinitialized self.inner. + self.hdr.first_byte = b; } - /// Checks if the Header Form bit (the most significant bit of `first_byte`) - /// indicates a Long Header. + /// Checks if the header is a Long Header. + /// + /// This is determined by checking the Header Form bit (the most significant + /// bit of the first byte). /// /// # Returns - /// `true` if bit 7 is 1 (Long Header), `false` if 0 (Short Header). - #[inline] + /// `true` if it is a Long Header, `false` otherwise. + #[inline(always)] pub fn is_long_header(&self) -> bool { - (self.first_byte & HEADER_FORM_BIT) == HEADER_FORM_BIT + self.hdr.is_long_header() } - /// Gets the Fixed Bit (bit 6 of `first_byte`). - /// Per RFC 9000, this bit MUST be 1 for all QUIC v1 packets except Version Negotiation. + /// Gets the Fixed Bit (the second most significant bit of the first byte). + /// + /// According to RFC 9000, this bit must be set to 1. Packets where this + /// bit is 0 are not valid QUIC packets. /// /// # Returns - /// The value of the Fixed Bit (0 or 1). - #[inline] + /// The value of the fixed bit (0 or 1). + #[inline(always)] pub fn fixed_bit(&self) -> u8 { - (self.first_byte & FIXED_BIT_MASK) >> 6 + (self.hdr.first_byte & FIXED_BIT_MASK) >> 6 } - /// Gets the Long Packet Type (bits 5-4 of `first_byte`) if this is a Long Header. + /// Gets the Packet Type if this is a Long Header. /// /// # Returns - /// `Ok(QuicPacketType)` with the packet type if Long Header and type is valid, - /// `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header, - /// `Err(QuicHdrError::InvalidPacketTypeBits)` if type bits are unrecognized. - #[inline] + /// `Ok(QuicPacketType)` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] pub fn long_packet_type(&self) -> Result { - if self.is_long_header() { - // Safety: is_long_header() ensures conditions for unchecked_long_packet_type_bits are met. - let type_bits = unsafe { self.unchecked_long_packet_type_bits() }; - match type_bits { - 0x00 => Ok(QuicPacketType::Initial), - 0x01 => Ok(QuicPacketType::ZeroRTT), - 0x02 => Ok(QuicPacketType::Handshake), - 0x03 => Ok(QuicPacketType::Retry), - _ => Err(QuicHdrError::InvalidPacketTypeBits), // Should not happen with 2 bits - } - } else { - Err(QuicHdrError::InvalidHeaderForm) + if !self.is_long_header() { + return Err(QuicHdrError::InvalidHeaderForm); } + // Safety: A header form has been checked. + let bits = unsafe { self.long_packet_type_bits_unchecked() }; + QuicPacketType::try_from(bits) } - /// Gets the Reserved Bits (bits 3-2 of `first_byte`) if this is a Long Header. - /// For QUIC v1 Initial, 0-RTT, and Handshake packets, these bits MUST be 0. - /// For Retry packets, these bits are part of the "Retry Packet Type" or ODCIL. + /// Gets the Reserved Bits (bits 4-5) if this is a Long Header. /// /// # Returns - /// `Ok(u8)` with the reserved bits value (0-3) if Long Header, - /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. - #[inline] + /// `Ok(u8)` containing the 2 reserved bits if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] pub fn reserved_bits_long(&self) -> Result { - if self.is_long_header() { - // Safety: is_long_header() ensures conditions for unchecked_reserved_bits_long are met. - Ok(unsafe { self.unchecked_reserved_bits_long() }) - } else { + if !self.is_long_header() { Err(QuicHdrError::InvalidHeaderForm) + } else { + // Safety: A header form has been checked. + Ok(unsafe { self.reserved_bits_long_unchecked() }) } } - /// Gets the encoded Packet Number Length (bits 1-0 of `first_byte`) if this is a Long Header - /// (for types that include a Packet Number: Initial, 0-RTT, Handshake). - /// The value is `actual_length_in_bytes - 1`. So, 0b00 means 1 byte, 0b11 means 4 bytes. + /// Gets the encoded Packet Number Length (bits 6-7) if this is a Long Header. + /// This value is `actual_length_in_bytes - 1`. /// /// # Returns - /// `Ok(u8)` with the encoded length (0-3) if Long Header, - /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. - /// Note: This doesn't check if the specific Long Packet Type actually has a packet number. - #[inline] + /// `Ok(u8)` containing the encoded length (0-3) if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] pub fn pn_length_bits_long(&self) -> Result { - if self.is_long_header() { - // Safety: is_long_header() ensures conditions for unchecked_pn_length_bits_long are met. - Ok(unsafe { self.unchecked_pn_length_bits_long() }) - } else { + if !self.is_long_header() { Err(QuicHdrError::InvalidHeaderForm) + } else { + // Safety: A header form has been checked. + Ok(unsafe { self.pn_length_bits_long_unchecked() }) } } - /// Gets the actual Packet Number Length in bytes (1 to 4) if this is a Long Header - /// and the header type includes a packet number. + /// Gets the actual Packet Number Length in bytes (1-4) if this is a Long Header. /// /// # Returns - /// `Ok(usize)` with the actual length (1-4 bytes) if Long Header, - /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. - #[inline] + /// `Ok(usize)` containing the length if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] pub fn packet_number_length_long(&self) -> Result { - self.pn_length_bits_long().map(|bits| (bits + 1) as usize) + self.pn_length_bits_long().map(|b| (b + 1) as usize) } - /// Gets the Spin Bit (bit 5 of `first_byte`) if this is a Short Header. + /// Gets the Spin Bit (bit 2) if this is a Short Header. /// /// # Returns - /// `Ok(bool)` (`true` if bit is 1, `false` if 0) if Short Header, - /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. - #[inline] + /// `Ok(bool)` with the spin bit value if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. + #[inline(always)] pub fn short_spin_bit(&self) -> Result { - if !self.is_long_header() { - // Safety: !is_long_header() ensures conditions for unchecked_short_spin_bit are met. - Ok(unsafe { self.unchecked_short_spin_bit() }) - } else { + if self.is_long_header() { Err(QuicHdrError::InvalidHeaderForm) + } else { + // Safety: A header form has been checked. + Ok(unsafe { self.short_spin_bit_unchecked() }) } } - /// Gets the Reserved Bits (bits 4-3 of `first_byte`) if this is a Short Header. - /// These bits MUST be 0 in QUIC v1 unless an extension defines otherwise. + /// Gets the Reserved Bits (bits 3-4) if this is a Short Header. /// /// # Returns - /// `Ok(u8)` with the reserved bits value (0-3) if Short Header, - /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. - #[inline] + /// `Ok(u8)` containing the 2 reserved bits if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. + #[inline(always)] pub fn short_reserved_bits(&self) -> Result { - if !self.is_long_header() { - // Safety: !is_long_header() ensures conditions are met. - Ok(unsafe { self.unchecked_short_reserved_bits() }) - } else { + if self.is_long_header() { Err(QuicHdrError::InvalidHeaderForm) + } else { + // Safety: A header form has been checked. + Ok(unsafe { self.short_reserved_bits_unchecked() }) } } - /// Gets the Key Phase Bit (bit 2 of `first_byte`) if this is a Short Header. + /// Gets the Key Phase Bit (bit 5) if this is a Short Header. /// /// # Returns - /// `Ok(bool)` (`true` if bit is 1, `false` if 0) if Short Header, - /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. - #[inline] + /// `Ok(bool)` with the key phase value if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. + #[inline(always)] pub fn short_key_phase(&self) -> Result { - if !self.is_long_header() { - // Safety: !is_long_header() ensures conditions for unchecked_short_key_phase are met. - Ok(unsafe { self.unchecked_short_key_phase() }) - } else { + if self.is_long_header() { Err(QuicHdrError::InvalidHeaderForm) + } else { + // Safety: A header form has been checked. + Ok(unsafe { self.short_key_phase_unchecked() }) } } - /// Gets the encoded Packet Number Length (bits 1-0 of `first_byte`) if this is a Short Header. - /// The value is `actual_length_in_bytes - 1`. + /// Gets the encoded Packet Number Length (bits 6-7) if this is a Short Header. + /// This value is `actual_length_in_bytes - 1`. /// /// # Returns - /// `Ok(u8)` with the encoded length (0-3) if Short Header, - /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. - #[inline] + /// `Ok(u8)` containing the encoded length (0-3) if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. + #[inline(always)] pub fn short_pn_length_bits(&self) -> Result { - if !self.is_long_header() { - // Safety: !is_long_header() ensures conditions for unchecked_short_pn_length_bits are met. - Ok(unsafe { self.unchecked_short_pn_length_bits() }) - } else { + if self.is_long_header() { Err(QuicHdrError::InvalidHeaderForm) + } else { + // Safety: A header form has been checked. + Ok(unsafe { self.short_pn_length_bits_unchecked() }) } } - /// Gets the actual Packet Number Length in bytes (1 to 4) if this is a Short Header. + /// Gets the actual Packet Number Length in bytes (1-4) if this is a Short Header. /// /// # Returns - /// `Ok(usize)` with the actual length (1-4 bytes) if Short Header, - /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. - #[inline] + /// `Ok(usize)` containing the length if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. + #[inline(always)] pub fn short_packet_number_length(&self) -> Result { - self.short_pn_length_bits().map(|bits| (bits + 1) as usize) + self.short_pn_length_bits().map(|b| (b + 1) as usize) } - /// Gets the QUIC version in host byte order, if this is a Long Header. + /// Gets the QUIC Version if this is a Long Header. /// /// # Returns - /// `Ok(u32)` with the version if Long Header, - /// `Err(QuicHdrError::InvalidHeaderForm)` for Short Headers. - #[inline] + /// `Ok(u32)` containing the version if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] pub fn version(&self) -> Result { - if self.is_long_header() { - // Safety: is_long_header() ensures self.inner.long is the active and initialized variant. - Ok(unsafe { self.unchecked_version() }) - } else { + if !self.is_long_header() { Err(QuicHdrError::InvalidHeaderForm) + } else { + // Safety: A header form has been checked. + Ok(unsafe { self.hdr.inner.long.version() }) } } - /// Gets the Destination Connection ID length as present on the wire for Long Headers. - /// This is the value of the DCIL byte. + /// Gets the on-the-wire length of the Destination Connection ID if this is a Long Header. /// /// # Returns - /// `Ok(u8)` with the on-wire DCID length if Long Header, - /// `Err(QuicHdrError::InvalidHeaderForm)` for Short Headers (as DCID length is not on the wire for them). - #[inline] + /// `Ok(u8)` with the length if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] pub fn dc_id_len_on_wire(&self) -> Result { - if self.is_long_header() { - // Safety: is_long_header() ensures self.inner.long is active. - Ok(unsafe { self.unchecked_dc_id_len_on_wire() }) + if !self.is_long_header() { + Err(QuicHdrError::InvalidHeaderForm) } else { + // Safety: A header form has been checked. + Ok(unsafe { self.hdr.inner.long.dst.len() }) + } + } + + /// Gets the on-the-wire length of the Source Connection ID if this is a Long Header. + /// + /// # Returns + /// `Ok(u8)` with the length if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] + pub fn sc_id_len_on_wire(&self) -> Result { + if !self.is_long_header() { Err(QuicHdrError::InvalidHeaderForm) + } else { + // Safety: A header form has been checked. + Ok(unsafe { self.hdr.inner.long.src.len() }) } } /// Gets the effective length of the Destination Connection ID. - /// For Long Headers, this is read from the Dst CID Len byte (via `self.inner.long.dst.len()`). - /// For Short Headers, this is the contextual length stored in `header_type.dc_id_len`. + /// + /// For Long Headers, this length is read directly from the header data. For Short + /// Headers, this is the contextual length that was provided when [`QuicHdr::parse()`] + /// was called. /// /// # Returns - /// The `u8` effective length of the DCID. - #[inline] + /// The length of the Destination Connection ID in bytes. + #[inline(always)] pub fn dc_id_effective_len(&self) -> u8 { - // Safety: self.header_type is always consistent with the active union variant. - unsafe { self.unchecked_dc_id_effective_len() } + match self.header_type { + // Safety: Header form is known. + QuicHeaderType::QuicLong => unsafe { self.hdr.inner.long.dst.len() }, + QuicHeaderType::QuicShort { dc_id_len } => dc_id_len, + } } - /// Gets a slice to the Destination Connection ID bytes. + /// Gets a slice containing the bytes of the Destination Connection ID. + /// /// The length of the slice is determined by `dc_id_effective_len()`. /// /// # Returns - /// A `&[u8]` slice of the DCID. - #[inline] + /// A byte slice (`&[u8]`) representing the Destination Connection ID. + #[inline(always)] pub fn dc_id(&self) -> &[u8] { - // Safety: self.header_type is consistent, and dc_id_effective_len ensures bounds. - unsafe { self.unchecked_dc_id() } - } - - /// Gets the Source Connection ID length as present on the wire (Long Headers only). - /// This is the value of the SCIL byte. - /// - /// # Returns - /// `Ok(u8)` with the on-wire SCID length if Long Header, - /// `Err(QuicHdrError::InvalidHeaderForm)` for Short Headers. - #[inline] - pub fn sc_id_len_on_wire(&self) -> Result { - if self.is_long_header() { - // Safety: is_long_header() ensures self.inner.long is active. - Ok(unsafe { self.unchecked_sc_id_len_on_wire() }) - } else { - Err(QuicHdrError::InvalidHeaderForm) - } + let n = self.dc_id_effective_len() as usize; + let raw = match self.header_type { + // Safety: We are in a `ParsedQuicHdr`, so we know which union variant is active. + QuicHeaderType::QuicLong => unsafe { &self.hdr.inner.long.dst.bytes }, + QuicHeaderType::QuicShort { .. } => unsafe { &self.hdr.inner.short.dst.bytes }, + }; + // Safety: `dc_id_effective_len` is bounded by `QUIC_MAX_CID_LEN` during parsing. + unsafe { raw.get_unchecked(..n) } } - /// Gets a slice to the Source Connection ID bytes if this is a Long Header. + /// Gets a slice of the Source Connection ID bytes if this is a Long Header. /// /// # Returns - /// `Ok(&[u8])` slice of the SCID if Long Header, - /// `Err(QuicHdrError::InvalidHeaderForm)` for Short Headers. - #[inline] + /// `Ok(&[u8])` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] pub fn sc_id(&self) -> Result<&[u8], QuicHdrError> { - if self.is_long_header() { - // Safety: is_long_header() ensures self.inner.long is active and its .src.as_slice() is valid. - Ok(unsafe { self.unchecked_sc_id() }) - } else { + if !self.is_long_header() { Err(QuicHdrError::InvalidHeaderForm) + } else { + // Safety: A header form has been checked. + Ok(unsafe { self.hdr.inner.long.src.as_slice() }) } } - /// Gets the current `QuicHeaderType` discriminator. - /// This provides context for interpreting the union and, for Short Headers, the Destination CID length. + /// Returns the logical header type determined during parsing. /// /// # Returns - /// The `QuicHeaderType` of this header instance. - #[inline] + /// The [`QuicHeaderType`] enum variant (`QuicLong` or `QuicShort`) for this header. + #[inline(always)] pub fn header_type(&self) -> QuicHeaderType { self.header_type } - /// Sets the Header Form bit (bit 7) in `first_byte`. + /// Sets the Fixed Bit (bit 1 of `first_byte`). /// /// # Parameters - /// * `is_long`: If `true`, sets for Long Header; `false` for Short. - /// - /// # Safety - /// This method only changes the bit in `first_byte`. - /// For a safe structural change of the header form (Long vs. Short) which also - /// reinitialized the `inner` union data and updates `header_type`, use `set_header_type()`. - /// If this method is used to change the header form bit, the caller is responsible for - /// ensuring `header_type` and `inner` data are also updated to maintain consistency. - #[inline] - pub fn set_header_form_bit(&mut self, is_long: bool) { - // Safety: Direct bit manipulation; caller must ensure overall consistency. - unsafe { self.unchecked_set_header_form_bit(is_long) }; + /// * `v`: The bit value (0 or 1). Masked to 1 bit. + #[inline(always)] + pub fn set_fixed_bit(&mut self, v: u8) { + // Safety: This operation is valid for both header forms. + unsafe { self.set_fixed_bit_unchecked(v) } } - /// Sets the Fixed Bit (bit 6 of `first_byte`). + /// Sets the Packet Type if this is a Long Header. /// /// # Parameters - /// * `val`: The new value for the Fixed Bit (0 or 1). Input is masked to 1 bit. - #[inline] - pub fn set_fixed_bit(&mut self, val: u8) { - // Safety: Direct bitwise operation on self.first_byte. - unsafe { self.unchecked_set_fixed_bit(val) }; + /// * `pt`: The `QuicPacketType` to set. + /// + /// # Returns + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] + pub fn set_long_packet_type(&mut self, pt: QuicPacketType) -> Result<(), QuicHdrError> { + if !self.is_long_header() { + return Err(QuicHdrError::InvalidHeaderForm); + } + // Safety: A header form has been checked. + unsafe { + self.set_long_packet_type_bits_unchecked(pt as u8); + } + Ok(()) } - /// Sets the Long Packet Type (bits 5-4 of `first_byte`) if this is a Long Header. + /// Sets the Reserved Bits (bits 4-5) if this is a Long Header. /// /// # Parameters - /// * `lptype`: The `QuicPacketType` enum value. + /// * `v`: The 2-bit value to set. Masked to 2 bits. /// /// # Returns - /// `Ok(())` if the operation is applicable and successful, - /// `Err(QuicHdrError::InvalidHeaderForm)` if called on a Short Header. - #[inline] - pub fn set_long_packet_type(&mut self, lptype: QuicPacketType) -> Result<(), QuicHdrError> { - if self.is_long_header() { - // Safety: `is_long_header()` check ensures conditions for are met. - unsafe { self.unchecked_set_long_packet_type_bits(lptype as u8) }; - Ok(()) - } else { - Err(QuicHdrError::InvalidHeaderForm) + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] + pub fn set_reserved_bits_long(&mut self, v: u8) -> Result<(), QuicHdrError> { + if !self.is_long_header() { + return Err(QuicHdrError::InvalidHeaderForm); } + // Safety: A header form has been checked. + unsafe { self.set_reserved_bits_long_unchecked(v) }; + Ok(()) } - /// Sets the Reserved Bits (bits 3-2 of `first_byte`) if this is a Long Header. + /// Sets the encoded Packet Number Length (bits 6-7) if this is a Long Header. /// /// # Parameters - /// * `val`: The Reserved Bits value (0-3). Input is masked to 2 bits. - /// For QUIC v1 Initial, 0-RTT, Handshake, `val` MUST typically be 0. + /// * `v`: Encoded Packet Number Length (`actual_length_in_bytes - 1`, range 0-3). Masked to 2 bits. /// /// # Returns /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline] - pub fn set_reserved_bits_long(&mut self, val: u8) -> Result<(), QuicHdrError> { - if self.is_long_header() { - // Safety: Condition met. - unsafe { self.unchecked_set_reserved_bits_long(val) }; - Ok(()) - } else { - Err(QuicHdrError::InvalidHeaderForm) + #[inline(always)] + pub fn set_pn_length_bits_long(&mut self, v: u8) -> Result<(), QuicHdrError> { + if !self.is_long_header() { + return Err(QuicHdrError::InvalidHeaderForm); } + // Safety: A header form has been checked. + unsafe { self.set_pn_length_bits_long_unchecked(v) }; + Ok(()) } - /// Sets the encoded Packet Number Length (bits 1-0 of `first_byte`) if this is a Long Header. + /// Sets the actual Packet Number Length in bytes (1-4) if this is a Long Header. /// /// # Parameters - /// * `val`: Encoded Packet Number Length (`actual_length_in_bytes - 1`, range 0-3). Masked to 2 bits. + /// * `n`: The actual length in bytes (1-4). /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline] - pub fn set_pn_length_bits_long(&mut self, val: u8) -> Result<(), QuicHdrError> { - if self.is_long_header() { - // Safety: Condition met. - unsafe { self.unchecked_set_pn_length_bits_long(val) }; - Ok(()) - } else { - Err(QuicHdrError::InvalidHeaderForm) + /// `Ok(())` if successful, `Err` if `n` is out of range or this is not a Long Header. + #[inline(always)] + pub fn set_packet_number_length_long(&mut self, n: usize) -> Result<(), QuicHdrError> { + if !(1..=4).contains(&n) { + return Err(QuicHdrError::InvalidLength); + } + if !self.is_long_header() { + return Err(QuicHdrError::InvalidHeaderForm); } + // Safety: A length and header form have been checked. + unsafe { self.set_packet_number_length_long_unchecked(n) }; + Ok(()) } - /// Sets the actual Packet Number Length (1-4 bytes) if this is a Long Header. - /// Clamps input `len_bytes` to the valid 1-4 range if out of bounds. + /// Sets the Spin Bit (bit 2) if this is a Short Header. /// /// # Parameters - /// * `len_bytes`: Actual PN length in bytes (1-4). + /// * `b`: The boolean value for the spin bit. /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - pub fn set_packet_number_length_long(&mut self, len_bytes: usize) -> Result<(), QuicHdrError> { + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. + #[inline(always)] + pub fn set_short_spin_bit(&mut self, b: bool) -> Result<(), QuicHdrError> { if self.is_long_header() { - // Safety: Condition met. - unsafe { self.unchecked_set_packet_number_length_long(len_bytes) }; - Ok(()) - } else { - Err(QuicHdrError::InvalidHeaderForm) + return Err(QuicHdrError::InvalidHeaderForm); } + // Safety: A header form has been checked. + unsafe { self.set_short_spin_bit_unchecked(b) }; + Ok(()) } - /// Sets the Spin Bit (bit 5 of `first_byte`) if this is a Short Header. + /// Sets the Reserved Bits (bits 3-4) if this is a Short Header. /// /// # Parameters - /// * `spin`: Value for the Spin Bit (`true` for 1, `false` for 0). + /// * `v`: The 2-bit value to set. Masked to 2 bits. /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Short Header. - #[inline] - pub fn set_short_spin_bit(&mut self, spin: bool) -> Result<(), QuicHdrError> { - if !self.is_long_header() { - // Safety: Condition met. - unsafe { self.unchecked_set_short_spin_bit(spin) }; - Ok(()) - } else { - Err(QuicHdrError::InvalidHeaderForm) + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. + #[inline(always)] + pub fn set_short_reserved_bits(&mut self, v: u8) -> Result<(), QuicHdrError> { + if self.is_long_header() { + return Err(QuicHdrError::InvalidHeaderForm); } + // Safety: A header form has been checked. + unsafe { self.set_short_reserved_bits_unchecked(v) }; + Ok(()) } - /// Sets the Reserved Bits (bits 4-3 of `first_byte`) if this is a Short Header. + /// Sets the Key Phase Bit (bit 5) if this is a Short Header. /// /// # Parameters - /// * `val`: The Reserved Bits value (0-3). Input is masked to 2 bits. - /// For QUIC v1, `val` MUST typically be 0. + /// * `kp`: The boolean value for the key phase. /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Short Header. - #[inline] - pub fn set_short_reserved_bits(&mut self, val: u8) -> Result<(), QuicHdrError> { - if !self.is_long_header() { - // Safety: Condition met. - unsafe { self.unchecked_set_short_reserved_bits(val) }; - Ok(()) - } else { - Err(QuicHdrError::InvalidHeaderForm) + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. + #[inline(always)] + pub fn set_short_key_phase(&mut self, kp: bool) -> Result<(), QuicHdrError> { + if self.is_long_header() { + return Err(QuicHdrError::InvalidHeaderForm); } + // Safety: A header form has been checked. + unsafe { self.set_short_key_phase_unchecked(kp) }; + Ok(()) } - /// Sets the Key Phase Bit (bit 2 of `first_byte`) if this is a Short Header. + /// Sets the encoded Packet Number Length (bits 6-7) if this is a Short Header. /// /// # Parameters - /// * `key_phase`: Value for the Key Phase Bit (`true` for 1, `false` for 0). + /// * `v`: Encoded Packet Number Length (`actual_length_in_bytes - 1`, range 0-3). Masked to 2 bits. /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Short Header. - #[inline] - pub fn set_short_key_phase(&mut self, key_phase: bool) -> Result<(), QuicHdrError> { - if !self.is_long_header() { - // Safety: Condition met. - unsafe { self.unchecked_set_short_key_phase(key_phase) }; - Ok(()) - } else { - Err(QuicHdrError::InvalidHeaderForm) + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. + #[inline(always)] + pub fn set_short_pn_length_bits(&mut self, v: u8) -> Result<(), QuicHdrError> { + if self.is_long_header() { + return Err(QuicHdrError::InvalidHeaderForm); } + // Safety: A header form has been checked. + unsafe { self.set_short_pn_length_bits_unchecked(v) }; + Ok(()) } - /// Sets the encoded Packet Number Length (bits 1-0 of `first_byte`) if this is a Short Header. + /// Sets the actual Packet Number Length in bytes (1-4) if this is a Short Header. /// /// # Parameters - /// * `val`: Encoded Packet Number Length (`actual_length_in_bytes - 1`, range 0-3). Masked to 2 bits. + /// * `n`: The actual length in bytes (1-4). /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Short Header. - #[inline] - pub fn set_short_pn_length_bits(&mut self, val: u8) -> Result<(), QuicHdrError> { - if !self.is_long_header() { - // Safety: Condition met. - unsafe { self.unchecked_set_short_pn_length_bits(val) }; - Ok(()) - } else { - Err(QuicHdrError::InvalidHeaderForm) + /// `Ok(())` if successful, `Err` if `n` is out of range or this is a Long Header. + #[inline(always)] + pub fn set_short_packet_number_length(&mut self, n: usize) -> Result<(), QuicHdrError> { + if !(1..=4).contains(&n) { + return Err(QuicHdrError::InvalidLength); } - } - - /// Sets the actual Packet Number Length (1-4 bytes) if this is a Short Header. - /// Clamps input `len_bytes` to the valid 1-4 range if out of bounds. - /// - /// # Parameters - /// * `len_bytes`: Actual PN length in bytes (1-4). - /// - /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Short Header. - pub fn set_short_packet_number_length(&mut self, len_bytes: usize) -> Result<(), QuicHdrError> { - if !self.is_long_header() { - // Safety: Condition met. - unsafe { self.unchecked_set_short_packet_number_length(len_bytes) }; - Ok(()) - } else { - Err(QuicHdrError::InvalidHeaderForm) + if self.is_long_header() { + return Err(QuicHdrError::InvalidHeaderForm); } + // Safety: A length and header form have been checked. + unsafe { self.set_short_packet_number_length_unchecked(n) }; + Ok(()) } - /// Sets the QUIC version (host byte order), if this is a Long Header. - /// Does nothing if it's a Short Header. + /// Sets the QUIC Version if this is a Long Header. /// /// # Parameters - /// * `v`: The QUIC version in host byte order. + /// * `v`: The `u32` version value. /// /// # Returns - /// `Ok(())` if successful (i.e., was a Long Header), - /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. - #[inline] + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] pub fn set_version(&mut self, v: u32) -> Result<(), QuicHdrError> { - if self.is_long_header() { - // Safety: Condition met, self.inner.long is active. - unsafe { self.unchecked_set_version(v) }; - Ok(()) - } else { - Err(QuicHdrError::InvalidHeaderForm) + if !self.is_long_header() { + return Err(QuicHdrError::InvalidHeaderForm); } + // Safety: A header form has been checked. + unsafe { self.set_version_unchecked(v) }; + Ok(()) } /// Sets the effective length of the Destination Connection ID. /// /// # Parameters - /// * `new_len`: The new desired effective length for the DCID. Values are clamped - /// to `QUIC_MAX_CID_LEN`. - /// - /// For Long Headers, this updates the internal `len` field of `self.inner.long.dst`, - /// which corresponds to the on-wire DCIL byte. The actual DCID bytes are *not* zeroed - /// or changed by this call alone if the length shrinks; use `set_dc_id` to change bytes. - /// For Short Headers, this updates the contextual `dc_id_len` in `self.header_type` - /// and also updates the internal `len` of `self.inner.short.dst` for consistency. - pub fn set_dc_id_effective_len(&mut self, new_len: u8) { - // Safety: unchecked method correctly handles union based on self.header_type. - unsafe { self.unchecked_set_dc_id_effective_len(new_len) }; + /// * `l`: The new length. Will be truncated to `QUIC_MAX_CID_LEN`. + #[inline(always)] + pub fn set_dc_id_effective_len(&mut self, l: u8) { + // Safety: This operation is valid for both header forms and handles the logic internally. + unsafe { self.set_dc_id_effective_len_unchecked(l) }; } /// Sets the Destination Connection ID from a slice. - /// This updates the internal CID bytes and its length. - /// For Long Headers, `self.inner.long.dst.len` (on-wire DCIL) is updated to `data.len()`. - /// For Short Headers, `self.header_type.dc_id_len` (contextual length) and - /// `self.inner.short.dst.len` (internal tracking) are updated to `data.len()`. + /// This also updates the effective length. /// /// # Parameters - /// * `data`: A byte slice containing the new DCID. Length is clamped to `QUIC_MAX_CID_LEN`. - pub fn set_dc_id(&mut self, data: &[u8]) { - // Safety: unchecked method correctly handles union based on self.header_type. - unsafe { self.unchecked_set_dc_id(data) }; + /// * `d`: A slice containing the new DCID. + #[inline(always)] + pub fn set_dc_id(&mut self, d: &[u8]) { + // Safety: This operation is valid for both header forms and handles the logic internally. + unsafe { self.set_dc_id_unchecked(d) }; } - /// Sets the Source Connection ID length (Long Headers only). - /// This updates the internal `len` field of `self.inner.long.src`, which - /// corresponds to the on-wire SCIL byte. The SCID bytes are *not* zeroed or changed. + /// Sets the on-the-wire length of the Source Connection ID if this is a Long Header. /// /// # Parameters - /// * `len`: The new desired length for the SCID. Values are clamped to `QUIC_MAX_CID_LEN`. + /// * `l`: The new length. Will be truncated to `QUIC_MAX_CID_LEN`. /// /// # Returns - /// `Ok(())` if successful (i.e., was a Long Header), - /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. - pub fn set_sc_id_len_on_wire(&mut self, len: u8) -> Result<(), QuicHdrError> { - if self.is_long_header() { - // Safety: Condition met, self.inner.long is active. - unsafe { self.unchecked_set_sc_id_len_on_wire(len) }; - Ok(()) - } else { - Err(QuicHdrError::InvalidHeaderForm) - } - } - - /// Sets the Source Connection ID from a slice (Long Headers only). - /// This updates the internal SCID bytes and its length (`self.inner.long.src.len`). - /// - /// # Parameters - /// * `data`: A byte slice containing the new SCID. Length clamped to `QUIC_MAX_CID_LEN`. - /// - /// # Returns - /// `Ok(())` if successful (i.e., was a Long Header), - /// `Err(QuicHdrError::InvalidHeaderForm)` otherwise. - pub fn set_sc_id(&mut self, data: &[u8]) -> Result<(), QuicHdrError> { - if self.is_long_header() { - // Safety: Condition met, self.inner.long is active. - unsafe { self.unchecked_set_sc_id(data) }; - Ok(()) - } else { - Err(QuicHdrError::InvalidHeaderForm) + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] + pub fn set_sc_id_len_on_wire(&mut self, l: u8) -> Result<(), QuicHdrError> { + if !self.is_long_header() { + return Err(QuicHdrError::InvalidHeaderForm); } + // Safety: A header form has been checked. + unsafe { self.set_sc_id_len_on_wire_unchecked(l) }; + Ok(()) } - /// Sets the `QuicHeaderType` and reinitialized the inner `QuicHdrUn` union - /// to its default state for the new type if the fundamental header form (Long/Short) changes. - /// This is to prevent misinterpreting stale bytes from the previous union variant. - /// The `first_byte`'s Header Form bit and Fixed Bit are updated to match the new type. - /// Other specific bits in `first_byte` (like packet type, PN length) are *not* reset by this - /// method and should be configured by the caller as needed for the new type. - /// - /// # Parameters - /// * `new_type`: The new `QuicHeaderType` to set. - /// - /// # Safety - /// This method correctly manages union transitions by re-initializing the union. - /// Any CID data or other header fields must be repopulated by the caller if they need to be - /// preserved or set for the new header type. - pub fn set_header_type(&mut self, new_type: QuicHeaderType) { - let other_bits = self.first_byte & !(HEADER_FORM_BIT | FIXED_BIT_MASK); - // Safety: This method correctly manages union transitions and first_byte consistency. - unsafe { self.unchecked_set_header_type(new_type) }; - self.first_byte |= other_bits; - } - - /// Parses the Connection ID Length byte. - /// - /// In QUIC Long Headers, the DCID Len and SCID Len bytes directly specify the length - /// of their respective Connection IDs. This function validates that the encoded length - /// does not exceed the maximum allowed Connection ID length (`QUIC_MAX_CID_LEN`). + /// Sets the Source Connection ID from a slice if this is a Long Header. + /// This also updates the on-the-wire length. /// /// # Parameters - /// * `len_byte`: The byte read from the packet that encodes the Connection ID length. + /// * `d`: A slice containing the new SCID. /// /// # Returns - /// A `Result` containing the parsed Connection ID length as `usize` if valid, - /// or a `QuicHdrError::InvalidLength` if the length exceeds `QUIC_MAX_CID_LEN`. - pub fn parse_cid_len(len_byte: u8) -> Result { - if len_byte > QUIC_MAX_CID_LEN as u8 { - Err(QuicHdrError::InvalidLength) - } else { - Ok(len_byte as usize) + /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] + pub fn set_sc_id(&mut self, d: &[u8]) -> Result<(), QuicHdrError> { + if !self.is_long_header() { + return Err(QuicHdrError::InvalidHeaderForm); } + // Safety: A header form has been checked. + unsafe { self.set_sc_id_unchecked(d) }; + Ok(()) } } -// Unsafe (unchecked) methods -impl QuicHdr { - /// Gets the Long Packet Type bits from `first_byte` without checking a header form. +// Unchecked, unsafe internal methods. +impl<'a> ParsedQuicHdr<'a> { + /// Gets the Long Header Packet Type bits from `first_byte` without checking header form. /// /// # Returns - /// The Long Packet Type bits value (0-3). + /// The Packet Type bits value (0-3). /// /// # Safety /// Caller must ensure `self.is_long_header()` is true. - #[inline] - unsafe fn unchecked_long_packet_type_bits(&self) -> u8 { - (self.first_byte & LONG_PACKET_TYPE_MASK) >> LONG_PACKET_TYPE_SHIFT + #[inline(always)] + unsafe fn long_packet_type_bits_unchecked(&self) -> u8 { + (self.hdr.first_byte & LONG_PACKET_TYPE_MASK) >> LONG_PACKET_TYPE_SHIFT } - /// Sets the Long Packet Type bits in `first_byte` without checking header form. - /// - /// # Parameters - /// * `lptype_bits`: The Long Packet Type bits (0-3). + /// Sets the Long Header Packet Type bits in `first_byte` without checking header form. /// /// # Safety /// Caller must ensure `self.is_long_header()` is true. - #[inline] - unsafe fn unchecked_set_long_packet_type_bits(&mut self, lptype_bits: u8) { - self.first_byte = (self.first_byte & !LONG_PACKET_TYPE_MASK) - | ((lptype_bits & 0x03) << LONG_PACKET_TYPE_SHIFT); + #[inline(always)] + unsafe fn set_long_packet_type_bits_unchecked(&mut self, b: u8) { + self.hdr.first_byte = + (self.hdr.first_byte & !LONG_PACKET_TYPE_MASK) | ((b & 0x03) << LONG_PACKET_TYPE_SHIFT); } /// Gets the Reserved Bits (Long Header) from `first_byte` without checking header form. @@ -1563,480 +1196,269 @@ impl QuicHdr { /// /// # Safety /// Caller must ensure `self.is_long_header()` is true. - #[inline] - unsafe fn unchecked_reserved_bits_long(&self) -> u8 { - (self.first_byte & RESERVED_BITS_LONG_MASK) >> RESERVED_BITS_LONG_SHIFT + #[inline(always)] + unsafe fn reserved_bits_long_unchecked(&self) -> u8 { + (self.hdr.first_byte & RESERVED_BITS_LONG_MASK) >> RESERVED_BITS_LONG_SHIFT } /// Sets the Reserved Bits (Long Header) in `first_byte` without checking header form. /// - /// # Parameters - /// * `val`: The Reserved Bits value (0-3). - /// /// # Safety /// Caller must ensure `self.is_long_header()` is true. - #[inline] - unsafe fn unchecked_set_reserved_bits_long(&mut self, val: u8) { - self.first_byte = (self.first_byte & !RESERVED_BITS_LONG_MASK) - | ((val & 0x03) << RESERVED_BITS_LONG_SHIFT); + #[inline(always)] + unsafe fn set_reserved_bits_long_unchecked(&mut self, v: u8) { + self.hdr.first_byte = (self.hdr.first_byte & !RESERVED_BITS_LONG_MASK) + | ((v & 0x03) << RESERVED_BITS_LONG_SHIFT); } - /// Gets the encoded Packet Number Length (Long Header) from `first_byte` without checking header form. + /// Gets the encoded Packet Number Length bits (Long Header) from `first_byte` without checking header form. /// /// # Returns - /// The encoded PN Length (0-3). + /// The encoded PN length bits (0-3). /// /// # Safety - /// Caller must ensure `self.is_long_header()` is true and the packet type includes a PN. - #[inline] - unsafe fn unchecked_pn_length_bits_long(&self) -> u8 { - self.first_byte & PN_LENGTH_BITS_MASK + /// Caller must ensure `self.is_long_header()` is true. + #[inline(always)] + unsafe fn pn_length_bits_long_unchecked(&self) -> u8 { + self.hdr.first_byte & PN_LENGTH_BITS_MASK } - /// Sets the encoded Packet Number Length (Long Header) in `first_byte` without checking header form. - /// - /// # Parameters - /// * `val`: Encoded PN Length (0-3). + /// Sets the encoded Packet Number Length bits (Long Header) in `first_byte` without checking header form. /// /// # Safety /// Caller must ensure `self.is_long_header()` is true. - #[inline] - unsafe fn unchecked_set_pn_length_bits_long(&mut self, val: u8) { - self.first_byte = (self.first_byte & !PN_LENGTH_BITS_MASK) | (val & PN_LENGTH_BITS_MASK); + #[inline(always)] + unsafe fn set_pn_length_bits_long_unchecked(&mut self, v: u8) { + self.hdr.first_byte = + (self.hdr.first_byte & !PN_LENGTH_BITS_MASK) | (v & PN_LENGTH_BITS_MASK); } - /// Sets the Packet Number Length (Long Header) from actual length without checking header form. - /// - /// # Parameters - /// * `len_bytes`: Actual PN length (1-4 bytes). Values outside this range are clamped to 1. + /// Sets the Packet Number Length (Long Header) in `first_byte` without checking header form or length validity. /// /// # Safety - /// Caller must ensure `self.is_long_header()` is true. - unsafe fn unchecked_set_packet_number_length_long(&mut self, len_bytes: usize) { - let encoded_val = match len_bytes { - 1 => 0b00, - 2 => 0b01, - 3 => 0b10, - 4 => 0b11, - _ => 0b00, // Default to 1-byte PN length if input is invalid - }; - self.unchecked_set_pn_length_bits_long(encoded_val); + /// Caller must ensure `self.is_long_header()` is true and `1 <= n <= 4`. + #[inline(always)] + unsafe fn set_packet_number_length_long_unchecked(&mut self, n: usize) { + self.set_pn_length_bits_long_unchecked((n - 1) as u8); } /// Gets the Spin Bit (Short Header) from `first_byte` without checking header form. /// /// # Returns - /// `true` if Spin Bit is 1, `false` if 0. + /// The spin bit value. /// /// # Safety - /// Caller must ensure `!self.is_long_header()` is true. - #[inline] - unsafe fn unchecked_short_spin_bit(&self) -> bool { - (self.first_byte & SHORT_SPIN_BIT_MASK) != 0 + /// Caller must ensure `self.is_long_header()` is false. + #[inline(always)] + unsafe fn short_spin_bit_unchecked(&self) -> bool { + (self.hdr.first_byte & SHORT_SPIN_BIT_MASK) != 0 } /// Sets the Spin Bit (Short Header) in `first_byte` without checking header form. /// - /// # Parameters - /// * `spin`: Value for the Spin Bit. - /// /// # Safety - /// Caller must ensure `!self.is_long_header()` is true. - #[inline] - unsafe fn unchecked_set_short_spin_bit(&mut self, spin: bool) { - if spin { - self.first_byte |= SHORT_SPIN_BIT_MASK; + /// Caller must ensure `self.is_long_header()` is false. + #[inline(always)] + unsafe fn set_short_spin_bit_unchecked(&mut self, b: bool) { + if b { + self.hdr.first_byte |= SHORT_SPIN_BIT_MASK; } else { - self.first_byte &= !SHORT_SPIN_BIT_MASK; + self.hdr.first_byte &= !SHORT_SPIN_BIT_MASK; } } /// Gets the Reserved Bits (Short Header) from `first_byte` without checking header form. /// /// # Returns - /// The Reserved Bits value (0-3). + /// The reserved bits value (0-3). /// /// # Safety - /// Caller must ensure `!self.is_long_header()` is true. - #[inline] - unsafe fn unchecked_short_reserved_bits(&self) -> u8 { - (self.first_byte & SHORT_RESERVED_BITS_MASK) >> SHORT_RESERVED_BITS_SHIFT + /// Caller must ensure `self.is_long_header()` is false. + #[inline(always)] + unsafe fn short_reserved_bits_unchecked(&self) -> u8 { + (self.hdr.first_byte & SHORT_RESERVED_BITS_MASK) >> SHORT_RESERVED_BITS_SHIFT } /// Sets the Reserved Bits (Short Header) in `first_byte` without checking header form. /// - /// # Parameters - /// * `val`: The Reserved Bits value (0-3). - /// /// # Safety - /// Caller must ensure `!self.is_long_header()` is true. - #[inline] - unsafe fn unchecked_set_short_reserved_bits(&mut self, val: u8) { - self.first_byte = (self.first_byte & !SHORT_RESERVED_BITS_MASK) - | ((val & 0x03) << SHORT_RESERVED_BITS_SHIFT); + /// Caller must ensure `self.is_long_header()` is false. + #[inline(always)] + unsafe fn set_short_reserved_bits_unchecked(&mut self, v: u8) { + self.hdr.first_byte = (self.hdr.first_byte & !SHORT_RESERVED_BITS_MASK) + | ((v & 0x03) << SHORT_RESERVED_BITS_SHIFT); } - /// Gets the Key Phase Bit (Short Header) from `first_byte` without checking header form. + /// Gets the Key Phase bit (Short Header) from `first_byte` without checking header form. /// /// # Returns - /// `true` if Key Phase Bit is 1, `false` if 0. + /// The key phase bit value. /// /// # Safety - /// Caller must ensure `!self.is_long_header()` is true. - #[inline] - unsafe fn unchecked_short_key_phase(&self) -> bool { - (self.first_byte & SHORT_KEY_PHASE_BIT_MASK) != 0 + /// Caller must ensure `self.is_long_header()` is false. + #[inline(always)] + unsafe fn short_key_phase_unchecked(&self) -> bool { + (self.hdr.first_byte & SHORT_KEY_PHASE_BIT_MASK) != 0 } - /// Sets the Key Phase Bit (Short Header) in `first_byte` without checking header form. - /// - /// # Parameters - /// * `key_phase`: Value for the Key Phase Bit. + /// Sets the Key Phase bit (Short Header) in `first_byte` without checking header form. /// /// # Safety - /// Caller must ensure `!self.is_long_header()` is true. - #[inline] - unsafe fn unchecked_set_short_key_phase(&mut self, key_phase: bool) { - if key_phase { - self.first_byte |= SHORT_KEY_PHASE_BIT_MASK; + /// Caller must ensure `self.is_long_header()` is false. + #[inline(always)] + unsafe fn set_short_key_phase_unchecked(&mut self, b: bool) { + if b { + self.hdr.first_byte |= SHORT_KEY_PHASE_BIT_MASK; } else { - self.first_byte &= !SHORT_KEY_PHASE_BIT_MASK; + self.hdr.first_byte &= !SHORT_KEY_PHASE_BIT_MASK; } } - /// Gets the encoded Packet Number Length (Short Header) from `first_byte` without checking header form. + /// Gets the encoded Packet Number Length bits (Short Header) from `first_byte` without checking header form. /// /// # Returns - /// The encoded PN Length (0-3). - /// - /// # Safety - /// Caller must ensure `!self.is_long_header()` is true. - #[inline] - unsafe fn unchecked_short_pn_length_bits(&self) -> u8 { - self.first_byte & PN_LENGTH_BITS_MASK - } - - /// Sets the encoded Packet Number Length (Short Header) in `first_byte` without checking header form. - /// - /// # Parameters - /// * `val`: Encoded PN Length (0-3). - /// - /// # Safety - /// Caller must ensure `!self.is_long_header()` is true. - #[inline] - unsafe fn unchecked_set_short_pn_length_bits(&mut self, val: u8) { - self.first_byte = (self.first_byte & !PN_LENGTH_BITS_MASK) | (val & PN_LENGTH_BITS_MASK); - } - - /// Sets the Packet Number Length (Short Header) from actual length without checking header form. - /// - /// # Parameters - /// * `len_bytes`: Actual PN length (1-4 bytes). Values outside this range are clamped to 1. + /// The encoded PN length bits (0-3). /// /// # Safety - /// Caller must ensure `!self.is_long_header()` is true. - unsafe fn unchecked_set_short_packet_number_length(&mut self, len_bytes: usize) { - let encoded_val = match len_bytes { - 1 => 0b00, - 2 => 0b01, - 3 => 0b10, - 4 => 0b11, - _ => 0b00, // Default to 1-byte PN length - }; - self.unchecked_set_short_pn_length_bits(encoded_val); + /// Caller must ensure `self.is_long_header()` is false. + #[inline(always)] + unsafe fn short_pn_length_bits_unchecked(&self) -> u8 { + self.hdr.first_byte & PN_LENGTH_BITS_MASK } - /// Gets the QUIC version without checking header form. - /// - /// # Returns - /// The QUIC version (host byte order). + /// Sets the encoded Packet Number Length bits (Short Header) in `first_byte` without checking header form. /// /// # Safety - /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. - #[inline] - unsafe fn unchecked_version(&self) -> u32 { - self.inner.long.version() + /// Caller must ensure `self.is_long_header()` is false. + #[inline(always)] + unsafe fn set_short_pn_length_bits_unchecked(&mut self, v: u8) { + self.hdr.first_byte = + (self.hdr.first_byte & !PN_LENGTH_BITS_MASK) | (v & PN_LENGTH_BITS_MASK); } - /// Sets the QUIC version without checking header form. - /// - /// # Parameters - /// * `v`: The QUIC version (host byte order). + /// Sets the Packet Number Length (Short Header) in `first_byte` without checking header form or length validity. /// /// # Safety - /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. - #[inline] - unsafe fn unchecked_set_version(&mut self, v: u32) { - self.inner.long.set_version(v); + /// Caller must ensure `self.is_long_header()` is false and `1 <= n <= 4`. + #[inline(always)] + unsafe fn set_short_packet_number_length_unchecked(&mut self, n: usize) { + self.set_short_pn_length_bits_unchecked((n - 1) as u8); } - /// Gets the on-wire DCID length (Long Header) without checking header form. - /// - /// # Returns - /// The on-wire DCID length from `self.inner.long.dst.len`. + /// Sets the Fixed Bit in `first_byte` without any checks. /// /// # Safety - /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. - #[inline] - unsafe fn unchecked_dc_id_len_on_wire(&self) -> u8 { - self.inner.long.dst.len() + /// This is always safe from a memory perspective, but the caller must ensure the resulting byte is valid. + #[inline(always)] + unsafe fn set_fixed_bit_unchecked(&mut self, v: u8) { + self.hdr.first_byte = (self.hdr.first_byte & !FIXED_BIT_MASK) | ((v & 1) << 6); } - /// Gets the effective DCID length based on `header_type` without checking union validity. - /// - /// # Returns - /// The effective DCID length. + /// Sets the QUIC version without checking a header form. /// /// # Safety - /// Caller must ensure `self.header_type` is consistent with the active union variant - /// and that the corresponding variant's CID `len` field or `dc_id_len` context is correctly set. - #[inline] - unsafe fn unchecked_dc_id_effective_len(&self) -> u8 { - match self.header_type { - QuicHeaderType::QuicLong => self.inner.long.dst.len(), - QuicHeaderType::QuicShort { dc_id_len } => dc_id_len, - } + /// Caller must ensure `self.is_long_header()` is true. + #[inline(always)] + unsafe fn set_version_unchecked(&mut self, v: u32) { + self.hdr.inner.long.set_version(v); } - /// Sets the effective DCID length without checking union validity. - /// - /// # Parameters - /// * `new_len`: The new effective DCID length. Clamped to `QUIC_MAX_CID_LEN`. + /// Sets the effective DCID length without any checks. /// /// # Safety - /// Caller must ensure `self.header_type` is consistent with the active union variant. - /// This updates lengths in both `header_type` (for Short) and the CID struct itself. - unsafe fn unchecked_set_dc_id_effective_len(&mut self, new_len: u8) { - let validated_len = cmp::min(new_len, QUIC_MAX_CID_LEN as u8); + /// Caller must ensure the correct `header_type` is handled and that the length is valid. + #[inline(always)] + unsafe fn set_dc_id_effective_len_unchecked(&mut self, l: u8) { + let l = cmp::min(l, QUIC_MAX_CID_LEN as u8); match &mut self.header_type { QuicHeaderType::QuicLong => { - self.inner.long.dst.len = validated_len; + self.hdr.inner.long.dst.len = l; } - QuicHeaderType::QuicShort { - dc_id_len: current_dc_len, - } => { - *current_dc_len = validated_len; - self.inner.short.dst.len = validated_len; // Keep short.dst.len consistent - } - } - } - - /// Gets the DCID slice based on `header_type` without checking union validity. - /// - /// # Returns - /// A slice to the DCID bytes. - /// - /// # Safety - /// Caller must ensure `self.header_type` is consistent with the active union variant and - /// `unchecked_dc_id_effective_len()` returns a valid length for the active variant's buffer. - #[inline] - unsafe fn unchecked_dc_id(&self) -> &[u8] { - let len = self.unchecked_dc_id_effective_len() as usize; - match self.header_type { - QuicHeaderType::QuicLong => &self.inner.long.dst.bytes[..len], - QuicHeaderType::QuicShort { .. } => &self.inner.short.dst.bytes[..len], + QuicHeaderType::QuicShort { dc_id_len } => *dc_id_len = l, } } - /// Sets the DCID without checking union validity. - /// - /// # Parameters - /// * `data`: Slice containing the new DCID. Length is clamped by `set()`. + /// Sets the DCID without any checks. /// /// # Safety - /// Caller must ensure `self.header_type` is consistent with the active union variant. - /// This updates lengths in `header_type` (for Short) and the CID struct itself via `set()`. - unsafe fn unchecked_set_dc_id(&mut self, data: &[u8]) { + /// Caller must ensure the correct `header_type` is handled. + #[inline(always)] + unsafe fn set_dc_id_unchecked(&mut self, d: &[u8]) { match &mut self.header_type { - QuicHeaderType::QuicLong => { - self.inner.long.dst.set(data); - } - QuicHeaderType::QuicShort { - dc_id_len: current_dc_len, - } => { - self.inner.short.dst.set(data); - *current_dc_len = self.inner.short.dst.len(); - } - } - } - - /// Gets the on-wire SCID length (Long Header) without checking header form. - /// - /// # Returns - /// The on-wire SCID length from `self.inner.long.src.len`. - /// - /// # Safety - /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. - #[inline] - unsafe fn unchecked_sc_id_len_on_wire(&self) -> u8 { - self.inner.long.src.len() - } - - /// Sets the on-wire SCID length (Long Header) without checking a header form. CID bytes are not changed. - /// - /// # Parameters - /// * `len`: The new SCID length. Clamped to `QUIC_MAX_CID_LEN`. - /// - /// # Safety - /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. - unsafe fn unchecked_set_sc_id_len_on_wire(&mut self, len: u8) { - self.inner.long.src.len = cmp::min(len, QUIC_MAX_CID_LEN as u8); - } - - /// Gets a slice to the SCID (Long Header) without checking a header form. - /// - /// # Returns - /// A slice to the SCID bytes. - /// - /// # Safety - /// Caller must ensure `self.header_type` is `QuicLong`, `self.inner.long` is initialized, - /// and `self.inner.long.src.len()` is a valid length for `self.inner.long.src.bytes`. - #[inline] - unsafe fn unchecked_sc_id(&self) -> &[u8] { - self.inner.long.src.as_slice() - } - - /// Sets the SCID (Long Header) without checking a header form. - /// - /// # Parameters - /// * `data`: Slice containing the new SCID. Length clamped by `set()`. - /// - /// # Safety - /// Caller must ensure `self.header_type` is `QuicLong` and `self.inner.long` is initialized and active. - unsafe fn unchecked_set_sc_id(&mut self, data: &[u8]) { - self.inner.long.src.set(data); - } - - /// Sets the `QuicHeaderType` and manages union transition, without boundary checks for CIDs. - /// The `first_byte`'s Header Form and Fixed Bits are updated according to the `new_type`. - /// Other bits in `first_byte` are *not* touched by this specific unsafe method. - /// - /// # Parameters - /// * `new_type`: The new `QuicHeaderType`. - /// - /// # Safety - /// This method correctly handles the re-initialization of the `inner` union - /// when the fundamental header form (Long/Short) changes, preventing misinterpretation - /// of stale bytes. - /// Caller must repopulate CID data if it needs to be preserved across such a type change. - /// Caller should ensure other bits in `first_byte` are appropriate for `new_type`. - unsafe fn unchecked_set_header_type(&mut self, new_type: QuicHeaderType) { - let current_is_long = self.is_long_header(); - let new_is_long = match new_type { - QuicHeaderType::QuicLong => true, - QuicHeaderType::QuicShort { .. } => false, - }; - if current_is_long != new_is_long { - // Form is changing, reinitialize union and update first_byte's Form bit - if new_is_long { - self.inner = QuicHdrUn { - long: QuicHdrLong::default(), - }; - self.first_byte = (self.first_byte | HEADER_FORM_BIT) | FIXED_BIT_MASK; - } else { - let dc_id_len_for_short = if let QuicHeaderType::QuicShort { dc_id_len } = new_type - { - dc_id_len - } else { - 0 // Should not happen if new_is_long is false - }; - let mut short_data = QuicHdrShort::default(); - short_data.dst.len = cmp::min(dc_id_len_for_short, QUIC_MAX_CID_LEN as u8); - self.inner = QuicHdrUn { short: short_data }; - self.first_byte = (self.first_byte & !HEADER_FORM_BIT) | FIXED_BIT_MASK; - } - } else { - // Form is not changing, but QuicShort's dc_id_len might be. - // Ensure the fixed bit is correct for the form. - if new_is_long { - // This implies new_type is QuicLong - self.first_byte |= HEADER_FORM_BIT | FIXED_BIT_MASK; - } else { - // This implies new_type is QuicShort - self.first_byte = (self.first_byte & !HEADER_FORM_BIT) | FIXED_BIT_MASK; - // If the type is QuicShort, update inner.short.dst.len if dc_id_len changes - if let QuicHeaderType::QuicShort { dc_id_len } = new_type { - self.inner.short.dst.len = cmp::min(dc_id_len, QUIC_MAX_CID_LEN as u8); - } + QuicHeaderType::QuicLong => self.hdr.inner.long.dst.set(d), + QuicHeaderType::QuicShort { dc_id_len } => { + self.hdr.inner.short.dst.set(d); + *dc_id_len = self.hdr.inner.short.dst.len(); } } - self.header_type = new_type; } - /// Sets the Header Form bit in `first_byte` without performing structural changes. - /// - /// # Parameters - /// * `is_long`: `true` for Long Header form, `false` for Short. + /// Sets the on-the-wire SCID length without checking a header form. /// /// # Safety - /// This directly modifies the `first_byte`. Caller must ensure that `header_type` and - /// the active `inner` union variant are consistent with this change. Prefer using - /// the safe `set_header_type` for structural changes. - #[inline] - unsafe fn unchecked_set_header_form_bit(&mut self, is_long: bool) { - if is_long { - self.first_byte |= HEADER_FORM_BIT; - } else { - self.first_byte &= !HEADER_FORM_BIT; - } + /// Caller must ensure `self.is_long_header()` is true. + #[inline(always)] + unsafe fn set_sc_id_len_on_wire_unchecked(&mut self, l: u8) { + self.hdr.inner.long.src.len = cmp::min(l, QUIC_MAX_CID_LEN as u8); } - /// Sets the Fixed Bit in `first_byte` without checking a header form. - /// - /// # Parameters - /// * `val`: The new value for the Fixed Bit (0 or 1). Input masked to 1 bit. + /// Sets the SCID without checking a header form. /// /// # Safety - /// This is a direct bitwise operation on `first_byte`. - #[inline] - unsafe fn unchecked_set_fixed_bit(&mut self, val: u8) { - self.first_byte = (self.first_byte & !FIXED_BIT_MASK) | ((val & 1) << 6); + /// Caller must ensure `self.is_long_header()` is true. + #[inline(always)] + unsafe fn set_sc_id_unchecked(&mut self, d: &[u8]) { + self.hdr.inner.long.src.set(d); } } impl fmt::Debug for QuicHdr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut s = f.debug_struct("QuicHdr"); - s.field( - "first_byte", - &format_args!( - // format_args! is core-friendly - "{:#04x} (Form: {}, Fixed: {}, TypeSpecific: {:#04x})", - self.first_byte, - if self.is_long_header() { + f.debug_struct("QuicHdr") + .field("first_byte", &format_args!("{:#04x}", self.first_byte)) + .field("is_long", &self.is_long_header()) + .finish() + } +} +impl<'a> fmt::Debug for ParsedQuicHdr<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut s = f.debug_struct("ParsedQuicHdr"); + s.field("first_byte", &format_args!("{:#04x}", self.first_byte())) + .field( + "form", + &if self.is_long_header() { "Long" } else { "Short" }, - self.fixed_bit(), - self.first_byte & !(HEADER_FORM_BIT | FIXED_BIT_MASK) - ), - ); - s.field("header_type", &self.header_type); + ) + .field("fixed", &self.fixed_bit()); match self.header_type { QuicHeaderType::QuicLong => { - // Safely access long header fields for debug output - s.field("version", &self.version().ok()); - s.field("dc_id", &self.dc_id()); - s.field("sc_id", &self.sc_id().ok()); - s.field("long_packet_type", &self.long_packet_type().ok()); + s.field("version", &self.version().ok()) + .field("dc_id", &self.dc_id()) + .field("sc_id", &self.sc_id().ok()) + .field("ptype", &self.long_packet_type().ok()); } QuicHeaderType::QuicShort { .. } => { - s.field("dc_id", &self.dc_id()); - s.field("spin_bit", &self.short_spin_bit().ok()); - s.field("key_phase", &self.short_key_phase().ok()); + s.field("dc_id", &self.dc_id()) + .field("spin", &self.short_spin_bit().ok()) + .field("phase", &self.short_key_phase().ok()); } }; s.finish() } } -// Implement TryFrom for QuicPacketType to u8 and vice-versa if needed for C interop or matching impl TryFrom for QuicPacketType { - type Error = QuicHdrError; // Using QuicHdrError for consistency with other methods + type Error = QuicHdrError; - fn try_from(value: u8) -> Result { - match value { + /// Converts a byte to a `QuicPacketType`. + /// + /// # Returns + /// `Ok(QuicPacketType)` on a valid value, `Err(QuicHdrError::InvalidPacketTypeBits)` otherwise. + fn try_from(v: u8) -> Result { + match v { 0x00 => Ok(QuicPacketType::Initial), 0x01 => Ok(QuicPacketType::ZeroRTT), 0x02 => Ok(QuicPacketType::Handshake), @@ -2045,202 +1467,133 @@ impl TryFrom for QuicPacketType { } } } - impl From for u8 { - fn from(ptype: QuicPacketType) -> Self { - ptype as u8 + #[inline(always)] + fn from(p: QuicPacketType) -> Self { + p as u8 } } #[cfg(feature = "serde")] mod serde_header_impl { use super::*; - use core::fmt; + extern crate alloc; + use alloc::vec::Vec; + use serde::{ - de::{self, Visitor}, - Deserializer, Serializer, + de::{self, Deserializer, Visitor}, + ser::{Serialize, Serializer}, }; - struct TruncatedHeaderError { - name: &'static str, - got: usize, - min: usize, - } - impl fmt::Display for TruncatedHeaderError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Truncated {}: got {} bytes, need at least {}", - self.name, self.got, self.min - ) - } - } - - struct HeaderFieldLengthExceedsMaxError { - value: usize, - max: usize, - field_name: &'static str, - } - impl fmt::Display for HeaderFieldLengthExceedsMaxError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{} {} from wire exceeds max {}", - self.field_name, self.value, self.max - ) - } - } - - impl serde::Serialize for QuicHdr { - fn serialize(&self, serializer: S) -> Result + impl Serialize for QuicHdr { + fn serialize(&self, ser: S) -> Result where S: Serializer, { - // Max possible size: 1 (first_byte) + 4 (version) + 1 (DCIL) + MAX_CID + 1 (SCIL) + MAX_CID - let mut buf = [0u8; 7 + QUIC_MAX_CID_LEN + QUIC_MAX_CID_LEN]; - let mut current_idx = 0usize; - buf[current_idx] = self.first_byte; - current_idx += 1; - match self.header_type { - QuicHeaderType::QuicLong => { - // Safety: header_type check - let long_hdr = unsafe { &self.inner.long }; - buf[current_idx..current_idx + 4].copy_from_slice(&long_hdr.version); - current_idx += 4; - let dc_len = long_hdr.dst.len() as usize; // Length from QuicDstConnLong - buf[current_idx] = long_hdr.dst.len(); - current_idx += 1; - buf[current_idx..current_idx + dc_len].copy_from_slice(long_hdr.dst.as_slice()); - current_idx += dc_len; - let sc_len = long_hdr.src.len() as usize; // Length from QuicSrcConnLong - buf[current_idx] = long_hdr.src.len(); - current_idx += 1; - buf[current_idx..current_idx + sc_len].copy_from_slice(long_hdr.src.as_slice()); - current_idx += sc_len; - } - QuicHeaderType::QuicShort { dc_id_len } => { - // For short headers, dc_id_len from header_type is authoritative. - // self.inner.short.dst.bytes contains the data. - // self.inner.short.dst.len() should match dc_id_len if consistent. - let short_hdr_dst_bytes = unsafe { &self.inner.short.dst.bytes }; // Safe due to header_type - let actual_dc_len = cmp::min(dc_id_len as usize, QUIC_MAX_CID_LEN); - if actual_dc_len > 0 { - buf[current_idx..current_idx + actual_dc_len] - .copy_from_slice(&short_hdr_dst_bytes[..actual_dc_len]); - } - current_idx += actual_dc_len; - } + let mut v = Vec::with_capacity(1 + 4 + 1 + QUIC_MAX_CID_LEN + 1 + QUIC_MAX_CID_LEN); + v.push(self.first_byte); + if self.is_long_header() { + // Safety: `is_long_header` is true, so `inner.long` is the active variant. + let long = unsafe { &self.inner.long }; + v.extend_from_slice(&long.version); + v.push(long.dst.len()); + v.extend_from_slice(long.dst.as_slice()); + v.push(long.src.len()); + v.extend_from_slice(long.src.as_slice()); + } else { + // Safety: `is_long_header` is false, so `inner.short` is the active variant. + let short = unsafe { &self.inner.short }; + v.extend_from_slice(short.dst.as_slice()); } - serializer.serialize_bytes(&buf[..current_idx]) + ser.serialize_bytes(&v) } } - struct QuicHdrVisitor; - impl<'de> Visitor<'de> for QuicHdrVisitor { + struct HdrVisitor; + impl<'de> Visitor<'de> for HdrVisitor { type Value = QuicHdr; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "raw QUIC header bytes") + f.write_str("a QUIC header") } - fn visit_bytes(self, v: &[u8]) -> Result + + fn visit_bytes(self, b: &[u8]) -> Result where E: de::Error, { - if v.is_empty() { - return Err(E::custom("QUIC header cannot be empty")); + if b.is_empty() { + return Err(E::custom("empty header")); } - let first_byte = v[0]; - let mut current_idx = 1usize; - if (first_byte & HEADER_FORM_BIT) != 0 { - // Long Header - if v.len() < QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE { - // Basic check for minimal parts - return Err(E::custom(TruncatedHeaderError { - name: "long header", - got: v.len(), - min: QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE, - })); - } - let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); - // Version - if v.len() < current_idx + 4 { - return Err(E::custom("Truncated version in long header")); - } - let mut version_bytes = [0u8; 4]; - version_bytes.copy_from_slice(&v[current_idx..current_idx + 4]); - hdr.set_version(u32::from_be_bytes(version_bytes)) - .map_err(|e| E::custom(e))?; - current_idx += 4; - if v.len() < current_idx + 1 { - return Err(E::custom("Missing DCIL in long header")); - } - let dc_len_on_wire = v[current_idx] as usize; - current_idx += 1; - if dc_len_on_wire > QUIC_MAX_CID_LEN { - return Err(E::custom(HeaderFieldLengthExceedsMaxError { - value: dc_len_on_wire, - max: QUIC_MAX_CID_LEN, - field_name: "DCID length", - })); - } - if v.len() < current_idx + dc_len_on_wire { - return Err(E::custom("Truncated DCID in long header")); - } - hdr.set_dc_id(&v[current_idx..current_idx + dc_len_on_wire]); - current_idx += dc_len_on_wire; - if v.len() < current_idx + 1 { - return Err(E::custom("Missing SCIL in long header")); - } - let sc_len_on_wire = v[current_idx] as usize; - current_idx += 1; - if sc_len_on_wire > QUIC_MAX_CID_LEN { - return Err(E::custom(HeaderFieldLengthExceedsMaxError { - value: sc_len_on_wire, - max: QUIC_MAX_CID_LEN, - field_name: "SCID length", - })); - } - if v.len() < current_idx + sc_len_on_wire { - return Err(E::custom("Truncated SCID in long header")); + + let first = b[0]; + let mut cur = &b[1..]; + let mut hdr = QuicHdr::default(); + hdr.first_byte = first; + + #[inline(always)] + fn take<'a, E>(buf: &mut &'a [u8], n: usize, msg: &'static str) -> Result<&'a [u8], E> + where + E: de::Error, + { + if buf.len() < n { + Err(E::custom(msg)) + } else { + let (h, t) = buf.split_at(n); + *buf = t; + Ok(h) } - hdr.set_sc_id(&v[current_idx..current_idx + sc_len_on_wire]) - .map_err(|e| E::custom(e))?; // Pass the error that implements Display - hdr.set_first_byte(first_byte); // Set the original first_byte - Ok(hdr) - } else { - // Short Header - // For short headers, the length of DCID is not on the wire. - // The deserializer must infer it from the remaining bytes. - // This makes QuicHeaderType::QuicShort { dc_id_len } crucial. - let dcid_bytes_from_slice = &v[current_idx..]; - let dc_id_len_parsed = - cmp::min(dcid_bytes_from_slice.len(), QUIC_MAX_CID_LEN) as u8; - let mut hdr = QuicHdr::new(QuicHeaderType::QuicShort { - dc_id_len: dc_id_len_parsed, - }); - hdr.set_dc_id(&dcid_bytes_from_slice[..dc_id_len_parsed as usize]); - hdr.set_first_byte(first_byte); // Set the original first_byte - Ok(hdr) } + + if (first & HEADER_FORM_BIT) == 0 { + // Safety: This is a short header, so we can write to `inner.short`. + unsafe { hdr.inner.short.dst.set(cur) }; + return Ok(hdr); + } + + if b.len() < QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE { + return Err(E::custom("truncated long header")); + } + // Safety: This is a long header, so we can write to `inner.long`. + let long = unsafe { &mut hdr.inner.long }; + long.version.copy_from_slice(take(&mut cur, 4, "version")?); + + let dcl = take(&mut cur, 1, "dcil")?[0] as usize; + if dcl > QUIC_MAX_CID_LEN || cur.len() < dcl { + return Err(E::custom("dcid len")); + } + long.dst.set(take(&mut cur, dcl, "dcid")?); + + let scl = take(&mut cur, 1, "scil")?[0] as usize; + if scl > QUIC_MAX_CID_LEN || cur.len() < scl { + return Err(E::custom("scid len")); + } + long.src.set(take(&mut cur, scl, "scid")?); + + Ok(hdr) } } - impl<'de> serde::Deserialize<'de> for QuicHdr { - fn deserialize(deserializer: D) -> Result + + impl<'de> de::Deserialize<'de> for QuicHdr { + fn deserialize(de: D) -> Result where D: Deserializer<'de>, { - deserializer.deserialize_bytes(QuicHdrVisitor) + de.deserialize_bytes(HdrVisitor) } } } #[cfg(test)] mod tests { - use super::*; #[cfg(feature = "serde")] use bincode; + use core::ptr::{addr_of_mut, write}; #[cfg(feature = "serde")] use serde_test::{assert_tokens, Token}; + use super::*; + #[cfg(feature = "serde")] + extern crate alloc; + #[test] fn test_min_header_len_constants() { assert_eq!(QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE, 7); @@ -2249,43 +1602,43 @@ mod tests { #[test] fn test_long_header_creation_and_accessors() { - let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); + let mut storage = QuicHdr::new(QuicHeaderType::QuicLong); + let mut hdr = storage.parse(0).unwrap(); assert!(hdr.is_long_header()); - assert_eq!(hdr.first_byte() & 0xC0, HEADER_FORM_BIT | FIXED_BIT_MASK); // Form and Fixed bits + assert_eq!(hdr.first_byte() & 0xC0, HEADER_FORM_BIT | FIXED_BIT_MASK); assert!(hdr.set_long_packet_type(QuicPacketType::ZeroRTT).is_ok()); assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::ZeroRTT)); assert!(hdr.set_reserved_bits_long(0b00).is_ok()); assert_eq!(hdr.reserved_bits_long(), Ok(0b00)); - assert!(hdr.set_packet_number_length_long(4).is_ok()); // 4 bytes PN - assert_eq!(hdr.pn_length_bits_long(), Ok(0b11)); // Encoded as 3 + assert!(hdr.set_packet_number_length_long(4).is_ok()); + assert_eq!(hdr.pn_length_bits_long(), Ok(0b11)); assert_eq!(hdr.packet_number_length_long(), Ok(4)); assert!(hdr.set_version(0x0000_0001).is_ok()); assert_eq!(hdr.version(), Ok(0x0000_0001)); let dcid_data = [1, 2, 3, 4, 5, 6, 7, 8]; let scid_data = [0xA, 0xB, 0xC, 0xD]; - hdr.set_dc_id(&dcid_data); // Sets DCID and its length (8) - assert!(hdr.set_sc_id(&scid_data).is_ok()); // Sets SCID and its length (4) + hdr.set_dc_id(&dcid_data); + assert!(hdr.set_sc_id(&scid_data).is_ok()); assert_eq!(hdr.dc_id_effective_len(), 8); assert_eq!(hdr.dc_id_len_on_wire(), Ok(8)); assert_eq!(hdr.dc_id(), &dcid_data); assert_eq!(hdr.sc_id_len_on_wire(), Ok(4)); assert_eq!(hdr.sc_id().unwrap(), &scid_data); - // Check the internal consistency of CIDs within the QuicHdrLong part unsafe { - assert_eq!(hdr.inner.long.dst.len(), 8); - assert_eq!(hdr.inner.long.dst.as_slice(), &dcid_data); - assert_eq!(hdr.inner.long.src.len(), 4); - assert_eq!(hdr.inner.long.src.as_slice(), &scid_data); + assert_eq!(hdr.hdr.inner.long.dst.len(), 8); + assert_eq!(hdr.hdr.inner.long.dst.as_slice(), &dcid_data); + assert_eq!(hdr.hdr.inner.long.src.len(), 4); + assert_eq!(hdr.hdr.inner.long.src.as_slice(), &scid_data); } } #[test] fn test_short_header_creation_and_accessors() { let dcid_data = [0xAA, 0xBB, 0xCC]; - // For short headers, DCID length is contextual. - let mut hdr = QuicHdr::new(QuicHeaderType::QuicShort { + let mut storage = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: dcid_data.len() as u8, }); + let mut hdr = storage.parse(dcid_data.len() as u8).unwrap(); assert!(!hdr.is_long_header()); assert_eq!(hdr.first_byte() & 0xC0, FIXED_BIT_MASK); // Form bit 0, Fixed bit 1 assert!(hdr.set_short_spin_bit(true).is_ok()); @@ -2311,68 +1664,54 @@ mod tests { } #[test] - fn test_header_type_transition_and_cid_reset() { - let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); - hdr.set_dc_id(&[1, 2, 3]); - assert!(hdr.set_version(123).is_ok()); - assert_eq!(hdr.dc_id(), &[1, 2, 3]); - let original_first_byte_bits = hdr.first_byte() & 0x3F; - hdr.set_header_type(QuicHeaderType::QuicShort { dc_id_len: 0 }); - assert!(!hdr.is_long_header()); - assert!(hdr.version().is_err()); - assert_eq!(hdr.dc_id_effective_len(), 0); - assert_eq!(hdr.dc_id(), &[]); - assert_eq!(hdr.first_byte() & HEADER_FORM_BIT, 0); - assert_eq!(hdr.first_byte() & FIXED_BIT_MASK, FIXED_BIT_MASK); - assert_eq!(hdr.first_byte() & 0x3F, original_first_byte_bits); - hdr.set_dc_id(&[4, 5, 6]); - assert_eq!(hdr.dc_id_effective_len(), 3); - assert_eq!(hdr.dc_id(), &[4, 5, 6]); - if let QuicHeaderType::QuicShort { dc_id_len } = hdr.header_type() { - assert_eq!(dc_id_len, 3); - } else { - panic!("Not a short header!"); + fn test_stateless_parsing_logic() { + let mut storage = QuicHdr::new(QuicHeaderType::QuicLong); + { + let mut parsed_long = storage.parse(0).unwrap(); + parsed_long.set_dc_id(&[1, 2, 3]); + assert!(parsed_long.set_version(123).is_ok()); + assert_eq!(parsed_long.dc_id(), &[1, 2, 3]); + } + storage.first_byte = FIXED_BIT_MASK; // a short header's first byte + unsafe { storage.inner.short.dst.set(&[4, 5]) }; + { + let parsed_short = storage.parse(2).unwrap(); + assert!(!parsed_short.is_long_header()); + assert_eq!(parsed_short.dc_id(), &[4, 5]); + assert!(parsed_short.version().is_err()); } - let short_first_byte_bits = hdr.first_byte() & 0x3F; - hdr.set_header_type(QuicHeaderType::QuicLong); - assert!(hdr.is_long_header()); - assert_eq!(hdr.version(), Ok(0)); // Version is reset to default - assert_eq!(hdr.dc_id_effective_len(), 0); // DCID is reset - assert_eq!(hdr.dc_id(), &[]); - assert_eq!(hdr.sc_id_len_on_wire(), Ok(0)); // SCID is reset - assert_eq!(hdr.first_byte() & HEADER_FORM_BIT, HEADER_FORM_BIT); - assert_eq!(hdr.first_byte() & FIXED_BIT_MASK, FIXED_BIT_MASK); - assert_eq!(hdr.first_byte() & 0x3F, short_first_byte_bits); } #[cfg(feature = "serde")] #[test] fn test_long_header_serde_roundtrip() { - let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); - // Set first byte: Form=1, Fixed=1, Type=0(Initial), Reserved=0, PNLEN=2 (encoded 0b01) + let mut storage = QuicHdr::new(QuicHeaderType::QuicLong); + let mut hdr = storage.parse(0).unwrap(); hdr.set_first_byte(0xC0 | (0b00 << 4) | (0b00 << 2) | 0b01); assert!(hdr.set_version(0x01020304).is_ok()); hdr.set_dc_id(&[0xAA; 8]); assert!(hdr.set_sc_id(&[0xBB; 4]).is_ok()); - let expected_first_byte = hdr.first_byte(); // Should be 0xC0 | 0b01 = 0xC1 + let expected_first_byte = hdr.first_byte(); assert_eq!(expected_first_byte, 0xC1); - - let config = bincode::config::standard(); - let bytes = bincode::serde::encode_to_vec(&hdr, config).expect("Serialization failed"); - - assert_eq!(bytes[0], expected_first_byte); - assert_eq!(&bytes[1..5], &0x01020304u32.to_be_bytes()); // Version - assert_eq!(bytes[5], 8); // DCIL - assert_eq!(&bytes[6..14], &[0xAA; 8]); // DCID - assert_eq!(bytes[14], 4); // SCIL - assert_eq!(&bytes[15..19], &[0xBB; 4]); // SCID - assert_eq!(bytes.len(), 19); // Total length - let (de, len): (QuicHdr, usize) = + let config = bincode::config::standard().with_fixed_int_encoding(); + let bytes = bincode::serde::encode_to_vec(&storage, config).expect("Serialization failed"); + let on_wire_len = 1 + 4 + 1 + 8 + 1 + 4; + assert_eq!(bytes.len(), 8 + on_wire_len); + let header_bytes = &bytes[8..]; + assert_eq!(header_bytes[0], expected_first_byte); + assert_eq!(&header_bytes[1..5], &0x01020304u32.to_be_bytes()); // Version + assert_eq!(header_bytes[5], 8); // DCIL + assert_eq!(&header_bytes[6..14], &[0xAA; 8]); // DCID + assert_eq!(header_bytes[14], 4); // SCIL + assert_eq!(&header_bytes[15..19], &[0xBB; 4]); // SCID + assert_eq!(header_bytes.len(), on_wire_len); + let (mut de_storage, len): (QuicHdr, usize) = bincode::serde::decode_from_slice(&bytes, config).expect("Deserialization failed"); assert_eq!(len, bytes.len()); + let de = de_storage.parse(0).unwrap(); assert_eq!(de.first_byte(), expected_first_byte); assert!(de.is_long_header()); - assert_eq!(de.header_type(), QuicHeaderType::QuicLong); // Deserializer sets this + assert_eq!(de.header_type(), QuicHeaderType::QuicLong); assert_eq!(de.version().unwrap(), 0x01020304); assert_eq!(de.dc_id_effective_len(), 8); assert_eq!(de.dc_id(), &[0xAA; 8]); @@ -2387,21 +1726,26 @@ mod tests { #[test] fn test_short_header_serde_roundtrip() { let dcid_data = [0xCC, 0xDD, 0xEE, 0xFF, 0x11]; - let mut hdr = QuicHdr::new(QuicHeaderType::QuicShort { + let mut storage = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: dcid_data.len() as u8, }); + let mut hdr = storage.parse(dcid_data.len() as u8).unwrap(); hdr.set_first_byte(0x40 | SHORT_SPIN_BIT_MASK | SHORT_KEY_PHASE_BIT_MASK | 0b00); // Fixed | Spin | KeyPhase | PNLEN=1 (00) hdr.set_dc_id(&dcid_data); let expected_first_byte = hdr.first_byte(); assert_eq!(expected_first_byte, 0x40 | 0x20 | 0x04 | 0b00); // 0x64 - let config = bincode::config::standard(); - let bytes = bincode::serde::encode_to_vec(&hdr, config).expect("Serialization failed"); - assert_eq!(bytes[0], expected_first_byte); - assert_eq!(&bytes[1..], &dcid_data); // DCID directly follows - assert_eq!(bytes.len(), 1 + dcid_data.len()); - let (de, len): (QuicHdr, usize) = + let config = bincode::config::standard().with_fixed_int_encoding(); + let bytes = bincode::serde::encode_to_vec(&storage, config).expect("Serialization failed"); + let on_wire_len = 1 + dcid_data.len(); + assert_eq!(bytes.len(), 8 + on_wire_len); + let header_bytes = &bytes[8..]; + assert_eq!(header_bytes[0], expected_first_byte); + assert_eq!(&header_bytes[1..], &dcid_data); + assert_eq!(header_bytes.len(), on_wire_len); + let (mut de_storage, len): (QuicHdr, usize) = bincode::serde::decode_from_slice(&bytes, config).expect("Deserialization failed"); assert_eq!(len, bytes.len()); + let de = de_storage.parse(dcid_data.len() as u8).unwrap(); assert_eq!(de.first_byte(), expected_first_byte); assert!(!de.is_long_header()); if let QuicHeaderType::QuicShort { dc_id_len } = de.header_type() { @@ -2412,9 +1756,9 @@ mod tests { assert_eq!(de.dc_id_effective_len(), dcid_data.len() as u8); assert_eq!(de.dc_id(), &dcid_data); assert_eq!(de.short_spin_bit(), Ok(true)); - assert_eq!(de.short_reserved_bits(), Ok(0b00)); // Reserved bits are 00 + assert_eq!(de.short_reserved_bits(), Ok(0b00)); assert_eq!(de.short_key_phase(), Ok(true)); - assert_eq!(de.short_pn_length_bits(), Ok(0b00)); // PNLEN=1 + assert_eq!(de.short_pn_length_bits(), Ok(0b00)); } #[test] @@ -2439,20 +1783,7 @@ mod tests { fn test_cid_serde_long_direct() { let mut cid = QuicDstConnLong::new(); cid.set(&[1, 2, 3]); - assert_tokens( - &cid, - &[ - Token::Struct { - name: "QuicDstConnLong", - len: 2, - }, - Token::Str("len"), - Token::U8(3), - Token::Str("bytes"), - Token::BorrowedBytes(&[1, 2, 3]), - Token::StructEnd, - ], - ); + assert_tokens(&cid, &[Token::Bytes(&[3, 1, 2, 3])]); } #[cfg(feature = "serde")] @@ -2460,342 +1791,287 @@ mod tests { fn test_cid_serde_short_direct() { let mut cid = QuicDstConnShort::new(); cid.set(&[1, 2, 3, 4]); - assert_tokens(&cid, &[Token::BorrowedBytes(&[1, 2, 3, 4])]); + assert_tokens(&cid, &[Token::Bytes(&[1, 2, 3, 4])]); } #[test] - fn test_parse_realistic_long_header_initial_packet() { + fn test_ebpf_like_agent_parsing_long_header() { let packet_bytes: &[u8] = &[ 0xC1, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, 0x08, - 0x08, 0x76, 0x05, 0x96, 0x95, 0xC0, 0x9A, 0x58, 0x57, + 0x05, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xAB, 0xCD, ]; - let mut current_offset = 0; - let first_byte = packet_bytes[current_offset]; - current_offset += 1; + let first_byte = packet_bytes[0]; + assert_eq!((first_byte & HEADER_FORM_BIT), HEADER_FORM_BIT); + let mut hdr_storage = QuicHdr::default(); + hdr_storage.first_byte = first_byte; + unsafe { + let long = &mut hdr_storage.inner.long; + long.version.copy_from_slice(&packet_bytes[1..5]); + long.dst.len = packet_bytes[5]; + long.dst.bytes[..8].copy_from_slice(&packet_bytes[6..14]); + long.src.len = packet_bytes[14]; + long.src.bytes[..5].copy_from_slice(&packet_bytes[15..20]); + } + let parsed = hdr_storage.parse(0).unwrap(); + assert!(parsed.is_long_header()); + assert_eq!(parsed.long_packet_type(), Ok(QuicPacketType::Initial)); + assert_eq!(parsed.packet_number_length_long(), Ok(2)); + assert_eq!(parsed.version(), Ok(0x00000001)); assert_eq!( - (first_byte & HEADER_FORM_BIT), - HEADER_FORM_BIT, - "Should be Long Header according to first byte" - ); - let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); - hdr.set_first_byte(first_byte); - let mut version_bytes = [0u8; 4]; - version_bytes.copy_from_slice(&packet_bytes[current_offset..current_offset + 4]); - current_offset += 4; - hdr.set_version(u32::from_be_bytes(version_bytes)) - .expect("Set version failed for Long Header"); - let dcid_len_on_wire = packet_bytes[current_offset]; - current_offset += 1; - assert!( - dcid_len_on_wire as usize <= QUIC_MAX_CID_LEN, - "DCID length exceeds max" - ); - let dcid_data_from_packet = - &packet_bytes[current_offset..current_offset + dcid_len_on_wire as usize]; - hdr.set_dc_id(dcid_data_from_packet); - current_offset += dcid_len_on_wire as usize; - let scid_len_on_wire = packet_bytes[current_offset]; - current_offset += 1; - assert!( - scid_len_on_wire as usize <= QUIC_MAX_CID_LEN, - "SCID length exceeds max" + parsed.dc_id(), + &[0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, 0x08] ); - let scid_data_from_packet = - &packet_bytes[current_offset..current_offset + scid_len_on_wire as usize]; - hdr.set_sc_id(scid_data_from_packet) - .expect("Set SCID failed for Long Header"); - current_offset += scid_len_on_wire as usize; - assert!(hdr.is_long_header()); - assert_eq!(hdr.fixed_bit(), 1); + assert_eq!(parsed.sc_id().unwrap(), &[0xAA, 0xBB, 0xCC, 0xDD, 0xEE]); + } + + #[test] + fn test_ebpf_like_agent_parsing_short_header() { + let known_dcid_value_from_context = [0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]; + let known_dcid_len = known_dcid_value_from_context.len() as u8; + let packet_bytes: &[u8] = &[0x45, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]; + let mut hdr_storage = QuicHdr::default(); + hdr_storage.first_byte = packet_bytes[0]; + unsafe { + hdr_storage.inner.short.dst.bytes[..known_dcid_len as usize] + .copy_from_slice(&packet_bytes[1..]); + } + let parsed = hdr_storage.parse(known_dcid_len).unwrap(); + assert!(!parsed.is_long_header()); + assert_eq!(parsed.short_spin_bit(), Ok(false)); + assert_eq!(parsed.short_key_phase(), Ok(true)); + assert_eq!(parsed.short_packet_number_length(), Ok(2)); + assert_eq!(parsed.dc_id_effective_len(), known_dcid_len); + assert_eq!(parsed.dc_id(), &known_dcid_value_from_context); + } + + #[test] + fn test_long_packet_type_enum() { + let mut storage = QuicHdr::new(QuicHeaderType::QuicLong); + let mut hdr = storage.parse(0).unwrap(); + hdr.set_first_byte(0xC0); assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::Initial)); - assert_eq!(hdr.reserved_bits_long(), Ok(0b00)); - assert_eq!(hdr.pn_length_bits_long(), Ok(0b01)); - assert_eq!(hdr.packet_number_length_long(), Ok(2)); - assert_eq!(hdr.version(), Ok(0x00000001)); - assert_eq!(hdr.dc_id_len_on_wire(), Ok(8)); + hdr.set_first_byte(0xD0); + assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::ZeroRTT)); + hdr.set_first_byte(0xE0); + assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::Handshake)); + hdr.set_first_byte(0xF0); + assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::Retry)); + } + + #[test] + fn test_cid_set_truncation() { + let mut cid = QuicDstConnShort::new(); + let oversized_data = [0u8; QUIC_MAX_CID_LEN + 5]; + cid.set(&oversized_data); + assert_eq!(cid.len() as usize, QUIC_MAX_CID_LEN); + assert_eq!(cid.as_slice(), &oversized_data[..QUIC_MAX_CID_LEN]); + } + + #[test] + fn test_parse_errors() { + let mut storage_long_dcid = QuicHdr::new(QuicHeaderType::QuicLong); + unsafe { + write( + addr_of_mut!(storage_long_dcid.inner.long.dst.len), + (QUIC_MAX_CID_LEN + 1) as u8, + ); + } + assert_eq!( - hdr.dc_id(), - &[0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, 0x08] + storage_long_dcid.parse(0).unwrap_err(), + QuicHdrError::InvalidLength ); - assert_eq!(hdr.sc_id_len_on_wire(), Ok(8)); + let mut storage_long_scid = QuicHdr::new(QuicHeaderType::QuicLong); + unsafe { + write( + addr_of_mut!(storage_long_scid.inner.long.src.len), + (QUIC_MAX_CID_LEN + 1) as u8, + ); + } assert_eq!( - hdr.sc_id().unwrap(), - &[0x76, 0x05, 0x96, 0x95, 0xC0, 0x9A, 0x58, 0x57] + storage_long_scid.parse(0).unwrap_err(), + QuicHdrError::InvalidLength ); + let mut storage_short = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: 8 }); assert_eq!( - current_offset, - 1 // first_byte - + 4 // version - + 1 // dcil - + 8 // dcid - + 1 // scil - + 8 // scid + storage_short + .parse((QUIC_MAX_CID_LEN + 1) as u8) + .unwrap_err(), + QuicHdrError::InvalidLength ); } #[test] - fn test_parse_realistic_short_header_1rtt_packet() { - // Based on RFC 9000 Appendix A.4 (1-RTT) - // First Byte: Short Header (0), Fixed Bit (1), Spin Bit (0), Reserved (00), Key Phase (1), PN Len (01 -> 2 bytes) - // 01000101 = 0x45 - let dcid_from_connection_context = [0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, 0x08]; - let contextual_dcid_len = dcid_from_connection_context.len() as u8; - let packet_bytes: &[u8] = &[ - 0x45, // First Byte - // DCID (actual bytes, length is from context, not on wire here) - 0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, - 0x08, - // Packet number (e.g., 0x00, 0x01) would follow. - ]; - let mut current_offset = 0; - let first_byte = packet_bytes[current_offset]; - current_offset += 1; + fn test_invalid_header_form_errors() { + let mut long_storage = QuicHdr::new(QuicHeaderType::QuicLong); + let mut long_hdr = long_storage.parse(0).unwrap(); assert_eq!( - (first_byte & HEADER_FORM_BIT), - 0, - "Should be Short Header according to first byte" + long_hdr.short_spin_bit(), + Err(QuicHdrError::InvalidHeaderForm) ); - let mut hdr = QuicHdr::new(QuicHeaderType::QuicShort { - dc_id_len: contextual_dcid_len, - }); - hdr.set_first_byte(first_byte); // Set the entire first byte - let dcid_data_from_packet = - &packet_bytes[current_offset..current_offset + contextual_dcid_len as usize]; - hdr.set_dc_id(dcid_data_from_packet); // This sets data and updates internal length fields - current_offset += contextual_dcid_len as usize; - assert!(!hdr.is_long_header()); // Confirmed by first_byte set earlier - assert_eq!(hdr.fixed_bit(), 1); - assert_eq!(hdr.short_spin_bit(), Ok(false)); - assert_eq!(hdr.short_reserved_bits(), Ok(0b00)); - assert_eq!(hdr.short_key_phase(), Ok(true)); - assert_eq!(hdr.short_pn_length_bits(), Ok(0b01)); // Encoded: 2 bytes actual length - assert_eq!(hdr.short_packet_number_length(), Ok(2)); - assert_eq!(hdr.dc_id_effective_len(), contextual_dcid_len); - assert_eq!(hdr.dc_id(), &dcid_from_connection_context); - assert_eq!(hdr.version(), Err(QuicHdrError::InvalidHeaderForm)); - assert_eq!(hdr.sc_id(), Err(QuicHdrError::InvalidHeaderForm)); assert_eq!( - hdr.dc_id_len_on_wire(), + long_hdr.short_key_phase(), + Err(QuicHdrError::InvalidHeaderForm) + ); + assert_eq!( + long_hdr.set_short_spin_bit(true), + Err(QuicHdrError::InvalidHeaderForm) + ); + let mut short_storage = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: 8 }); + let mut short_hdr = short_storage.parse(8).unwrap(); + assert_eq!(short_hdr.version(), Err(QuicHdrError::InvalidHeaderForm)); + assert_eq!( + short_hdr.long_packet_type(), + Err(QuicHdrError::InvalidHeaderForm) + ); + assert_eq!(short_hdr.sc_id(), Err(QuicHdrError::InvalidHeaderForm)); + assert_eq!( + short_hdr.set_version(1), Err(QuicHdrError::InvalidHeaderForm) ); - assert_eq!(current_offset, 1 + contextual_dcid_len as usize); } #[test] - fn test_ebpf_like_agent_parsing_long_header() { - let packet_bytes: &[u8] = &[ - 0xC1, // First Byte (Long, Fixed, Type=Initial, Res=0, PNLEN=2bytes) - 0x00, 0x00, 0x00, 0x01, // Version (QUIC v1) - 0x08, // DCID Len - 0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, 0x08, // DCID - 0x05, // SCID Len - 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, // SCID - 0xAB, 0xCD, // Example Packet Number (2 bytes), not parsed by QuicHdr itself - ]; - let total_packet_data_len = packet_bytes.len(); // Simulates ctx.data_end() or available length - let mut parse_ptr_offset = 0; // Simulates current read position in packet buffer - let mut quic_hdr_on_stack: QuicHdr; // Represents a stack-allocated struct - if parse_ptr_offset + 1 > total_packet_data_len { - panic!("Packet too short for first_byte"); - } - let first_byte = packet_bytes[parse_ptr_offset]; - parse_ptr_offset += 1; - if (first_byte & HEADER_FORM_BIT) != 0 { - // Is Long Header - quic_hdr_on_stack = QuicHdr::new(QuicHeaderType::QuicLong); - quic_hdr_on_stack.set_first_byte(first_byte); // Apply all bits from packet's first_byte - if parse_ptr_offset + 4 > total_packet_data_len { - panic!("Packet too short for version"); - } - let mut ver_buf = [0u8; 4]; - ver_buf.copy_from_slice(&packet_bytes[parse_ptr_offset..parse_ptr_offset + 4]); - quic_hdr_on_stack - .set_version(u32::from_be_bytes(ver_buf)) - .unwrap(); - parse_ptr_offset += 4; - if parse_ptr_offset + 1 > total_packet_data_len { - panic!("Packet too short for DCIL byte"); - } - let dcid_len_from_pkt = packet_bytes[parse_ptr_offset]; - parse_ptr_offset += 1; - if dcid_len_from_pkt as usize > QUIC_MAX_CID_LEN { - panic!("DCIL too large"); - } - if parse_ptr_offset + dcid_len_from_pkt as usize > total_packet_data_len { - panic!("Packet too short for DCID data"); - } - let mut dcid_temp_buf = [0u8; QUIC_MAX_CID_LEN]; - if dcid_len_from_pkt > 0 { - dcid_temp_buf[..dcid_len_from_pkt as usize].copy_from_slice( - &packet_bytes[parse_ptr_offset..parse_ptr_offset + dcid_len_from_pkt as usize], - ); - } - quic_hdr_on_stack.set_dc_id(&dcid_temp_buf[..dcid_len_from_pkt as usize]); - parse_ptr_offset += dcid_len_from_pkt as usize; - if parse_ptr_offset + 1 > total_packet_data_len { - panic!("Packet too short for SCIL byte"); - } - let scid_len_from_pkt = packet_bytes[parse_ptr_offset]; - parse_ptr_offset += 1; - if scid_len_from_pkt as usize > QUIC_MAX_CID_LEN { - panic!("SCIL too large"); - } - if parse_ptr_offset + scid_len_from_pkt as usize > total_packet_data_len { - panic!("Packet too short for SCID data"); - } - let mut scid_temp_buf = [0u8; QUIC_MAX_CID_LEN]; - if scid_len_from_pkt > 0 { - scid_temp_buf[..scid_len_from_pkt as usize].copy_from_slice( - &packet_bytes[parse_ptr_offset..parse_ptr_offset + scid_len_from_pkt as usize], - ); - } - quic_hdr_on_stack - .set_sc_id(&scid_temp_buf[..scid_len_from_pkt as usize]) - .unwrap(); - parse_ptr_offset += scid_len_from_pkt as usize; - assert!(quic_hdr_on_stack.is_long_header()); - assert_eq!( - quic_hdr_on_stack.long_packet_type(), - Ok(QuicPacketType::Initial) - ); - assert_eq!(quic_hdr_on_stack.packet_number_length_long(), Ok(2)); - assert_eq!(quic_hdr_on_stack.version(), Ok(0x00000001)); - assert_eq!( - quic_hdr_on_stack.dc_id(), - &[0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, 0x08] - ); - assert_eq!( - quic_hdr_on_stack.sc_id().unwrap(), - &[0xAA, 0xBB, 0xCC, 0xDD, 0xEE] - ); - let pn_actual_len = quic_hdr_on_stack.packet_number_length_long().unwrap(); - assert_eq!( - &packet_bytes[parse_ptr_offset..parse_ptr_offset + pn_actual_len], - &[0xAB, 0xCD] - ); - } else { - panic!("Test logic assumes Long Header based on first byte of test data."); - } + fn test_quic_packet_type_try_from_invalid() { + assert_eq!( + QuicPacketType::try_from(0x04), + Err(QuicHdrError::InvalidPacketTypeBits) + ); + assert_eq!( + QuicPacketType::try_from(0xFF), + Err(QuicHdrError::InvalidPacketTypeBits) + ); } #[test] - fn test_ebpf_like_agent_parsing_short_header() { - let known_dcid_value_from_context = [0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]; - let known_dcid_len_from_context = known_dcid_value_from_context.len() as u8; - let packet_bytes: &[u8] = &[ - 0x45, // First Byte (Short, Fixed, Spin=0, Res=0, KeyPhase=1, PNLEN=2bytes) - // DCID bytes follow immediately; length is known from context. - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0xBE, - 0xEF, // Example Packet Number (2 bytes), not parsed by QuicHdr itself - ]; - let total_packet_data_len = packet_bytes.len(); - let mut parse_ptr_offset = 0; - let mut quic_hdr_on_stack: QuicHdr; - if parse_ptr_offset + 1 > total_packet_data_len { - panic!("Packet too short for first_byte"); + fn test_display_and_debug_impls() { + use core::fmt::Write; + + struct StringWriter { + buffer: [u8; N], + len: usize, } - let first_byte = packet_bytes[parse_ptr_offset]; - parse_ptr_offset += 1; - if (first_byte & HEADER_FORM_BIT) == 0 { - // Is Short Header - quic_hdr_on_stack = QuicHdr::new(QuicHeaderType::QuicShort { - dc_id_len: known_dcid_len_from_context, - }); - quic_hdr_on_stack.set_first_byte(first_byte); - if known_dcid_len_from_context as usize > QUIC_MAX_CID_LEN { - panic!("Contextual DCID len too large"); + impl StringWriter { + fn new() -> Self { + Self { + buffer: [0; N], + len: 0, + } } - if parse_ptr_offset + known_dcid_len_from_context as usize > total_packet_data_len { - panic!("Packet too short for DCID data"); + fn as_str(&self) -> &str { + core::str::from_utf8(&self.buffer[..self.len]).unwrap() } - let mut dcid_temp_buf = [0u8; QUIC_MAX_CID_LEN]; - if known_dcid_len_from_context > 0 { - dcid_temp_buf[..known_dcid_len_from_context as usize].copy_from_slice( - &packet_bytes - [parse_ptr_offset..parse_ptr_offset + known_dcid_len_from_context as usize], - ); + } + impl Write for StringWriter { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + let bytes = s.as_bytes(); + if self.len + bytes.len() > self.buffer.len() { + return Err(core::fmt::Error); + } + self.buffer[self.len..self.len + bytes.len()].copy_from_slice(bytes); + self.len += bytes.len(); + Ok(()) } - quic_hdr_on_stack.set_dc_id(&dcid_temp_buf[..known_dcid_len_from_context as usize]); - parse_ptr_offset += known_dcid_len_from_context as usize; - assert!(!quic_hdr_on_stack.is_long_header()); - assert_eq!(quic_hdr_on_stack.short_spin_bit(), Ok(false)); - assert_eq!(quic_hdr_on_stack.short_key_phase(), Ok(true)); - assert_eq!(quic_hdr_on_stack.short_packet_number_length(), Ok(2)); - assert_eq!( - quic_hdr_on_stack.dc_id_effective_len(), - known_dcid_len_from_context - ); - assert_eq!(quic_hdr_on_stack.dc_id(), &known_dcid_value_from_context); - let pn_actual_len = quic_hdr_on_stack.short_packet_number_length().unwrap(); - assert_eq!( - &packet_bytes[parse_ptr_offset..parse_ptr_offset + pn_actual_len], - &[0xBE, 0xEF] - ); - } else { - panic!("Test logic assumes Short Header based on first byte of test data."); } - } - - // New tests for parse_cid_len - #[test] - fn test_quic_hdr_parse_cid_len_valid() { - assert_eq!(QuicHdr::parse_cid_len(0).unwrap(), 0); - assert_eq!(QuicHdr::parse_cid_len(8).unwrap(), 8); + let err = QuicHdrError::InvalidHeaderForm; + let mut writer = StringWriter::<128>::new(); + write!(writer, "{}", err).unwrap(); assert_eq!( - QuicHdr::parse_cid_len(QUIC_MAX_CID_LEN as u8).unwrap(), - QUIC_MAX_CID_LEN + writer.as_str(), + "Operation invalid for current QUIC header form" ); + let mut cid = QuicDstConnShort::new(); + cid.set(&[0xDE, 0xAD, 0xBE, 0xEF]); + let mut writer = StringWriter::<128>::new(); + write!(writer, "{:?}", cid).unwrap(); + assert_eq!(writer.as_str(), "QuicDstConnShort(de:ad:be:ef)"); + let long_storage = QuicHdr::new(QuicHeaderType::QuicLong); + let mut writer = StringWriter::<128>::new(); + write!(writer, "{:?}", long_storage).unwrap(); + assert!(writer.as_str().contains("is_long: true")); } #[test] - fn test_quic_hdr_parse_cid_len_invalid_too_long() { + fn test_set_packet_number_length_edge_cases() { + let mut storage = QuicHdr::new(QuicHeaderType::QuicLong); + let mut hdr = storage.parse(0).unwrap(); assert_eq!( - QuicHdr::parse_cid_len((QUIC_MAX_CID_LEN + 1) as u8), + hdr.set_packet_number_length_long(0), Err(QuicHdrError::InvalidLength) ); assert_eq!( - QuicHdr::parse_cid_len(255), // Max u8 value, likely > QUIC_MAX_CID_LEN + hdr.set_packet_number_length_long(5), Err(QuicHdrError::InvalidLength) ); + assert!(hdr.set_packet_number_length_long(1).is_ok()); + assert_eq!(hdr.packet_number_length_long(), Ok(1)); + assert!(hdr.set_packet_number_length_long(4).is_ok()); + assert_eq!(hdr.packet_number_length_long(), Ok(4)); } - // New tests for updated long_packet_type #[test] - fn test_long_packet_type_enum() { - let mut hdr_initial = QuicHdr::new(QuicHeaderType::QuicLong); - hdr_initial.set_first_byte(0xC0); // Initial - assert_eq!(hdr_initial.long_packet_type(), Ok(QuicPacketType::Initial)); - - let mut hdr_0rtt = QuicHdr::new(QuicHeaderType::QuicLong); - hdr_0rtt.set_first_byte(0xD0); // 0-RTT - assert_eq!(hdr_0rtt.long_packet_type(), Ok(QuicPacketType::ZeroRTT)); - - let mut hdr_handshake = QuicHdr::new(QuicHeaderType::QuicLong); - hdr_handshake.set_first_byte(0xE0); // Handshake - assert_eq!( - hdr_handshake.long_packet_type(), - Ok(QuicPacketType::Handshake) - ); - - let mut hdr_retry = QuicHdr::new(QuicHeaderType::QuicLong); - hdr_retry.set_first_byte(0xF0); // Retry - assert_eq!(hdr_retry.long_packet_type(), Ok(QuicPacketType::Retry)); + fn test_zero_length_cids() { + let mut storage = QuicHdr::new(QuicHeaderType::QuicLong); + let mut hdr = storage.parse(0).unwrap(); + hdr.set_dc_id(&[]); + assert!(hdr.set_sc_id(&[]).is_ok()); + assert_eq!(hdr.dc_id_effective_len(), 0); + assert_eq!(hdr.dc_id(), &[]); + assert_eq!(hdr.sc_id_len_on_wire(), Ok(0)); + assert_eq!(hdr.sc_id().unwrap(), &[]); + let mut short_storage = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: 0 }); + let mut short_hdr = short_storage.parse(0).unwrap(); + short_hdr.set_dc_id(&[]); + assert_eq!(short_hdr.dc_id_effective_len(), 0); + assert_eq!(short_hdr.dc_id(), &[]); } + #[cfg(feature = "serde")] #[test] - fn test_long_packet_type_invalid_bits() { - let mut hdr = QuicHdr::new(QuicHeaderType::QuicLong); - // Header Form = 1, Fixed Bit = 1, Type Bits = 11 (Retry), Reserved Bits = 11, PN Len = 11 - // This is valid as 0xFF is a Long Header - hdr.set_first_byte(0xFF); - assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::Retry)); - - // Short header, should fail - let mut short_hdr = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: 0 }); - short_hdr.set_first_byte(0x40); - assert_eq!( - short_hdr.long_packet_type(), - Err(QuicHdrError::InvalidHeaderForm) + fn test_deserialization_failures() { + let config = bincode::config::standard().with_fixed_int_encoding(); + let run_test = |bytes: &[u8]| -> Result<(QuicHdr, usize), _> { + let encoded = bincode::serde::encode_to_vec(bytes, config).unwrap(); + bincode::serde::decode_from_slice(&encoded, config) + }; + assert!( + run_test(&[]).is_err(), + "Deserializing empty bytes should fail" + ); + assert!( + run_test(&[0xC0, 1, 2, 3, 4, 5]).is_err(), + "Deserializing truncated long header should fail" + ); + let truncated_dcid = &[0xC0, 0, 0, 0, 1, 8, 4, 1, 2, 3, 4]; + assert!( + run_test(truncated_dcid).is_err(), + "Deserializing long header with truncated DCID should fail" + ); + let oversized_dcil = &[ + 0xC0, // Long header + 0, + 0, + 0, + 1, + (QUIC_MAX_CID_LEN + 1) as u8, + 0, + ]; + assert!( + run_test(oversized_dcil).is_err(), + "Deserializing long header with oversized DCIL should fail" + ); + let oversized_scil = &[ + 0xC0, // Long header + 0, + 0, + 0, + 1, + 0, + (QUIC_MAX_CID_LEN + 1) as u8, + ]; + assert!( + run_test(oversized_scil).is_err(), + "Deserializing long header with oversized SCIL should fail" ); } -} \ No newline at end of file +} From ccc998a49c2b4ee81a5c9efc0ed7816b81637041 Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Wed, 18 Jun 2025 22:08:06 -0500 Subject: [PATCH 07/14] Removed `Default` derives, added explicit constructors, and updated tests for flag-aware initialization in `QUIC` headers. --- src/quic.rs | 141 +++++++++++++++++++++++++--------------------------- 1 file changed, 67 insertions(+), 74 deletions(-) diff --git a/src/quic.rs b/src/quic.rs index ed4625f..f81f25b 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -10,7 +10,7 @@ use core::{ convert::TryFrom, fmt, hash::{self, Hash}, - mem, ptr, + ptr, }; /// The maximum length of a QUIC Connection ID (CID) in bytes, as per RFC 9000. @@ -115,22 +115,6 @@ mod cid_serde_helpers { use serde::de::Expected; - /// Helper struct for creating a `serde::de::Error` when expecting CID bytes. - pub struct ExpectedCidBytesInfo { - /// The expected length of the Connection ID. - pub len: u8, - } - impl fmt::Display for ExpectedCidBytesInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "CID bytes for explicit length {}", self.len) - } - } - impl Expected for ExpectedCidBytesInfo { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, f) - } - } - /// Helper struct for creating a `serde::de::Error` when a length constraint is violated. pub struct LengthExceedsMaxError { /// The invalid length that was encountered. @@ -190,13 +174,12 @@ unsafe fn raw_copy(src: *const u8, dst: *mut u8, n: usize) { /// This macro generates the following for the given `$ty`: /// - A `#[repr(C, packed)]` struct containing `len: u8` and `bytes: [u8; QUIC_MAX_CID_LEN]`. /// - An `impl` block with methods like `new()`, `len()`, `as_slice()`, and `set()`. -/// - Implementations for `Default`, `Debug`, `PartialEq`, `Eq`, and `Hash`. +/// - Implementations for `Debug`, `PartialEq`, `Eq`, and `Hash`. /// - If the `serde` feature is enabled, implementations for `Serialize` and `Deserialize` /// that respect the `$with_len_on_wire` argument. macro_rules! impl_cid_common { ($ty:ident, $doc:literal, $with_len_on_wire:tt) => { #[doc = concat!("Wrapper for a QUIC ", $doc, " Connection‑ID.")] - /// /// This struct holds a variable-length Connection ID in a fixed-size buffer, /// suitable for use in `no_std` environments like eBPF programs. It is /// generated by the `impl_cid_common!` macro. @@ -209,7 +192,7 @@ macro_rules! impl_cid_common { impl $ty { /// The total size of the struct in memory. - pub const LEN: usize = mem::size_of::(); + pub const LEN: usize = size_of::(); /// Creates a new, empty Connection ID. #[inline(always)] @@ -257,12 +240,6 @@ macro_rules! impl_cid_common { self.len = n as u8; } } - impl Default for $ty { - #[inline(always)] - fn default() -> Self { - Self::new() - } - } impl fmt::Debug for $ty { #[inline(always)] @@ -384,7 +361,7 @@ impl_cid_common!(QuicDstConnShort, "Destination (Short Hdr)", false); /// The inner payload of a QUIC Long Header. #[repr(C, packed)] -#[derive(Copy, Clone, Default, Debug)] +#[derive(Copy, Clone, Debug)] pub struct QuicHdrLong { /// The QUIC protocol version. pub version: [u8; 4], @@ -395,11 +372,21 @@ pub struct QuicHdrLong { } impl QuicHdrLong { /// The size of the struct in memory. - pub const LEN: usize = mem::size_of::(); + pub const LEN: usize = size_of::(); /// The minimum possible size of a Long Header on the wire. /// (1 byte first_byte + 4-byte version + 1 byte dcil + 1 byte scil) pub const MIN_LEN_ON_WIRE: usize = 7; + /// Creates a new, zero-initialized `QuicHdrLong`. + #[inline(always)] + pub const fn new() -> Self { + Self { + version: [0; 4], + dst: QuicDstConnLong::new(), + src: QuicSrcConnLong::new(), + } + } + /// Gets the version as a `u32`. #[inline(always)] pub fn version(&self) -> u32 { @@ -415,7 +402,7 @@ impl QuicHdrLong { /// The inner payload of a QUIC Short Header. #[repr(C, packed)] -#[derive(Copy, Clone, Default, Debug)] +#[derive(Copy, Clone, Debug)] pub struct QuicHdrShort { /// The Destination Connection ID. pub dst: QuicDstConnShort, @@ -423,10 +410,18 @@ pub struct QuicHdrShort { impl QuicHdrShort { /// The size of the struct in memory. - pub const LEN: usize = mem::size_of::(); + pub const LEN: usize = size_of::(); /// The minimum possible size of a Short Header on the wire. /// (1 byte first_byte) pub const MIN_LEN_ON_WIRE: usize = 1; + + /// Creates a new, zero-initialized `QuicHdrShort`. + #[inline(always)] + pub const fn new() -> Self { + Self { + dst: QuicDstConnShort::new(), + } + } } /// A `union` to hold either a Long or Short QUIC header payload. @@ -438,14 +433,19 @@ pub union QuicHdrUn { /// Short Header variant. pub short: QuicHdrShort, } -impl Default for QuicHdrUn { + +impl QuicHdrUn { + /// Creates a new, zero-initialized `QuicHdrUn`. + /// + /// The `long` variant is used for initialization, which zeroes the entire union. #[inline(always)] - fn default() -> Self { + pub const fn new() -> Self { Self { - long: QuicHdrLong::default(), + long: QuicHdrLong::new(), } } } + impl fmt::Debug for QuicHdrUn { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("QuicHdrUn { ... }") @@ -556,7 +556,7 @@ pub enum QuicHeaderType { /// } /// ``` #[repr(C, packed)] -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone)] pub struct QuicHdr { first_byte: u8, inner: QuicHdrUn, @@ -591,10 +591,10 @@ impl QuicHdr { match ht { QuicHeaderType::QuicLong => Self { first_byte: HEADER_FORM_BIT | FIXED_BIT_MASK, - inner: QuicHdrUn::default(), + inner: QuicHdrUn::new(), }, QuicHeaderType::QuicShort { dc_id_len } => { - let mut s = QuicHdrShort::default(); + let mut s = QuicHdrShort::new(); s.dst.len = cmp::min(dc_id_len, QUIC_MAX_CID_LEN as u8); Self { first_byte: FIXED_BIT_MASK, @@ -1167,7 +1167,7 @@ impl<'a> ParsedQuicHdr<'a> { // Unchecked, unsafe internal methods. impl<'a> ParsedQuicHdr<'a> { - /// Gets the Long Header Packet Type bits from `first_byte` without checking header form. + /// Gets the Long Header Packet Type bits from `first_byte` without checking a header form. /// /// # Returns /// The Packet Type bits value (0-3). @@ -1179,7 +1179,7 @@ impl<'a> ParsedQuicHdr<'a> { (self.hdr.first_byte & LONG_PACKET_TYPE_MASK) >> LONG_PACKET_TYPE_SHIFT } - /// Sets the Long Header Packet Type bits in `first_byte` without checking header form. + /// Sets the Long Header Packet Type bits in `first_byte` without checking a header form. /// /// # Safety /// Caller must ensure `self.is_long_header()` is true. @@ -1189,7 +1189,7 @@ impl<'a> ParsedQuicHdr<'a> { (self.hdr.first_byte & !LONG_PACKET_TYPE_MASK) | ((b & 0x03) << LONG_PACKET_TYPE_SHIFT); } - /// Gets the Reserved Bits (Long Header) from `first_byte` without checking header form. + /// Gets the Reserved Bits (Long Header) from `first_byte` without checking a header form. /// /// # Returns /// The Reserved Bits value (0-3). @@ -1201,7 +1201,7 @@ impl<'a> ParsedQuicHdr<'a> { (self.hdr.first_byte & RESERVED_BITS_LONG_MASK) >> RESERVED_BITS_LONG_SHIFT } - /// Sets the Reserved Bits (Long Header) in `first_byte` without checking header form. + /// Sets the Reserved Bits (Long Header) in `first_byte` without checking a header form. /// /// # Safety /// Caller must ensure `self.is_long_header()` is true. @@ -1211,7 +1211,7 @@ impl<'a> ParsedQuicHdr<'a> { | ((v & 0x03) << RESERVED_BITS_LONG_SHIFT); } - /// Gets the encoded Packet Number Length bits (Long Header) from `first_byte` without checking header form. + /// Gets the encoded Packet Number Length bits (Long Header) from `first_byte` without checking a header form. /// /// # Returns /// The encoded PN length bits (0-3). @@ -1223,7 +1223,7 @@ impl<'a> ParsedQuicHdr<'a> { self.hdr.first_byte & PN_LENGTH_BITS_MASK } - /// Sets the encoded Packet Number Length bits (Long Header) in `first_byte` without checking header form. + /// Sets the encoded Packet Number Length bits (Long Header) in `first_byte` without checking a header form. /// /// # Safety /// Caller must ensure `self.is_long_header()` is true. @@ -1242,7 +1242,7 @@ impl<'a> ParsedQuicHdr<'a> { self.set_pn_length_bits_long_unchecked((n - 1) as u8); } - /// Gets the Spin Bit (Short Header) from `first_byte` without checking header form. + /// Gets the Spin Bit (Short Header) from `first_byte` without checking a header form. /// /// # Returns /// The spin bit value. @@ -1254,7 +1254,7 @@ impl<'a> ParsedQuicHdr<'a> { (self.hdr.first_byte & SHORT_SPIN_BIT_MASK) != 0 } - /// Sets the Spin Bit (Short Header) in `first_byte` without checking header form. + /// Sets the Spin Bit (Short Header) in `first_byte` without checking a header form. /// /// # Safety /// Caller must ensure `self.is_long_header()` is false. @@ -1267,7 +1267,7 @@ impl<'a> ParsedQuicHdr<'a> { } } - /// Gets the Reserved Bits (Short Header) from `first_byte` without checking header form. + /// Gets the Reserved Bits (Short Header) from `first_byte` without checking a header form. /// /// # Returns /// The reserved bits value (0-3). @@ -1279,7 +1279,7 @@ impl<'a> ParsedQuicHdr<'a> { (self.hdr.first_byte & SHORT_RESERVED_BITS_MASK) >> SHORT_RESERVED_BITS_SHIFT } - /// Sets the Reserved Bits (Short Header) in `first_byte` without checking header form. + /// Sets the Reserved Bits (Short Header) in `first_byte` without checking a header form. /// /// # Safety /// Caller must ensure `self.is_long_header()` is false. @@ -1289,7 +1289,7 @@ impl<'a> ParsedQuicHdr<'a> { | ((v & 0x03) << SHORT_RESERVED_BITS_SHIFT); } - /// Gets the Key Phase bit (Short Header) from `first_byte` without checking header form. + /// Gets the Key Phase bit (Short Header) from `first_byte` without checking a header form. /// /// # Returns /// The key phase bit value. @@ -1301,7 +1301,7 @@ impl<'a> ParsedQuicHdr<'a> { (self.hdr.first_byte & SHORT_KEY_PHASE_BIT_MASK) != 0 } - /// Sets the Key Phase bit (Short Header) in `first_byte` without checking header form. + /// Sets the Key Phase bit (Short Header) in `first_byte` without checking a header form. /// /// # Safety /// Caller must ensure `self.is_long_header()` is false. @@ -1314,7 +1314,7 @@ impl<'a> ParsedQuicHdr<'a> { } } - /// Gets the encoded Packet Number Length bits (Short Header) from `first_byte` without checking header form. + /// Gets the encoded Packet Number Length bits (Short Header) from `first_byte` without checking a header form. /// /// # Returns /// The encoded PN length bits (0-3). @@ -1326,7 +1326,7 @@ impl<'a> ParsedQuicHdr<'a> { self.hdr.first_byte & PN_LENGTH_BITS_MASK } - /// Sets the encoded Packet Number Length bits (Short Header) in `first_byte` without checking header form. + /// Sets the encoded Packet Number Length bits (Short Header) in `first_byte` without checking a header form. /// /// # Safety /// Caller must ensure `self.is_long_header()` is false. @@ -1523,10 +1523,12 @@ mod serde_header_impl { if b.is_empty() { return Err(E::custom("empty header")); } - let first = b[0]; let mut cur = &b[1..]; - let mut hdr = QuicHdr::default(); + let mut hdr = QuicHdr { + first_byte: 0, + inner: QuicHdrUn::new(), + }; hdr.first_byte = first; #[inline(always)] @@ -1584,9 +1586,10 @@ mod serde_header_impl { #[cfg(test)] mod tests { + use core::ptr::{addr_of_mut, write}; + #[cfg(feature = "serde")] use bincode; - use core::ptr::{addr_of_mut, write}; #[cfg(feature = "serde")] use serde_test::{assert_tokens, Token}; @@ -1730,7 +1733,7 @@ mod tests { dc_id_len: dcid_data.len() as u8, }); let mut hdr = storage.parse(dcid_data.len() as u8).unwrap(); - hdr.set_first_byte(0x40 | SHORT_SPIN_BIT_MASK | SHORT_KEY_PHASE_BIT_MASK | 0b00); // Fixed | Spin | KeyPhase | PNLEN=1 (00) + hdr.set_first_byte(0x40 | SHORT_SPIN_BIT_MASK | SHORT_KEY_PHASE_BIT_MASK | 0b00); hdr.set_dc_id(&dcid_data); let expected_first_byte = hdr.first_byte(); assert_eq!(expected_first_byte, 0x40 | 0x20 | 0x04 | 0b00); // 0x64 @@ -1802,7 +1805,10 @@ mod tests { ]; let first_byte = packet_bytes[0]; assert_eq!((first_byte & HEADER_FORM_BIT), HEADER_FORM_BIT); - let mut hdr_storage = QuicHdr::default(); + let mut hdr_storage = QuicHdr { + first_byte: 0, + inner: QuicHdrUn::new(), + }; hdr_storage.first_byte = first_byte; unsafe { let long = &mut hdr_storage.inner.long; @@ -1829,7 +1835,10 @@ mod tests { let known_dcid_value_from_context = [0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]; let known_dcid_len = known_dcid_value_from_context.len() as u8; let packet_bytes: &[u8] = &[0x45, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]; - let mut hdr_storage = QuicHdr::default(); + let mut hdr_storage = QuicHdr { + first_byte: 0, + inner: QuicHdrUn::new(), + }; hdr_storage.first_byte = packet_bytes[0]; unsafe { hdr_storage.inner.short.dst.bytes[..known_dcid_len as usize] @@ -2047,28 +2056,12 @@ mod tests { run_test(truncated_dcid).is_err(), "Deserializing long header with truncated DCID should fail" ); - let oversized_dcil = &[ - 0xC0, // Long header - 0, - 0, - 0, - 1, - (QUIC_MAX_CID_LEN + 1) as u8, - 0, - ]; + let oversized_dcil = &[0xC0, 0, 0, 0, 1, (QUIC_MAX_CID_LEN + 1) as u8, 0]; assert!( run_test(oversized_dcil).is_err(), "Deserializing long header with oversized DCIL should fail" ); - let oversized_scil = &[ - 0xC0, // Long header - 0, - 0, - 0, - 1, - 0, - (QUIC_MAX_CID_LEN + 1) as u8, - ]; + let oversized_scil = &[0xC0, 0, 0, 0, 1, 0, (QUIC_MAX_CID_LEN + 1) as u8]; assert!( run_test(oversized_scil).is_err(), "Deserializing long header with oversized SCIL should fail" From 443ad3339dca7f2599a78c3165f83775be10adea Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Tue, 24 Jun 2025 20:40:51 -0500 Subject: [PATCH 08/14] Refactored `dc_id` and `sc_id` APIs, removed unused `parse_cid_len`, and added new helper methods for `QUIC` headers. --- src/quic.rs | 82 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/src/quic.rs b/src/quic.rs index f81f25b..d38eb88 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -641,22 +641,6 @@ impl QuicHdr { header_type, }) } - - /// Validates and returns a CID length. - /// - /// # Parameters - /// * `b`: The length byte to check. - /// - /// # Returns - /// `Ok(usize)` if the length is valid, `Err(QuicHdrError::InvalidLength)` otherwise. - #[inline(always)] - pub fn parse_cid_len(b: u8) -> Result { - if b as usize > QUIC_MAX_CID_LEN { - Err(QuicHdrError::InvalidLength) - } else { - Ok(b as usize) - } - } } impl<'a> ParsedQuicHdr<'a> { @@ -862,6 +846,7 @@ impl<'a> ParsedQuicHdr<'a> { } } + /// Gets the effective length of the Destination Connection ID. /// /// For Long Headers, this length is read directly from the header data. For Short @@ -871,9 +856,9 @@ impl<'a> ParsedQuicHdr<'a> { /// # Returns /// The length of the Destination Connection ID in bytes. #[inline(always)] - pub fn dc_id_effective_len(&self) -> u8 { + pub fn dc_id_len(&self) -> u8 { match self.header_type { - // Safety: Header form is known. + // Safety: A header form is known. QuicHeaderType::QuicLong => unsafe { self.hdr.inner.long.dst.len() }, QuicHeaderType::QuicShort { dc_id_len } => dc_id_len, } @@ -881,22 +866,49 @@ impl<'a> ParsedQuicHdr<'a> { /// Gets a slice containing the bytes of the Destination Connection ID. /// - /// The length of the slice is determined by `dc_id_effective_len()`. + /// The length of the slice is determined by `dc_id_len()`. /// /// # Returns /// A byte slice (`&[u8]`) representing the Destination Connection ID. #[inline(always)] pub fn dc_id(&self) -> &[u8] { - let n = self.dc_id_effective_len() as usize; + let n = self.dc_id_len() as usize; let raw = match self.header_type { // Safety: We are in a `ParsedQuicHdr`, so we know which union variant is active. QuicHeaderType::QuicLong => unsafe { &self.hdr.inner.long.dst.bytes }, QuicHeaderType::QuicShort { .. } => unsafe { &self.hdr.inner.short.dst.bytes }, }; - // Safety: `dc_id_effective_len` is bounded by `QUIC_MAX_CID_LEN` during parsing. + // Safety: `dc_id_len` is bounded by `QUIC_MAX_CID_LEN` during parsing. unsafe { raw.get_unchecked(..n) } } + /// Gets a slice containing the bytes of the Destination Connection ID. + /// + /// # Returns + /// A byte slice (`[u8; QUIC_MAX_CID_LEN]`) representing the Destination Connection ID. + #[inline(always)] + pub fn dc_id_raw(&self) -> [u8; QUIC_MAX_CID_LEN] { + match self.header_type { + // Safety: We are in a `ParsedQuicHdr`, so we know which union variant is active. + QuicHeaderType::QuicLong => unsafe { self.hdr.inner.long.dst.bytes }, + QuicHeaderType::QuicShort { .. } => unsafe { self.hdr.inner.short.dst.bytes }, + } + } + + /// Gets the effective length of the Source Connection ID bytes if this is a Long Header. + /// + /// # Returns + /// `Ok(u8)` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] + pub fn sc_id_len(&self) -> Result { + if !self.is_long_header() { + Err(QuicHdrError::InvalidHeaderForm) + } else { + // Safety: A header form has been checked. + Ok(unsafe { self.hdr.inner.long.src.as_slice().len() as u8 }) + } + } + /// Gets a slice of the Source Connection ID bytes if this is a Long Header. /// /// # Returns @@ -911,6 +923,20 @@ impl<'a> ParsedQuicHdr<'a> { } } + /// Gets a slice of the Source Connection ID bytes with padding if this is a Long Header. + /// + /// # Returns + /// `Ok([u8; QUIC_MAX_CID_LEN])` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. + #[inline(always)] + pub fn sc_id_raw(&self) -> Result<[u8; QUIC_MAX_CID_LEN], QuicHdrError> { + if !self.is_long_header() { + Err(QuicHdrError::InvalidHeaderForm) + } else { + // Safety: A header form has been checked. + Ok(unsafe { self.hdr.inner.long.src.bytes }) + } + } + /// Returns the logical header type determined during parsing. /// /// # Returns @@ -1622,7 +1648,7 @@ mod tests { let scid_data = [0xA, 0xB, 0xC, 0xD]; hdr.set_dc_id(&dcid_data); assert!(hdr.set_sc_id(&scid_data).is_ok()); - assert_eq!(hdr.dc_id_effective_len(), 8); + assert_eq!(hdr.dc_id_len(), 8); assert_eq!(hdr.dc_id_len_on_wire(), Ok(8)); assert_eq!(hdr.dc_id(), &dcid_data); assert_eq!(hdr.sc_id_len_on_wire(), Ok(4)); @@ -1659,7 +1685,7 @@ mod tests { } else { panic!("Header type mismatch after setting DCID for short header."); } - assert_eq!(hdr.dc_id_effective_len(), dcid_data.len() as u8); + assert_eq!(hdr.dc_id_len(), dcid_data.len() as u8); assert_eq!(hdr.dc_id(), &dcid_data); assert!(hdr.sc_id_len_on_wire().is_err()); assert!(hdr.sc_id().is_err()); @@ -1716,7 +1742,7 @@ mod tests { assert!(de.is_long_header()); assert_eq!(de.header_type(), QuicHeaderType::QuicLong); assert_eq!(de.version().unwrap(), 0x01020304); - assert_eq!(de.dc_id_effective_len(), 8); + assert_eq!(de.dc_id_len(), 8); assert_eq!(de.dc_id(), &[0xAA; 8]); assert_eq!(de.sc_id_len_on_wire().unwrap(), 4); assert_eq!(de.sc_id().unwrap(), &[0xBB; 4]); @@ -1756,7 +1782,7 @@ mod tests { } else { panic!("Deserialized to wrong header type: {:?}", de.header_type()); } - assert_eq!(de.dc_id_effective_len(), dcid_data.len() as u8); + assert_eq!(de.dc_id_len(), dcid_data.len() as u8); assert_eq!(de.dc_id(), &dcid_data); assert_eq!(de.short_spin_bit(), Ok(true)); assert_eq!(de.short_reserved_bits(), Ok(0b00)); @@ -1849,7 +1875,7 @@ mod tests { assert_eq!(parsed.short_spin_bit(), Ok(false)); assert_eq!(parsed.short_key_phase(), Ok(true)); assert_eq!(parsed.short_packet_number_length(), Ok(2)); - assert_eq!(parsed.dc_id_effective_len(), known_dcid_len); + assert_eq!(parsed.dc_id_len(), known_dcid_len); assert_eq!(parsed.dc_id(), &known_dcid_value_from_context); } @@ -2024,14 +2050,14 @@ mod tests { let mut hdr = storage.parse(0).unwrap(); hdr.set_dc_id(&[]); assert!(hdr.set_sc_id(&[]).is_ok()); - assert_eq!(hdr.dc_id_effective_len(), 0); + assert_eq!(hdr.dc_id_len(), 0); assert_eq!(hdr.dc_id(), &[]); assert_eq!(hdr.sc_id_len_on_wire(), Ok(0)); assert_eq!(hdr.sc_id().unwrap(), &[]); let mut short_storage = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: 0 }); let mut short_hdr = short_storage.parse(0).unwrap(); short_hdr.set_dc_id(&[]); - assert_eq!(short_hdr.dc_id_effective_len(), 0); + assert_eq!(short_hdr.dc_id_len(), 0); assert_eq!(short_hdr.dc_id(), &[]); } From 9f3a177ae4c052c27499ad64f40824fe502d8a3a Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Thu, 26 Jun 2025 00:08:04 -0500 Subject: [PATCH 09/14] Refactored `src/quic.rs` by cleaning up unused enums and structs, adding constants for QUIC fields, simplifying error handling, and improving overall readability. --- Cargo.toml | 1 - src/lib.rs | 1 + src/macros.rs | 82 ++ src/quic.rs | 2782 ++++++++++++++++++------------------------------- 4 files changed, 1118 insertions(+), 1748 deletions(-) create mode 100644 src/macros.rs diff --git a/Cargo.toml b/Cargo.toml index a4ab299..3839f2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ serde = { version = "1", default-features = false, features = ["derive"], option aya-ebpf = { git = "https://github.com/aya-rs/aya", branch = "main" } aya-log-ebpf = { git = "https://github.com/aya-rs/aya", branch = "main" } bincode = { version = "2.0.0-rc.3", default-features = false, features = ["serde", "alloc"] } -serde_test = { version = "1.0", default-features = false } [features] serde = ["dep:serde"] diff --git a/src/lib.rs b/src/lib.rs index abaee06..678359a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,3 +15,4 @@ pub mod udp; pub mod vlan; pub mod vxlan; pub mod llc; +mod macros; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..d7e9667 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,82 @@ +/// Reads a variable-length buffer from a TC context with a maximum length of 32 bytes. +/// +/// This macro reads up to `$max_len` bytes from the given TC context `$tc_ctx` +/// starting at offset `$off`. The actual number of bytes read is determined by `$len`. +/// +/// The `$max_len` is capped at 32 bytes due to limitations in the eBPF verifier, +/// which restricts the complexity of loops in kernel space to ensure termination. +/// This macro will fail to compile if `$max_len` is greater than 32. +/// +/// # Arguments +/// * `$tc_ctx`: The traffic control context to read from. +/// * `$off`: A mutable variable holding the current offset, which will be advanced. +/// * `$buf`: The destination buffer to write the bytes into. +/// * `$len`: The number of bytes to read. +/// * `$max_len`: The maximum number of bytes to read, cannot exceed 32. +/// +/// # Returns +/// `Ok(())` on success. On failure to load a byte from the context returns an `Err`. +#[macro_export] +macro_rules! read_var_buf_32 { + ($tc_ctx:expr, $off:ident, $buf:expr, $len:expr, $max_len:expr) => { + (|| -> Result<(), ()> { + // This will cause a compile-time error if $max_len is greater than 32. + const _: () = assert!($max_len <= 32, "$max_len cannot be greater than 32"); + for i in 0..core::cmp::min(16, $max_len) { + if usize::from(i as u8) >= $len { + return Ok(()); + } + $buf[i] = $tc_ctx.load($off).map_err(|_| ())?; + $off += 1; + } + if $max_len > 16 { + for i in 16..$max_len { + if usize::from(i as u8) >= $len { + return Ok(()); + } + $buf[i] = $tc_ctx.load($off).map_err(|_| ())?; + $off += 1; + } + } + Ok(()) + })() + }; +} + +/// Reads a QUIC variable-length integer from a TC context into a buffer. +/// +/// This macro reads a variable-length integer, storing its raw bytes in `$buf`. +/// It takes `$len_byte` (the first byte of the var-int) which it stores as the +/// first byte of the buffer. It then calculates the total length of the var-int +/// from the first two bits of `$len_byte` and reads the remaining bytes. +/// +/// # Arguments +/// * `$tc_ctx`: The traffic control context to read from. +/// * `$off`: A mutable variable holding the current offset, which will be advanced. +/// * `$buf`: The destination buffer to write the bytes into. +/// * `$len_byte`: The first byte of the variable-length integer. +/// * `$max_len`: The maximum capacity of the buffer cannot exceed 16. +/// +/// # Returns +/// `Ok(())` on success. On failure to load a byte from the context returns an `Err`. +#[macro_export] +macro_rules! read_var_buf_from_len_byte_16 { + ($tc_ctx:expr, $off:ident, $buf:expr, $len_byte:expr, $max_len:expr) => { + (|| -> Result<(), ()> { + const _: () = assert!($max_len <= 16, "$max_len cannot be greater than 16"); + let len = 1 << ($len_byte >> 6); + if $max_len < 1 { + return Err(()); + } + $buf[0] = $len_byte; + for i in 1..core::cmp::min(16, $max_len) { + if i >= len { + return Ok(()); + } + $buf[i] = $tc_ctx.load($off).map_err(|_| ())?; + $off += 1; + } + Ok(()) + })() + }; +} diff --git a/src/quic.rs b/src/quic.rs index d38eb88..cd61569 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -1,20 +1,16 @@ -//! QUIC header and Connection‑ID types with full support for -//! variable‑length IDs, long/short header forms and zero‑allocation -//! (kernel‑friendly) (de)serialization. -//! -//! Designed for use inside eBPF (`aya`) programs → `#![no_std]`, -//! fixed‑capacity buffers, no heap, packed layouts. - -use core::{ - cmp, - convert::TryFrom, - fmt, - hash::{self, Hash}, - ptr, -}; - -/// The maximum length of a QUIC Connection ID (CID) in bytes, as per RFC 9000. +use core::mem; + +/// The maximum supported length for a QUIC Connection ID (CID), as per RFC 9000. pub const QUIC_MAX_CID_LEN: usize = 20; +/// The maximum supported length for a QUIC Address Validation Token. +pub const QUIC_MAX_TOKEN_LEN: usize = 4; +/// The maximum supported length for the QUIC Length field, encoded as a variable-length integer. +pub const QUIC_MAX_LENGTH: usize = 8; +/// The default length for the Destination Connection ID (DCID) in a short header. +/// This value is an assumption, as the actual length is negotiated during the handshake. +pub const QUIC_SHORT_DEFAULT_DC_ID_LEN: u8 = 8; + +// Masks and shifts for decoding the first byte of a QUIC packet. const HEADER_FORM_BIT: u8 = 0x80; const FIXED_BIT_MASK: u8 = 0x40; const LONG_PACKET_TYPE_MASK: u8 = 0x30; @@ -22,2075 +18,1367 @@ const LONG_PACKET_TYPE_SHIFT: u8 = 4; const RESERVED_BITS_LONG_MASK: u8 = 0x0C; const RESERVED_BITS_LONG_SHIFT: u8 = 2; const SHORT_SPIN_BIT_MASK: u8 = 0x20; +const SHORT_SPIN_BIT_SHIFT: u8 = 5; const SHORT_RESERVED_BITS_MASK: u8 = 0x18; const SHORT_RESERVED_BITS_SHIFT: u8 = 3; const SHORT_KEY_PHASE_BIT_MASK: u8 = 0x04; +const SHORT_KEY_PHASE_BIT_SHIFT: u8 = 2; const PN_LENGTH_BITS_MASK: u8 = 0x03; -/// Errors that can occur during QUIC header parsing and manipulation. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum QuicHdrError { - /// An operation was attempted that is not valid for the current header form (e.g., reading a version from a Short Header). - InvalidHeaderForm, - /// A length field (e.g., for a Connection ID) was invalid or exceeded the maximum allowed size. - InvalidLength, - /// The packet type bits in a Long Header were not one of the valid, known values. - InvalidPacketTypeBits, -} - -impl fmt::Display for QuicHdrError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::InvalidHeaderForm => { - write!(f, "Operation invalid for current QUIC header form") - } - Self::InvalidLength => write!(f, "invalid length value for QUIC header"), - Self::InvalidPacketTypeBits => { - write!(f, "invalid packet type bits for QUIC long header") - } - } - } -} - -/// QUIC Long Header Packet Types, as per RFC 9000 Section 17.2. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[repr(u8)] -pub enum QuicPacketType { - /// Initial packet. - Initial = 0x00, - /// 0-RTT packet. - ZeroRTT = 0x01, - /// Handshake packet. - Handshake = 0x02, - /// Retry packet. - Retry = 0x03, -} - -/// QUIC Transport Error Codes, as per RFC 9000 Section 20. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[repr(u16)] -#[allow(non_camel_case_types)] -pub enum QuicTransportError { - /// No error. This is used when the connection is closed gracefully. - NoError = 0x0, - /// An internal error occurred in the endpoint. - InternalError = 0x1, - /// The server refused the connection. - ConnectionRefused = 0x2, - /// A flow control limit was violated. - FlowControlError = 0x3, - /// The number of streams exceeded the negotiated limit. - StreamLimitError = 0x4, - /// An operation was attempted on a stream in an invalid state. - StreamStateError = 0x5, - /// The final size of a stream is incorrect. - FinalSizeError = 0x6, - /// A frame was malformed. - FrameFormatError = 0x7, - /// A transport parameter was invalid. - TransportParameterError = 0x8, - /// The number of connection IDs exceeded the negotiated limit. - ConnectionIdLimitError = 0x9, - /// A general protocol violation was detected. - ProtocolViolation = 0xA, - /// A token (e.g., for retry or new token) was invalid. - InvalidToken = 0xB, - /// An application-specific error occurred. - ApplicationError = 0xC, - /// The crypto buffer was exceeded. - CryptoBufferExceeded = 0xD, - /// An error occurred during a key update. - KeyUpdateError = 0xE, - /// The AEAD confidentiality or integrity limit was reached. - AeadLimitReached = 0xF, - /// The endpoint has no viable network path. - NoViablePath = 0x10, -} - -/// Serde helper types for custom error messages. -#[cfg(feature = "serde")] -mod cid_serde_helpers { - use core::fmt; - - use serde::de::Expected; - - /// Helper struct for creating a `serde::de::Error` when a length constraint is violated. - pub struct LengthExceedsMaxError { - /// The invalid length that was encountered. - pub value: usize, - /// The maximum allowed length. - pub max: usize, - /// The name of the field with the invalid length. - pub field_name: &'static str, - } - impl fmt::Display for LengthExceedsMaxError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{} {} exceeds max {}", - self.field_name, self.value, self.max - ) - } - } - impl Expected for LengthExceedsMaxError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, f) - } - } -} - -/// A raw, fast, unchecked memory copy. +/// An enum representing errors that can occur while processing Quic headers. /// -/// # Safety -/// Caller must ensure that `src` and `dst` are valid for reads and writes -/// of `n` bytes, respectively, and that they do not overlap. -#[inline(always)] -unsafe fn raw_copy(src: *const u8, dst: *mut u8, n: usize) { - ptr::copy_nonoverlapping(src, dst, n); +/// # Variants +/// - `InvalidQuicType`: Indicates an attempt to access a field with an incompatible Quic message type. +/// For example, trying to access echo fields on a redirect message. +#[derive(Debug)] +pub enum QuicError { + InvalidQuicType, } -/// Generates a struct and associated implementations for a QUIC Connection ID (CID). -/// -/// QUIC CIDs are variable-length identifiers. To handle them in a `no_std`, -/// zero-allocation context like eBPF, this macro creates a struct that pairs -/// a `len` field with a fixed-size byte array. This allows for efficient, -/// direct mapping onto packet data. -/// -/// # Arguments -/// -/// * `$ty:ident`: The name for the new CID struct (e.g., `QuicDstConnLong`). -/// * `$doc:literal`: A string literal describing the CID type, used to generate -/// the struct's documentation (e.g., `"Destination (Long Hdr)"`). -/// * `$with_len_on_wire:tt`: A boolean (`true` or `false`) that controls the -/// `serde` (de)serialization format. -/// - `true`: The serialized format is `[length_byte, ...cid_bytes]`. This is -/// used for CIDs in Long Headers. -/// - `false`: The serialized format is just `[...cid_bytes]`. This is used for -/// the Destination CID in Short Headers, where the length is implicit. +/// Parses a QUIC header from a network buffer within an eBPF context. /// -/// # Generated Code +/// This macro is designed to be used in eBPF programs, specifically TC (Traffic Control) +/// classifiers, to inspect QUIC packets. It reads from a `TcContext`, parsing the +/// packet data into a `QuicHdr` struct, which can be either a `Long` or `Short` header variant. /// -/// This macro generates the following for the given `$ty`: -/// - A `#[repr(C, packed)]` struct containing `len: u8` and `bytes: [u8; QUIC_MAX_CID_LEN]`. -/// - An `impl` block with methods like `new()`, `len()`, `as_slice()`, and `set()`. -/// - Implementations for `Debug`, `PartialEq`, `Eq`, and `Hash`. -/// - If the `serde` feature is enabled, implementations for `Serialize` and `Deserialize` -/// that respect the `$with_len_on_wire` argument. -macro_rules! impl_cid_common { - ($ty:ident, $doc:literal, $with_len_on_wire:tt) => { - #[doc = concat!("Wrapper for a QUIC ", $doc, " Connection‑ID.")] - /// This struct holds a variable-length Connection ID in a fixed-size buffer, - /// suitable for use in `no_std` environments like eBPF programs. It is - /// generated by the `impl_cid_common!` macro. - #[repr(C, packed)] - #[derive(Copy, Clone)] - pub struct $ty { - len: u8, - bytes: [u8; QUIC_MAX_CID_LEN], - } - - impl $ty { - /// The total size of the struct in memory. - pub const LEN: usize = size_of::(); - - /// Creates a new, empty Connection ID. - #[inline(always)] - pub const fn new() -> Self { - Self { - len: 0, - bytes: [0; QUIC_MAX_CID_LEN], - } - } - - /// Returns the actual length of the Connection ID in bytes. - #[inline(always)] - pub const fn len(&self) -> u8 { - self.len - } - - /// Returns `true` if the Connection ID has a length of zero. - #[inline(always)] - pub const fn is_empty(&self) -> bool { - self.len == 0 - } - - /// Returns a slice of the actual Connection ID bytes. - #[inline(always)] - pub fn as_slice(&self) -> &[u8] { - // Safety: `self.len` is guaranteed by the `set` method to be <= `QUIC_MAX_CID_LEN`. - unsafe { self.bytes.get_unchecked(..self.len as usize) } - } - - /// Returns a mutable slice of the actual Connection ID bytes. - #[inline(always)] - pub fn as_mut_slice(&mut self) -> &mut [u8] { - // Safety: `self.len` is guaranteed by the `set` method to be <= `QUIC_MAX_CID_LEN`. - unsafe { self.bytes.get_unchecked_mut(..self.len as usize) } - } - - /// Sets the Connection ID from a slice. - /// - /// The data is truncated if it is longer than `QUIC_MAX_CID_LEN`. - #[inline(always)] - pub fn set(&mut self, data: &[u8]) { - let n = cmp::min(data.len(), QUIC_MAX_CID_LEN); - // Safety: `n` is bounded by `QUIC_MAX_CID_LEN`, ensuring no buffer overflow. - unsafe { raw_copy(data.as_ptr(), self.bytes.as_mut_ptr(), n) }; - self.len = n as u8; - } - } - - impl fmt::Debug for $ty { - #[inline(always)] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(stringify!($ty))?; - f.write_str("(")?; - for (i, b) in self.as_slice().iter().enumerate() { - if i != 0 { - f.write_str(":")?; - } - write!(f, "{:02x}", b)?; - } - f.write_str(")") - } - } - impl PartialEq for $ty { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - self.as_slice() == other.as_slice() - } - } - impl Eq for $ty {} - impl Hash for $ty { - #[inline(always)] - fn hash(&self, h: &mut H) { - self.len.hash(h); - h.write(self.as_slice()); - } - } - - #[cfg(feature = "serde")] - const _: () = { - use serde::{ - de::{self, Visitor}, - Deserializer, Serializer, - }; - - use crate::quic::cid_serde_helpers; - - struct CidVisitor; - impl<'de> Visitor<'de> for CidVisitor { - type Value = $ty; - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("a byte slice for a QUIC Connection‑ID") - } - fn visit_bytes(self, v: &[u8]) -> Result<$ty, E> - where - E: de::Error, - { - let data = if $with_len_on_wire { - if v.is_empty() { - return Err(de::Error::invalid_length(0, &"empty CID")); - } - let l = v[0] as usize; - if v.len() != l + 1 { - return Err(de::Error::invalid_value( - de::Unexpected::Bytes(v), - &"malformed length‑prefixed CID", - )); - } - &v[1..] - } else { - v - }; - - if data.len() > QUIC_MAX_CID_LEN { - return Err(de::Error::invalid_length( - data.len(), - &cid_serde_helpers::LengthExceedsMaxError { - value: data.len(), - max: QUIC_MAX_CID_LEN, - field_name: "CID length", - }, - )); - } - let mut id = $ty::new(); - id.set(data); - Ok(id) - } - } - - impl serde::Serialize for $ty { - fn serialize(&self, ser: S) -> Result - where - S: Serializer, - { - if $with_len_on_wire { - let mut buf = [0u8; 1 + QUIC_MAX_CID_LEN]; - buf[0] = self.len(); - // Safety: `self.len` is always <= `QUIC_MAX_CID_LEN`. - unsafe { - raw_copy( - self.bytes.as_ptr(), - buf[1..].as_mut_ptr(), - self.len as usize, - ) - }; - ser.serialize_bytes(&buf[..1 + self.len as usize]) - } else { - ser.serialize_bytes(self.as_slice()) - } - } - } - impl<'de> serde::Deserialize<'de> for $ty { - fn deserialize(deser: D) -> Result - where - D: Deserializer<'de>, - { - deser.deserialize_bytes(CidVisitor) - } - } - }; - }; -} - -impl_cid_common!(QuicDstConnLong, "Destination (Long Hdr)", true); -impl_cid_common!(QuicSrcConnLong, "Source (Long Hdr)", true); -impl_cid_common!(QuicDstConnShort, "Destination (Short Hdr)", false); - -/// The inner payload of a QUIC Long Header. -#[repr(C, packed)] -#[derive(Copy, Clone, Debug)] -pub struct QuicHdrLong { - /// The QUIC protocol version. - pub version: [u8; 4], - /// The Destination Connection ID. - pub dst: QuicDstConnLong, - /// The Source Connection ID. - pub src: QuicSrcConnLong, -} -impl QuicHdrLong { - /// The size of the struct in memory. - pub const LEN: usize = size_of::(); - /// The minimum possible size of a Long Header on the wire. - /// (1 byte first_byte + 4-byte version + 1 byte dcil + 1 byte scil) - pub const MIN_LEN_ON_WIRE: usize = 7; - - /// Creates a new, zero-initialized `QuicHdrLong`. - #[inline(always)] - pub const fn new() -> Self { - Self { - version: [0; 4], - dst: QuicDstConnLong::new(), - src: QuicSrcConnLong::new(), - } - } - - /// Gets the version as a `u32`. - #[inline(always)] - pub fn version(&self) -> u32 { - u32::from_be_bytes(self.version) - } - - /// Sets the version from a `u32`. - #[inline(always)] - pub fn set_version(&mut self, v: u32) { - self.version = v.to_be_bytes(); - } -} - -/// The inner payload of a QUIC Short Header. -#[repr(C, packed)] -#[derive(Copy, Clone, Debug)] -pub struct QuicHdrShort { - /// The Destination Connection ID. - pub dst: QuicDstConnShort, -} - -impl QuicHdrShort { - /// The size of the struct in memory. - pub const LEN: usize = size_of::(); - /// The minimum possible size of a Short Header on the wire. - /// (1 byte first_byte) - pub const MIN_LEN_ON_WIRE: usize = 1; - - /// Creates a new, zero-initialized `QuicHdrShort`. - #[inline(always)] - pub const fn new() -> Self { - Self { - dst: QuicDstConnShort::new(), - } - } -} - -/// A `union` to hold either a Long or Short QUIC header payload. -#[repr(C, packed)] -#[derive(Copy, Clone)] -pub union QuicHdrUn { - /// Long Header variant. - pub long: QuicHdrLong, - /// Short Header variant. - pub short: QuicHdrShort, -} - -impl QuicHdrUn { - /// Creates a new, zero-initialized `QuicHdrUn`. - /// - /// The `long` variant is used for initialization, which zeroes the entire union. - #[inline(always)] - pub const fn new() -> Self { - Self { - long: QuicHdrLong::new(), - } - } -} - -impl fmt::Debug for QuicHdrUn { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("QuicHdrUn { ... }") - } -} - -/// The logical type of QUIC header. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum QuicHeaderType { - /// A Long Header. All connection ID lengths are encoded on the wire. - QuicLong, - /// A Short Header. The destination connection ID length must be known from context. - QuicShort { dc_id_len: u8 }, -} - -/// A raw, on-the-wire representation of a QUIC header. +/// The macro handles the complexity of QUIC's variable-length fields, such as +/// Connection IDs, Tokens, and Packet Numbers, by reading them byte-by-byte from the +/// context and advancing an offset tracker. /// -/// This struct is intended to provide access to QUIC header fields. -/// The actual parsing and interpretation of variable-length fields (like CIDs, -/// Packet Number, Token, Length) often require sequential reading and context. +/// # Arguments /// -/// For Long Headers (RFC 9000, Section 17.2): -/// +-+-+-+-+-+-+-+-+ -/// |1|1|T T|X X X X| Header Form (1), Fixed Bit (1), Long Packet Type (2), Type-Specific (4) -/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/// | Version (32) | -/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/// | DCID Len (8) | -/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/// | Destination Connection ID (0..160) ... -/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/// | SCID Len (8) | -/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/// | Source Connection ID (0..160) ... -/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/// ... Type-specific fields (e.g., Token Length, Token for Initial; Length for others) ... -/// ... Packet Number (8, 16, 24, or 32 bits) ... +/// * `$ctx`: An expression that provides the `TcContext`. This is the source of the packet data. +/// * `$off`: A mutable `usize` variable representing the current byte offset within the `$ctx`. +/// The macro will increase this offset as it consumes bytes from the header. The caller is +/// responsible for initializing `$off` at the start of the QUIC header. +/// * `$short_dc_id_len`: An expression evaluating to a `u8`. This specifies the expected length +/// of the Destination Connection ID (DCID) for QUIC Short Headers. Unlike Long Headers, +/// Short Headers do not encode the DCID length, so it must be known from the connection's +/// context. /// -/// For Short Headers (RFC 9000, Section 17.3): -/// +-+-+-+-+-+-+-+-+ -/// |0|1|S|R R|K K|P P| Header Form (0), Fixed Bit (1), Spin Bit (S), Reserved (R R), Key Phase (K K), Packet Number Length (P P) -/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/// | Destination Connection ID (0..160) ... -> Optional, length implicit from context -/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/// | Packet Number (8, 16, 24, or 32 bits) ... -/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/// | Protected Payload (*) ... -/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// # Returns /// -/// This struct is designed to be safely loaded directly from packet data in -/// `no_std` environments like eBPF. After loading, call [`QuicHdr::parse()`] -/// to get a [`ParsedQuicHdr`], which provides a safe API for accessing header fields. +/// This macro evaluates to a `Result<$crate::quic::QuicHdr, ()>`. +/// - `Ok(QuicHdr)`: On successful parsing, contains the populated `QuicHdr` enum, which will +/// be either `QuicHdr::Long` or `QuicHdr::Short`. +/// - `Err(())`: If an error occurs during parsing, such as trying to read beyond the +/// bounds of the packet buffer. This allows for safe error handling within the eBPF program. /// -/// # Example (in an `aya_ebpf` kernel-space program) +/// # Example /// -/// This example demonstrates how to parse a QUIC header from a UDP packet -/// within a TC (Traffic Control) eBPF program. +/// The following example demonstrates how to use `parse_quic_hdr!` within a typical +/// eBPF TC program function. The key is to first parse the Ethernet, IP, and UDP +/// headers to correctly advance the offset to the beginning of the QUIC payload. /// -/// ```no_run -/// # use aya_ebpf::{programs::TcContext, macros::classifier}; -/// # use network_types::{ -/// # eth::EthHdr, -/// # ip::{IpProto, Ipv4Hdr}, -/// # udp::UdpHdr, -/// # quic::{QuicHdr, QuicHdrError}, -/// # }; +/// ```rust +/// # use aya_ebpf::programs::TcContext; +/// # use aya_ebpf::maps::HashMap; +/// # use aya_ebpf::bindings::{TC_ACT_OK, TC_ACT_SHOT}; +/// # use network_types::eth::EthHdr; +/// # use network_types::ip::{IpProto, Ipv4Hdr, Ipv6Hdr}; +/// # use network_types::udp::UdpHdr; +/// # use network_types::quic::{QuicHdr, QUIC_SHORT_DEFAULT_DC_ID_LEN}; +/// # use network_types::parse_quic_hdr; /// # -/// #[classifier] -/// pub fn my_quic_parser(ctx: TcContext) -> i32 { -/// match try_my_quic_parser(ctx) { -/// Ok(ret) => ret, -/// Err(_) => 1, // TC_ACT_SHOT -/// } -/// } +/// fn try_parse_packet(ctx: &TcContext) -> Result<(), i32> { +/// // Start at the beginning of the packet. +/// let mut offset = 0; /// -/// fn try_my_quic_parser(ctx: TcContext) -> Result { -/// // Assume offsets for Eth, IP, and UDP headers are already calculated. -/// // A real program would need to handle dynamic IP header lengths. -/// let udp_payload_offset = EthHdr::LEN + Ipv4Hdr::LEN + UdpHdr::LEN; +/// // 1. Parse L2 (Ethernet) header. +/// let eth_hdr: EthHdr = ctx.load(offset).map_err(|_| TC_ACT_OK)?; +/// offset += EthHdr::LEN; /// -/// // Load the raw QUIC header. `ctx.load` will handle bounds checks. -/// let mut quic_hdr: QuicHdr = ctx.load(udp_payload_offset).map_err(|_| ())?; +/// // 2. Parse L3 (IP) header. This logic must handle both IPv4 and IPv6. +/// let ip_hdr_len = match u16::from_be(eth_hdr.ether_type) { +/// 0x86DD => { +/// let ipv6_hdr: Ipv6Hdr = ctx.load(offset).map_err(|_| TC_ACT_OK)?; +/// if ipv6_hdr.next_hdr != IpProto::Udp { +/// return Err(TC_ACT_OK); // Not UDP, so not QUIC. +/// } +/// Ipv6Hdr::LEN +/// } +/// _ => return Err(TC_ACT_OK), // Not an IP packet. +/// }; +/// offset += ip_hdr_len; /// -/// // For Short Headers, the DCID length is not on the wire. The eBPF -/// // program must know it from context (e.g., from connection tracking). -/// // Here, we'll assume a length of 8. -/// const EXPECTED_DCID_LEN_FOR_SHORT_HDR: u8 = 8; +/// // 3. Parse L4 (UDP) header. +/// offset += UdpHdr::LEN; // Advance past the fixed-size UDP header. /// -/// // Parse the raw header into a safe, logical view. -/// let parsed = quic_hdr.parse(EXPECTED_DCID_LEN_FOR_SHORT_HDR).map_err(|_| ())?; +/// // 4. `offset` now points to the QUIC header. Call the macro. +/// // QUIC_SHORT_DEFAULT_DC_ID_LEN is used as the DCID length for short headers. +/// let quic_result = parse_quic_hdr!(ctx, offset, QUIC_SHORT_DEFAULT_DC_ID_LEN); /// -/// if parsed.is_long_header() { -/// let version = parsed.version().unwrap_or(0); -/// let dc_id = parsed.dc_id(); -/// let sc_id = parsed.sc_id().unwrap_or(&[]); -/// // Do something with the Long Header info... -/// // aya_log_ebpf::info!(&ctx, "QUIC Long: v={} dcid_len={} scid_len={}", -/// // version, dc_id.len(), sc_id.len()); -/// } else { -/// let dc_id = parsed.dc_id(); -/// let spin_bit = parsed.short_spin_bit().unwrap_or(false); -/// // Do something with the Short Header info... -/// // aya_log_ebpf::info!(&ctx, "QUIC Short: dcid_len={} spin={}", -/// // dc_id.len(), spin_bit); +/// // 5. Handle the parsing result. +/// match quic_result { +/// Ok(QuicHdr::Long(hdr)) => { +/// // Successfully parsed a QUIC Long Header. +/// // You can now access fields like `hdr.version()`, `hdr.dc_id`, etc. +/// // unsafe { store_result(map, hdr.version(), hdr.dc_id_len() as u32, hdr.sc_id_len() as u32) }; +/// } +/// Ok(QuicHdr::Short(hdr)) => { +/// // Successfully parsed a QUIC Short Header. +/// // You can now access fields like `hdr.spin_bit()`, `hdr.dc_id`, etc. +/// // unsafe { store_result(map, SHORT_HEADER_MARKER, hdr.dc_id_len() as u32, 0) }; +/// } +/// Err(_) => { +/// // The payload was not a valid QUIC packet, or a parsing error occurred. +/// return Err(TC_ACT_SHOT); +/// } /// } /// -/// Ok(0) // TC_ACT_OK +/// Ok(()) /// } /// ``` -#[repr(C, packed)] -#[derive(Copy, Clone)] -pub struct QuicHdr { - first_byte: u8, - inner: QuicHdrUn, +#[macro_export] +macro_rules! parse_quic_hdr { + ($ctx:expr, $off:ident, $short_dc_id_len:expr) => { + (|| -> Result<$crate::quic::QuicHdr, ()> { + use $crate::quic; + use $crate::{read_var_buf_32, read_var_buf_from_len_byte_16}; + let quic_fixed_hdr: quic::QuicFirstByteHdr = $ctx.load($off).map_err(|_| ())?; + $off += quic::QuicFirstByteHdr::LEN; + match quic_fixed_hdr.is_long_header() { + true => { + let quic_fixed_long_hdr: quic::QuicFixedLongHdr = + $ctx.load($off).map_err(|_| ())?; + $off += quic::QuicFixedLongHdr::LEN; + let mut quic_long_hdr = + quic::QuicLongHdr::new(quic_fixed_hdr, quic_fixed_long_hdr); + read_var_buf_32!( + $ctx, + $off, + quic_long_hdr.dc_id, + quic_long_hdr.fixed_hdr.dc_id_len as usize, + quic::QUIC_MAX_CID_LEN + ) + .map_err(|_| ())?; + quic_long_hdr.sc_id_len = $ctx.load($off).map_err(|_| ())?; + $off += 1; + read_var_buf_32!( + $ctx, + $off, + quic_long_hdr.sc_id, + quic_long_hdr.sc_id_len as usize, + quic::QUIC_MAX_CID_LEN + ) + .map_err(|_| ())?; + if quic_fixed_hdr.long_packet_type() == 0 { + let token_len_byte: u8 = $ctx.load($off).map_err(|_| ())?; + $off += 1; + read_var_buf_from_len_byte_16!( + $ctx, + $off, + quic_long_hdr.token_len, + token_len_byte, + quic::QUIC_MAX_TOKEN_LEN + ) + .map_err(|_| ())?; + let token_len_val = quic_long_hdr.token_len().map_err(|_| ())?; + $off += token_len_val; + } + if quic_fixed_hdr.long_packet_type() < 3 { + let len_byte: u8 = $ctx.load($off).map_err(|_| ())?; + $off += 1; + read_var_buf_from_len_byte_16!( + $ctx, + $off, + quic_long_hdr.length, + len_byte, + quic::QUIC_MAX_LENGTH + ) + .map_err(|_| ())?; + } + read_var_buf_32!( + $ctx, + $off, + quic_long_hdr.pn, + quic_long_hdr.first_byte.packet_number_length_long(), + 4 + ) + .map_err(|_| ())?; + Ok(quic::QuicHdr::Long(quic_long_hdr)) + } + false => { + let mut quic_short_hdr = + quic::QuicShortHdr::new($short_dc_id_len, quic_fixed_hdr); + read_var_buf_32!( + $ctx, + $off, + quic_short_hdr.dc_id, + quic_short_hdr.dc_id_len as usize, + quic::QUIC_MAX_CID_LEN + ) + .map_err(|_| ())?; + read_var_buf_32!( + $ctx, + $off, + quic_short_hdr.pn, + quic_short_hdr.first_byte.short_packet_number_length(), + 4 + ) + .map_err(|_| ())?; + Ok(quic::QuicHdr::Short(quic_short_hdr)) + } + } + })() + }; } -/// A safe, parsed view of a QUIC header. +/// Represents a QUIC header, which can be either a Long Header or a Short Header. +/// +/// This enum is the main entry point for working with QUIC headers. It is designed to +/// be used in eBPF programs where packet data is parsed sequentially. /// -/// This struct is created by [`QuicHdr::parse()`] and provides methods to -/// safely access and modify the fields of the underlying header. -pub struct ParsedQuicHdr<'a> { - hdr: &'a mut QuicHdr, - header_type: QuicHeaderType, +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[derive(Debug, PartialEq)] +pub enum QuicHdr { + /// A QUIC Long Header, used for connection establishment packets like Initial, + /// 0-RTT, Handshake, and Retry. + Long(QuicLongHdr), + /// A QUIC Short Header, used for data transfer (1-RTT packets) after the + /// connection is established. + Short(QuicShortHdr), } -impl QuicHdr { - /// The total size of the struct in memory. - pub const LEN: usize = size_of::(); - /// The minimum size of a valid Long Header on the wire. - pub const MIN_LONG_HDR_LEN_ON_WIRE: usize = QuicHdrLong::MIN_LEN_ON_WIRE; - /// The minimum size of a valid Short Header on the wire. - pub const MIN_SHORT_HDR_LEN_ON_WIRE: usize = QuicHdrShort::MIN_LEN_ON_WIRE; +/// Represents a QUIC Long Header. +/// +/// Long Headers are used for packets sent before a connection is fully established, +/// such as Initial, 0-RTT, Handshake, and Retry packets. They explicitly carry +/// version information and have longer connection IDs. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct QuicLongHdr { + /// The first byte, containing the header form, packet type, and packet number length. + pub first_byte: QuicFirstByteHdr, + /// The fixed part of the long header, containing version and DCID length. + pub fixed_hdr: QuicFixedLongHdr, + /// Destination Connection ID, up to 20 bytes. Its actual length is in `fixed_hdr.dc_id_len`. + pub dc_id: [u8; QUIC_MAX_CID_LEN], + /// Source Connection ID Length. + pub sc_id_len: u8, + /// Source Connection ID, up to 20 bytes. Its actual length is in `sc_id_len`. + pub sc_id: [u8; QUIC_MAX_CID_LEN], + /// Address Validation Token (for Initial packets), variable-length encoded. + pub token_len: [u8; QUIC_MAX_TOKEN_LEN], + /// The length of the rest of the packet (payload and packet number), variable-length encoded. + pub length: [u8; QUIC_MAX_LENGTH], + /// The packet number, 1 to 4 bytes long. The actual length is in `first_byte`. + pub pn: [u8; 4], +} + +impl QuicLongHdr { + /// The memory size of a `QuicLongHdr` struct. + pub const LEN: usize = mem::size_of::(); - /// Creates a new `QuicHdr` with a specified logical type. + /// Creates a new `QuicLongHdr` with default (zeroed) values for variable-length fields. /// /// # Parameters - /// * `ht`: The desired header type (`QuicLong` or `QuicShort`). + /// * `first_byte`: The pre-constructed `QuicFirstByteHdr`. + /// * `fixed_hdr`: The pre-constructed `QuicFixedLongHdr`. /// /// # Returns - /// A new `QuicHdr` initialized with default values for the given type. - #[inline(always)] - pub fn new(ht: QuicHeaderType) -> Self { - match ht { - QuicHeaderType::QuicLong => Self { - first_byte: HEADER_FORM_BIT | FIXED_BIT_MASK, - inner: QuicHdrUn::new(), - }, - QuicHeaderType::QuicShort { dc_id_len } => { - let mut s = QuicHdrShort::new(); - s.dst.len = cmp::min(dc_id_len, QUIC_MAX_CID_LEN as u8); - Self { - first_byte: FIXED_BIT_MASK, - inner: QuicHdrUn { short: s }, - } - } + /// A new `QuicLongHdr` instance. + #[inline] + pub fn new(first_byte: QuicFirstByteHdr, fixed_hdr: QuicFixedLongHdr) -> Self { + Self { + first_byte, + fixed_hdr, + dc_id: [0; QUIC_MAX_CID_LEN], + sc_id_len: 0, + sc_id: [0; QUIC_MAX_CID_LEN], + token_len: [0; QUIC_MAX_TOKEN_LEN], + length: [0; QUIC_MAX_LENGTH], + pn: [0; 4], } } - /// Checks if the header is a Long Header based on the first bit. - #[inline(always)] - pub fn is_long_header(&self) -> bool { - (self.first_byte & HEADER_FORM_BIT) != 0 - } - - /// Parses the raw header data into a safe, logical view. - /// - /// # Parameters - /// * `dcid_len_for_short`: The expected length of the Destination Connection ID - /// if this is a Short Header. This value must be known from the connection's context, - /// as it is not encoded on the wire in Short Headers. It is ignored for Long Headers. + /// Gets the Long Packet Type, which identifies the packet's purpose (e.g., Initial, Handshake). /// /// # Returns - /// `Ok(ParsedQuicHdr)` on success, or a `QuicHdrError` if the header contains invalid lengths. - #[inline(always)] - pub fn parse(&mut self, dcid_len_for_short: u8) -> Result, QuicHdrError> { - let header_type = if self.is_long_header() { - // Safety: We have checked that this is a Long Header, so accessing `inner.long` is valid. - let long = unsafe { &self.inner.long }; - if long.dst.len() > QUIC_MAX_CID_LEN as u8 || long.src.len() > QUIC_MAX_CID_LEN as u8 { - return Err(QuicHdrError::InvalidLength); - } - QuicHeaderType::QuicLong - } else { - if dcid_len_for_short > QUIC_MAX_CID_LEN as u8 { - return Err(QuicHdrError::InvalidLength); - } - QuicHeaderType::QuicShort { - dc_id_len: dcid_len_for_short, - } - }; - Ok(ParsedQuicHdr { - hdr: self, - header_type, - }) + /// The packet type as a `u8` (0 for Initial, 1 for 0-RTT, 2 for Handshake, 3 for Retry). + #[inline] + pub fn packet_type(&self) -> u8 { + self.first_byte.long_packet_type() } -} -impl<'a> ParsedQuicHdr<'a> { - /// Gets the raw first byte of the QUIC header. + /// Gets the QUIC Version. /// /// # Returns - /// The `u8` value of the first byte. - #[inline(always)] - pub fn first_byte(&self) -> u8 { - self.hdr.first_byte + /// The 32-bit QUIC version number. + #[inline] + pub fn version(&self) -> u32 { + self.fixed_hdr.version() } - /// Sets the raw first byte of the QUIC header. + /// Sets the QUIC Version. /// /// # Parameters - /// * `b`: The `u8` value to set as the first byte. - #[inline(always)] - pub fn set_first_byte(&mut self, b: u8) { - self.hdr.first_byte = b; + /// * `version`: The 32-bit QUIC version number to set. + #[inline] + pub fn set_version(&mut self, version: u32) { + self.fixed_hdr.set_version(version) } - /// Checks if the header is a Long Header. - /// - /// This is determined by checking the Header Form bit (the most significant - /// bit of the first byte). + /// Gets the Destination Connection ID Length. /// /// # Returns - /// `true` if it is a Long Header, `false` otherwise. - #[inline(always)] - pub fn is_long_header(&self) -> bool { - self.hdr.is_long_header() + /// The length of the DCID in bytes. + #[inline] + pub fn dc_id_len(&self) -> u8 { + self.fixed_hdr.dc_id_len() } - /// Gets the Fixed Bit (the second most significant bit of the first byte). - /// - /// According to RFC 9000, this bit must be set to 1. Packets where this - /// bit is 0 are not valid QUIC packets. + /// Sets the Destination Connection ID Length. /// - /// # Returns - /// The value of the fixed bit (0 or 1). - #[inline(always)] - pub fn fixed_bit(&self) -> u8 { - (self.hdr.first_byte & FIXED_BIT_MASK) >> 6 + /// # Parameters + /// * `dc_id_len`: The length of the DCID in bytes. + #[inline] + pub fn set_dc_id_len(&mut self, dc_id_len: u8) { + self.fixed_hdr.set_dc_id_len(dc_id_len) } - /// Gets the Packet Type if this is a Long Header. + /// Gets the Destination Connection ID as a fixed-size array. /// /// # Returns - /// `Ok(QuicPacketType)` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn long_packet_type(&self) -> Result { - if !self.is_long_header() { - return Err(QuicHdrError::InvalidHeaderForm); - } - // Safety: A header form has been checked. - let bits = unsafe { self.long_packet_type_bits_unchecked() }; - QuicPacketType::try_from(bits) + /// A `[u8; QUIC_MAX_CID_LEN]` array containing the DCID. Use `dc_id_len()` to get the actual length. + #[inline] + pub fn dc_id(&self) -> [u8; QUIC_MAX_CID_LEN] { + self.dc_id } - /// Gets the Reserved Bits (bits 4-5) if this is a Long Header. + /// Sets the Destination Connection ID. /// - /// # Returns - /// `Ok(u8)` containing the 2 reserved bits if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn reserved_bits_long(&self) -> Result { - if !self.is_long_header() { - Err(QuicHdrError::InvalidHeaderForm) - } else { - // Safety: A header form has been checked. - Ok(unsafe { self.reserved_bits_long_unchecked() }) - } + /// # Parameters + /// * `dc_id`: A `[u8; QUIC_MAX_CID_LEN]` array containing the DCID. + #[inline] + pub fn set_dc_id(&mut self, dc_id: [u8; QUIC_MAX_CID_LEN]) { + self.dc_id = dc_id; } - /// Gets the encoded Packet Number Length (bits 6-7) if this is a Long Header. - /// This value is `actual_length_in_bytes - 1`. + /// Gets the Source Connection ID Length. /// /// # Returns - /// `Ok(u8)` containing the encoded length (0-3) if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn pn_length_bits_long(&self) -> Result { - if !self.is_long_header() { - Err(QuicHdrError::InvalidHeaderForm) - } else { - // Safety: A header form has been checked. - Ok(unsafe { self.pn_length_bits_long_unchecked() }) - } + /// The length of the SCID in bytes. + #[inline] + pub fn sc_id_len(&self) -> u8 { + self.sc_id_len } - /// Gets the actual Packet Number Length in bytes (1-4) if this is a Long Header. + /// Sets the Source Connection ID Length. /// - /// # Returns - /// `Ok(usize)` containing the length if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn packet_number_length_long(&self) -> Result { - self.pn_length_bits_long().map(|b| (b + 1) as usize) + /// # Parameters + /// * `sc_id_len`: The length of the SCID in bytes. + #[inline] + pub fn set_sc_id_len(&mut self, sc_id_len: u8) { + self.sc_id_len = sc_id_len; } - /// Gets the Spin Bit (bit 2) if this is a Short Header. + /// Gets the Source Connection ID as a fixed-size array. /// /// # Returns - /// `Ok(bool)` with the spin bit value if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. - #[inline(always)] - pub fn short_spin_bit(&self) -> Result { - if self.is_long_header() { - Err(QuicHdrError::InvalidHeaderForm) - } else { - // Safety: A header form has been checked. - Ok(unsafe { self.short_spin_bit_unchecked() }) - } + /// A `[u8; QUIC_MAX_CID_LEN]` array containing the SCID. Use `sc_id_len()` to get the actual length. + #[inline] + pub fn sc_id(&self) -> [u8; QUIC_MAX_CID_LEN] { + self.sc_id } - /// Gets the Reserved Bits (bits 3-4) if this is a Short Header. + /// Sets the Source Connection ID. /// - /// # Returns - /// `Ok(u8)` containing the 2 reserved bits if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. - #[inline(always)] - pub fn short_reserved_bits(&self) -> Result { - if self.is_long_header() { - Err(QuicHdrError::InvalidHeaderForm) - } else { - // Safety: A header form has been checked. - Ok(unsafe { self.short_reserved_bits_unchecked() }) - } + /// # Parameters + /// * `sc_id`: A `[u8; QUIC_MAX_CID_LEN]` array containing the SCID. + #[inline] + pub fn set_sc_id(&mut self, sc_id: [u8; QUIC_MAX_CID_LEN]) { + self.sc_id = sc_id; } - /// Gets the Key Phase Bit (bit 5) if this is a Short Header. + /// Encodes and sets the `Length` field, which indicates the length of the UDP payload. /// - /// # Returns - /// `Ok(bool)` with the key phase value if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. - #[inline(always)] - pub fn short_key_phase(&self) -> Result { - if self.is_long_header() { - Err(QuicHdrError::InvalidHeaderForm) - } else { - // Safety: A header form has been checked. - Ok(unsafe { self.short_key_phase_unchecked() }) - } + /// # Parameters + /// * `length`: The length value to encode. + #[inline] + pub fn set_length(&mut self, length: usize) { + self.length = (length as u64).to_be_bytes(); } - /// Gets the encoded Packet Number Length (bits 6-7) if this is a Short Header. - /// This value is `actual_length_in_bytes - 1`. + /// Encodes and sets the `Token Length` field. This is a variable-length integer. + /// This field is only present on Initial (type 0) packets. /// - /// # Returns - /// `Ok(u8)` containing the encoded length (0-3) if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. - #[inline(always)] - pub fn short_pn_length_bits(&self) -> Result { - if self.is_long_header() { - Err(QuicHdrError::InvalidHeaderForm) - } else { - // Safety: A header form has been checked. - Ok(unsafe { self.short_pn_length_bits_unchecked() }) - } - } - - /// Gets the actual Packet Number Length in bytes (1-4) if this is a Short Header. + /// # Parameters + /// * `token_len`: The length of the address validation token. /// /// # Returns - /// `Ok(usize)` containing the length if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. - #[inline(always)] - pub fn short_packet_number_length(&self) -> Result { - self.short_pn_length_bits().map(|b| (b + 1) as usize) + /// `Ok(())` if successful, `Err(QuicError::InvalidQuicType)` if the packet type is not Initial (0). + #[inline] + pub fn set_token_len(&mut self, token_len: usize) -> Result<(), QuicError> { + if self.packet_type() != 0 { + return Err(QuicError::InvalidQuicType); + } + let token_len = token_len as u64; + self.token_len = [0; QUIC_MAX_TOKEN_LEN]; + if token_len < (1 << 6) { + self.token_len[0] = token_len as u8; + } else if token_len < (1 << 14) { + self.token_len[0] = (0b01 << 6) | (token_len >> 8) as u8; + self.token_len[1] = token_len as u8; + } else if token_len < (1 << 30) { + self.token_len[0] = (0b10 << 6) | (token_len >> 24) as u8; + self.token_len[1] = (token_len >> 16) as u8; + self.token_len[2] = (token_len >> 8) as u8; + self.token_len[3] = token_len as u8; + } + Ok(()) } - /// Gets the QUIC Version if this is a Long Header. + /// Reads and decodes the variable-length `Token Length` field. /// /// # Returns - /// `Ok(u32)` containing the version if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn version(&self) -> Result { - if !self.is_long_header() { - Err(QuicHdrError::InvalidHeaderForm) - } else { - // Safety: A header form has been checked. - Ok(unsafe { self.hdr.inner.long.version() }) + /// `Ok(usize)` with the decoded token length if successful, `Err(QuicError::InvalidQuicType)` if the + /// packet type is not Initial (0). + #[inline] + pub fn token_len(&self) -> Result { + if self.packet_type() != 0 { + return Err(QuicError::InvalidQuicType); } + let first_byte = self.token_len[0]; + let len = 1 << (first_byte >> 6); + let mut val = (first_byte & 0x3F) as u64; + for i in 1..len { + val = (val << 8) + self.token_len[i as usize] as u64; + } + Ok(val as usize) } - /// Gets the on-the-wire length of the Destination Connection ID if this is a Long Header. + /// Reads and decodes the variable-length `Length` field. /// /// # Returns - /// `Ok(u8)` with the length if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn dc_id_len_on_wire(&self) -> Result { - if !self.is_long_header() { - Err(QuicHdrError::InvalidHeaderForm) - } else { - // Safety: A header form has been checked. - Ok(unsafe { self.hdr.inner.long.dst.len() }) + /// `Ok(usize)` with the decoded length, or `Err(QuicError::InvalidQuicType)` if the packet type is Retry (3), + /// which does not have a Length field. + #[inline] + pub fn length(&self) -> Result { + if self.packet_type() >= 3 { + return Err(QuicError::InvalidQuicType); } + Ok(u64::from_be_bytes(self.length) as usize) } - /// Gets the on-the-wire length of the Source Connection ID if this is a Long Header. + /// Gets the Packet Number from the header. /// /// # Returns - /// `Ok(u8)` with the length if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn sc_id_len_on_wire(&self) -> Result { - if !self.is_long_header() { - Err(QuicHdrError::InvalidHeaderForm) - } else { - // Safety: A header form has been checked. - Ok(unsafe { self.hdr.inner.long.src.len() }) + /// `Ok(u32)` with the packet number, or `Err(QuicError::InvalidQuicType)` if the packet type is Retry (3), + /// which does not have a Packet Number field. + #[inline] + pub fn pn(&self) -> Result { + if self.packet_type() == 3 { + return Err(QuicError::InvalidQuicType); } + Ok(u32::from_be_bytes(self.pn)) } - - /// Gets the effective length of the Destination Connection ID. + /// Sets the Packet Number in the header. /// - /// For Long Headers, this length is read directly from the header data. For Short - /// Headers, this is the contextual length that was provided when [`QuicHdr::parse()`] - /// was called. + /// # Parameters + /// * `pn`: The 32-bit packet number. Note that only a portion of this may be encoded in the packet, + /// depending on the Packet Number Length. /// /// # Returns - /// The length of the Destination Connection ID in bytes. - #[inline(always)] - pub fn dc_id_len(&self) -> u8 { - match self.header_type { - // Safety: A header form is known. - QuicHeaderType::QuicLong => unsafe { self.hdr.inner.long.dst.len() }, - QuicHeaderType::QuicShort { dc_id_len } => dc_id_len, + /// `Ok(())` on success, or `Err(QuicError::InvalidQuicType)` if the packet type is Retry (3). + #[inline] + pub fn set_pn(&mut self, pn: u32) -> Result<(), QuicError> { + if self.packet_type() == 3 { + return Err(QuicError::InvalidQuicType); } + self.pn = pn.to_be_bytes(); + Ok(()) } +} + +/// Represents a QUIC Short Header. +/// +/// Short Headers are used for 1-RTT packets after the connection handshake is complete. +/// They are more compact than Long Headers and do not include version information. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct QuicShortHdr { + /// Destination Connection ID Length. This is not encoded in the header itself + /// and must be known from the connection context (e.g., from a map). + pub dc_id_len: u8, + /// The first byte, containing the header form, spin bit, key phase, and packet number length. + pub first_byte: QuicFirstByteHdr, + /// Destination Connection ID. The actual length is stored in `dc_id_len`. + pub dc_id: [u8; QUIC_MAX_CID_LEN], + /// The packet number, 1 to 4 bytes long. The actual length is in `first_byte`. + pub pn: [u8; 4], +} + +impl QuicShortHdr { + /// The memory size of a `QuicShortHdr` struct. + pub const LEN: usize = mem::size_of::(); - /// Gets a slice containing the bytes of the Destination Connection ID. + /// Creates a new `QuicShortHdr` with default values. /// - /// The length of the slice is determined by `dc_id_len()`. + /// # Parameters + /// * `dc_id_len`: The length of the Destination Connection ID. This must be known from context. + /// * `first_byte`: The pre-constructed `QuicFirstByteHdr` for a short header. /// /// # Returns - /// A byte slice (`&[u8]`) representing the Destination Connection ID. - #[inline(always)] - pub fn dc_id(&self) -> &[u8] { - let n = self.dc_id_len() as usize; - let raw = match self.header_type { - // Safety: We are in a `ParsedQuicHdr`, so we know which union variant is active. - QuicHeaderType::QuicLong => unsafe { &self.hdr.inner.long.dst.bytes }, - QuicHeaderType::QuicShort { .. } => unsafe { &self.hdr.inner.short.dst.bytes }, - }; - // Safety: `dc_id_len` is bounded by `QUIC_MAX_CID_LEN` during parsing. - unsafe { raw.get_unchecked(..n) } + /// A new `QuicShortHdr` instance. + #[inline] + pub fn new(dc_id_len: u8, first_byte: QuicFirstByteHdr) -> Self { + Self { + dc_id_len, + first_byte, + dc_id: [0; QUIC_MAX_CID_LEN], + pn: [0; 4], + } } - /// Gets a slice containing the bytes of the Destination Connection ID. + /// Gets the Destination Connection ID Length. This value is not read from the packet + /// but is stored from the connection's context. /// /// # Returns - /// A byte slice (`[u8; QUIC_MAX_CID_LEN]`) representing the Destination Connection ID. - #[inline(always)] - pub fn dc_id_raw(&self) -> [u8; QUIC_MAX_CID_LEN] { - match self.header_type { - // Safety: We are in a `ParsedQuicHdr`, so we know which union variant is active. - QuicHeaderType::QuicLong => unsafe { self.hdr.inner.long.dst.bytes }, - QuicHeaderType::QuicShort { .. } => unsafe { self.hdr.inner.short.dst.bytes }, - } + /// The length of the DCID in bytes. + #[inline] + pub fn dc_id_len(&self) -> u8 { + self.dc_id_len } - /// Gets the effective length of the Source Connection ID bytes if this is a Long Header. + /// Sets the Destination Connection ID Length. /// - /// # Returns - /// `Ok(u8)` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn sc_id_len(&self) -> Result { - if !self.is_long_header() { - Err(QuicHdrError::InvalidHeaderForm) - } else { - // Safety: A header form has been checked. - Ok(unsafe { self.hdr.inner.long.src.as_slice().len() as u8 }) - } + /// # Parameters + /// * `dc_id_len`: The length of the DCID in bytes. + #[inline] + pub fn set_dc_id_len(&mut self, dc_id_len: u8) { + self.dc_id_len = dc_id_len; } - /// Gets a slice of the Source Connection ID bytes if this is a Long Header. + /// Gets the Destination Connection ID as a fixed-size array. /// /// # Returns - /// `Ok(&[u8])` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn sc_id(&self) -> Result<&[u8], QuicHdrError> { - if !self.is_long_header() { - Err(QuicHdrError::InvalidHeaderForm) - } else { - // Safety: A header form has been checked. - Ok(unsafe { self.hdr.inner.long.src.as_slice() }) - } + /// A `[u8; QUIC_MAX_CID_LEN]` array containing the DCID. Use `dc_id_len()` to get the actual length. + #[inline] + pub fn dc_id(&self) -> [u8; QUIC_MAX_CID_LEN] { + self.dc_id } - /// Gets a slice of the Source Connection ID bytes with padding if this is a Long Header. + /// Sets the Destination Connection ID. /// - /// # Returns - /// `Ok([u8; QUIC_MAX_CID_LEN])` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn sc_id_raw(&self) -> Result<[u8; QUIC_MAX_CID_LEN], QuicHdrError> { - if !self.is_long_header() { - Err(QuicHdrError::InvalidHeaderForm) - } else { - // Safety: A header form has been checked. - Ok(unsafe { self.hdr.inner.long.src.bytes }) - } + /// # Parameters + /// * `dc_id`: A `[u8; QUIC_MAX_CID_LEN]` array containing the DCID. + #[inline] + pub fn set_dc_id(&mut self, dc_id: [u8; QUIC_MAX_CID_LEN]) { + self.dc_id = dc_id; } - /// Returns the logical header type determined during parsing. + /// Gets the Packet Number from the header. /// /// # Returns - /// The [`QuicHeaderType`] enum variant (`QuicLong` or `QuicShort`) for this header. - #[inline(always)] - pub fn header_type(&self) -> QuicHeaderType { - self.header_type + /// The 32-bit packet number. + #[inline] + pub fn pn(&self) -> u32 { + u32::from_be_bytes(self.pn) } - /// Sets the Fixed Bit (bit 1 of `first_byte`). + /// Sets the Packet Number in the header. /// /// # Parameters - /// * `v`: The bit value (0 or 1). Masked to 1 bit. - #[inline(always)] - pub fn set_fixed_bit(&mut self, v: u8) { - // Safety: This operation is valid for both header forms. - unsafe { self.set_fixed_bit_unchecked(v) } + /// * `pn`: The 32-bit packet number to set. + #[inline] + pub fn set_pn(&mut self, pn: u32) { + self.pn = pn.to_be_bytes(); } - /// Sets the Packet Type if this is a Long Header. - /// - /// # Parameters - /// * `pt`: The `QuicPacketType` to set. + /// Gets the Spin Bit, used for passive RTT measurement. /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn set_long_packet_type(&mut self, pt: QuicPacketType) -> Result<(), QuicHdrError> { - if !self.is_long_header() { - return Err(QuicHdrError::InvalidHeaderForm); - } - // Safety: A header form has been checked. - unsafe { - self.set_long_packet_type_bits_unchecked(pt as u8); - } - Ok(()) + /// `true` if the spin bit is 1, `false` otherwise. + #[inline] + pub fn spin_bit(&self) -> bool { + self.first_byte.short_spin_bit() } - /// Sets the Reserved Bits (bits 4-5) if this is a Long Header. + /// Sets the Spin Bit. /// /// # Parameters - /// * `v`: The 2-bit value to set. Masked to 2 bits. + /// * `spin_bit`: The new state of the spin bit (`true` for 1, `false` for 0). + #[inline] + pub fn set_spin_bit(&mut self, spin_bit: bool) { + self.first_byte.set_short_spin_bit(spin_bit); + } + + /// Gets the Key Phase bit, which indicates a key update. /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn set_reserved_bits_long(&mut self, v: u8) -> Result<(), QuicHdrError> { - if !self.is_long_header() { - return Err(QuicHdrError::InvalidHeaderForm); - } - // Safety: A header form has been checked. - unsafe { self.set_reserved_bits_long_unchecked(v) }; - Ok(()) + /// `true` if the key phase bit is 1, `false` otherwise. + #[inline] + pub fn key_phase(&self) -> bool { + self.first_byte.short_key_phase() } - /// Sets the encoded Packet Number Length (bits 6-7) if this is a Long Header. + /// Sets the Key Phase bit. /// /// # Parameters - /// * `v`: Encoded Packet Number Length (`actual_length_in_bytes - 1`, range 0-3). Masked to 2 bits. + /// * `key_phase`: The new state of the key phase bit (`true` for 1, `false` for 0). + #[inline] + pub fn set_key_phase(&mut self, key_phase: bool) { + self.first_byte.set_short_key_phase(key_phase); + } + + /// Gets the actual length of the Packet Number in bytes (1-4). /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn set_pn_length_bits_long(&mut self, v: u8) -> Result<(), QuicHdrError> { - if !self.is_long_header() { - return Err(QuicHdrError::InvalidHeaderForm); - } - // Safety: A header form has been checked. - unsafe { self.set_pn_length_bits_long_unchecked(v) }; - Ok(()) + /// The decoded length of the packet number field in bytes. + #[inline] + pub fn packet_number_length(&self) -> usize { + self.first_byte.short_packet_number_length() } - /// Sets the actual Packet Number Length in bytes (1-4) if this is a Long Header. + /// Sets the actual length of the Packet Number field in bytes (1-4). /// /// # Parameters - /// * `n`: The actual length in bytes (1-4). - /// - /// # Returns - /// `Ok(())` if successful, `Err` if `n` is out of range or this is not a Long Header. - #[inline(always)] - pub fn set_packet_number_length_long(&mut self, n: usize) -> Result<(), QuicHdrError> { - if !(1..=4).contains(&n) { - return Err(QuicHdrError::InvalidLength); - } - if !self.is_long_header() { - return Err(QuicHdrError::InvalidHeaderForm); - } - // Safety: A length and header form have been checked. - unsafe { self.set_packet_number_length_long_unchecked(n) }; - Ok(()) + /// * `len`: The desired length of the packet number in bytes (1-4). Values outside this range are clamped. + #[inline] + pub fn set_packet_number_length(&mut self, len: usize) { + self.first_byte.set_short_packet_number_length(len); } +} + +/// Represents the fixed portion of a QUIC Long Header that follows the first byte. +/// It contains the version and DCID length. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct QuicFixedLongHdr { + /// The QUIC version number. + pub version: [u8; 4], + /// The length of the Destination Connection ID. + pub dc_id_len: u8, +} + +impl QuicFixedLongHdr { + /// The memory size of a `QuicFixedLongHdr` struct. + pub const LEN: usize = mem::size_of::(); - /// Sets the Spin Bit (bit 2) if this is a Short Header. + /// Creates a new `QuicFixedLongHdr`. /// /// # Parameters - /// * `b`: The boolean value for the spin bit. + /// * `version`: The 32-bit QUIC version number. + /// * `dc_id_len`: The length of the Destination Connection ID in bytes. /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. - #[inline(always)] - pub fn set_short_spin_bit(&mut self, b: bool) -> Result<(), QuicHdrError> { - if self.is_long_header() { - return Err(QuicHdrError::InvalidHeaderForm); + /// A new `QuicFixedLongHdr` instance. + #[inline] + pub fn new(version: u32, dc_id_len: u8) -> Self { + Self { + version: version.to_be_bytes(), + dc_id_len, } - // Safety: A header form has been checked. - unsafe { self.set_short_spin_bit_unchecked(b) }; - Ok(()) } - /// Sets the Reserved Bits (bits 3-4) if this is a Short Header. - /// - /// # Parameters - /// * `v`: The 2-bit value to set. Masked to 2 bits. + /// Gets the QUIC version from the header. /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. - #[inline(always)] - pub fn set_short_reserved_bits(&mut self, v: u8) -> Result<(), QuicHdrError> { - if self.is_long_header() { - return Err(QuicHdrError::InvalidHeaderForm); - } - // Safety: A header form has been checked. - unsafe { self.set_short_reserved_bits_unchecked(v) }; - Ok(()) + /// The 32-bit QUIC version number. + #[inline] + pub fn version(&self) -> u32 { + u32::from_be_bytes(self.version) } - /// Sets the Key Phase Bit (bit 5) if this is a Short Header. + /// Sets the QUIC version in the header. /// /// # Parameters - /// * `kp`: The boolean value for the key phase. + /// * `version`: The 32-bit QUIC version number to set. + #[inline] + pub fn set_version(&mut self, version: u32) { + self.version = version.to_be_bytes(); + } + + /// Gets the Destination Connection ID length from the header. /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. - #[inline(always)] - pub fn set_short_key_phase(&mut self, kp: bool) -> Result<(), QuicHdrError> { - if self.is_long_header() { - return Err(QuicHdrError::InvalidHeaderForm); - } - // Safety: A header form has been checked. - unsafe { self.set_short_key_phase_unchecked(kp) }; - Ok(()) + /// The length of the DCID in bytes. + #[inline] + pub fn dc_id_len(&self) -> u8 { + self.dc_id_len } - /// Sets the encoded Packet Number Length (bits 6-7) if this is a Short Header. + /// Sets the Destination Connection ID length in the header. /// /// # Parameters - /// * `v`: Encoded Packet Number Length (`actual_length_in_bytes - 1`, range 0-3). Masked to 2 bits. - /// - /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if a Long Header. - #[inline(always)] - pub fn set_short_pn_length_bits(&mut self, v: u8) -> Result<(), QuicHdrError> { - if self.is_long_header() { - return Err(QuicHdrError::InvalidHeaderForm); - } - // Safety: A header form has been checked. - unsafe { self.set_short_pn_length_bits_unchecked(v) }; - Ok(()) + /// * `len`: The length of the DCID in bytes. + #[inline] + pub fn set_dc_id_len(&mut self, len: u8) { + self.dc_id_len = len; } +} - /// Sets the actual Packet Number Length in bytes (1-4) if this is a Short Header. +/// Represents the first byte of any QUIC packet. +/// +/// This byte contains critical information for parsing the rest of the header, +/// such as the header form (Long or Short) and various type-specific flags. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct QuicFirstByteHdr { + /// The raw value of the first byte. + pub first_byte: u8, +} + +impl QuicFirstByteHdr { + /// The memory size of a `QuicFirstByteHdr` struct. + pub const LEN: usize = mem::size_of::(); + + /// Creates a new `QuicFirstByteHdr` for a Long Header. /// /// # Parameters - /// * `n`: The actual length in bytes (1-4). + /// * `packet_type`: The Long Packet Type (0-3). + /// * `reserved_bits`: The reserved bits (must be 0 for valid packets). + /// * `pn_len_bits`: The encoded packet number length (`actual_length - 1`), value in range 0-3. /// /// # Returns - /// `Ok(())` if successful, `Err` if `n` is out of range or this is a Long Header. - #[inline(always)] - pub fn set_short_packet_number_length(&mut self, n: usize) -> Result<(), QuicHdrError> { - if !(1..=4).contains(&n) { - return Err(QuicHdrError::InvalidLength); - } - if self.is_long_header() { - return Err(QuicHdrError::InvalidHeaderForm); - } - // Safety: A length and header form have been checked. - unsafe { self.set_short_packet_number_length_unchecked(n) }; - Ok(()) + /// A new `QuicFirstByteHdr` configured for a Long Header. + #[inline] + pub fn new(packet_type: u8, reserved_bits: u8, pn_len_bits: u8) -> Self { + let first_byte = HEADER_FORM_BIT + | FIXED_BIT_MASK + | ((packet_type & 0b11) << LONG_PACKET_TYPE_SHIFT) + | ((reserved_bits & 0b11) << RESERVED_BITS_LONG_SHIFT) + | (pn_len_bits & PN_LENGTH_BITS_MASK); + Self { first_byte } } - /// Sets the QUIC Version if this is a Long Header. + /// Creates the `first_byte` value for a Short Header. /// /// # Parameters - /// * `v`: The `u32` version value. + /// * `spin_bit`: The connection spin bit value (`true` for 1, `false` for 0). + /// * `key_phase`: The key phase value (`true` for 1, `false` for 0). + /// * `pn_len_bits`: The encoded packet number length (`actual_length - 1`), value in range 0-3. /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn set_version(&mut self, v: u32) -> Result<(), QuicHdrError> { - if !self.is_long_header() { - return Err(QuicHdrError::InvalidHeaderForm); - } - // Safety: A header form has been checked. - unsafe { self.set_version_unchecked(v) }; - Ok(()) + /// The `u8` value for the first byte of a Short Header. + #[inline] + pub fn new_short_header_first_byte(spin_bit: bool, key_phase: bool, pn_len_bits: u8) -> u8 { + let spin_val = if spin_bit { 1 } else { 0 }; + let key_phase_val = if key_phase { 1 } else { 0 }; + FIXED_BIT_MASK + | (spin_val << SHORT_SPIN_BIT_SHIFT) + | (key_phase_val << SHORT_KEY_PHASE_BIT_SHIFT) + | (pn_len_bits & PN_LENGTH_BITS_MASK) } - /// Sets the effective length of the Destination Connection ID. + /// Gets the raw `first_byte` value. /// - /// # Parameters - /// * `l`: The new length. Will be truncated to `QUIC_MAX_CID_LEN`. - #[inline(always)] - pub fn set_dc_id_effective_len(&mut self, l: u8) { - // Safety: This operation is valid for both header forms and handles the logic internally. - unsafe { self.set_dc_id_effective_len_unchecked(l) }; + /// # Returns + /// The `u8` value of the header byte. + #[inline] + pub fn first_byte(&self) -> u8 { + self.first_byte } - /// Sets the Destination Connection ID from a slice. - /// This also updates the effective length. + /// Sets the raw `first_byte` value. /// /// # Parameters - /// * `d`: A slice containing the new DCID. - #[inline(always)] - pub fn set_dc_id(&mut self, d: &[u8]) { - // Safety: This operation is valid for both header forms and handles the logic internally. - unsafe { self.set_dc_id_unchecked(d) }; + /// * `first_byte`: The `u8` value to set as the header byte. + #[inline] + pub fn set_first_byte(&mut self, first_byte: u8) { + self.first_byte = first_byte; } - /// Sets the on-the-wire length of the Source Connection ID if this is a Long Header. - /// - /// # Parameters - /// * `l`: The new length. Will be truncated to `QUIC_MAX_CID_LEN`. + /// Checks if the Header Form bit indicates a Long Header. /// /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn set_sc_id_len_on_wire(&mut self, l: u8) -> Result<(), QuicHdrError> { - if !self.is_long_header() { - return Err(QuicHdrError::InvalidHeaderForm); - } - // Safety: A header form has been checked. - unsafe { self.set_sc_id_len_on_wire_unchecked(l) }; - Ok(()) + /// `true` if the high bit is 1 (Long Header), `false` otherwise (Short Header). + #[inline] + pub fn is_long_header(&self) -> bool { + (self.first_byte & HEADER_FORM_BIT) == HEADER_FORM_BIT } - /// Sets the Source Connection ID from a slice if this is a Long Header. - /// This also updates the on-the-wire length. + /// Sets the Header Form bit. /// /// # Parameters - /// * `d`: A slice containing the new SCID. - /// - /// # Returns - /// `Ok(())` if successful, `Err(QuicHdrError::InvalidHeaderForm)` if not a Long Header. - #[inline(always)] - pub fn set_sc_id(&mut self, d: &[u8]) -> Result<(), QuicHdrError> { - if !self.is_long_header() { - return Err(QuicHdrError::InvalidHeaderForm); + /// * `is_long`: `true` to set the Long Header bit, `false` to clear it for a Short Header. + #[inline] + pub fn set_header_form(&mut self, is_long: bool) { + if is_long { + self.first_byte |= HEADER_FORM_BIT; + } else { + self.first_byte &= !HEADER_FORM_BIT; } - // Safety: A header form has been checked. - unsafe { self.set_sc_id_unchecked(d) }; - Ok(()) } -} -// Unchecked, unsafe internal methods. -impl<'a> ParsedQuicHdr<'a> { - /// Gets the Long Header Packet Type bits from `first_byte` without checking a header form. + /// Gets the Fixed Bit. This bit must be 1 for all valid QUIC packets. /// /// # Returns - /// The Packet Type bits value (0-3). - /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is true. - #[inline(always)] - unsafe fn long_packet_type_bits_unchecked(&self) -> u8 { - (self.hdr.first_byte & LONG_PACKET_TYPE_MASK) >> LONG_PACKET_TYPE_SHIFT + /// The value of the fixed bit (bit 6). + #[inline] + pub fn fixed_bit(&self) -> u8 { + (self.first_byte & FIXED_BIT_MASK) >> 6 } - /// Sets the Long Header Packet Type bits in `first_byte` without checking a header form. + /// Sets the Fixed Bit. /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is true. - #[inline(always)] - unsafe fn set_long_packet_type_bits_unchecked(&mut self, b: u8) { - self.hdr.first_byte = - (self.hdr.first_byte & !LONG_PACKET_TYPE_MASK) | ((b & 0x03) << LONG_PACKET_TYPE_SHIFT); + /// # Parameters + /// * `val`: The value for the fixed bit (0 or 1). + #[inline] + pub fn set_fixed_bit(&mut self, val: u8) { + self.first_byte = (self.first_byte & !FIXED_BIT_MASK) | ((val & 0x01) << 6); } - /// Gets the Reserved Bits (Long Header) from `first_byte` without checking a header form. + /// Gets the Long Packet Type (bits 5-4) if this is a Long Header. /// /// # Returns - /// The Reserved Bits value (0-3). - /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is true. - #[inline(always)] - unsafe fn reserved_bits_long_unchecked(&self) -> u8 { - (self.hdr.first_byte & RESERVED_BITS_LONG_MASK) >> RESERVED_BITS_LONG_SHIFT + /// The decoded packet type value (0-3). + #[inline] + pub fn long_packet_type(&self) -> u8 { + (self.first_byte & LONG_PACKET_TYPE_MASK) >> LONG_PACKET_TYPE_SHIFT } - /// Sets the Reserved Bits (Long Header) in `first_byte` without checking a header form. + /// Sets the Long Packet Type (bits 5-4). This is only meaningful for Long Headers. /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is true. - #[inline(always)] - unsafe fn set_reserved_bits_long_unchecked(&mut self, v: u8) { - self.hdr.first_byte = (self.hdr.first_byte & !RESERVED_BITS_LONG_MASK) - | ((v & 0x03) << RESERVED_BITS_LONG_SHIFT); + /// # Parameters + /// * `lptype`: The packet type value (0-3). The value is masked to 2 bits. + #[inline] + pub fn set_long_packet_type(&mut self, lptype: u8) { + self.first_byte = (self.first_byte & !LONG_PACKET_TYPE_MASK) + | ((lptype << LONG_PACKET_TYPE_SHIFT) & LONG_PACKET_TYPE_MASK); } - /// Gets the encoded Packet Number Length bits (Long Header) from `first_byte` without checking a header form. + /// Gets the Reserved Bits (bits 3-2) if this is a Long Header. These must be 0. /// /// # Returns - /// The encoded PN length bits (0-3). - /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is true. - #[inline(always)] - unsafe fn pn_length_bits_long_unchecked(&self) -> u8 { - self.hdr.first_byte & PN_LENGTH_BITS_MASK - } - - /// Sets the encoded Packet Number Length bits (Long Header) in `first_byte` without checking a header form. - /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is true. - #[inline(always)] - unsafe fn set_pn_length_bits_long_unchecked(&mut self, v: u8) { - self.hdr.first_byte = - (self.hdr.first_byte & !PN_LENGTH_BITS_MASK) | (v & PN_LENGTH_BITS_MASK); + /// The decoded value of the reserved bits. + #[inline] + pub fn reserved_bits_long(&self) -> u8 { + (self.first_byte & RESERVED_BITS_LONG_MASK) >> RESERVED_BITS_LONG_SHIFT } - /// Sets the Packet Number Length (Long Header) in `first_byte` without checking header form or length validity. + /// Sets the Reserved Bits (bits 3-2). This is only meaningful for Long Headers. /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is true and `1 <= n <= 4`. - #[inline(always)] - unsafe fn set_packet_number_length_long_unchecked(&mut self, n: usize) { - self.set_pn_length_bits_long_unchecked((n - 1) as u8); + /// # Parameters + /// * `val`: The value for the reserved bits. Per RFC 9000, this should be 0. + #[inline] + pub fn set_reserved_bits_long(&mut self, val: u8) { + self.first_byte = (self.first_byte & !RESERVED_BITS_LONG_MASK) + | ((val << RESERVED_BITS_LONG_SHIFT) & RESERVED_BITS_LONG_MASK); } - /// Gets the Spin Bit (Short Header) from `first_byte` without checking a header form. + /// Gets the encoded Packet Number Length (bits 1-0) if this is a Long Header. /// /// # Returns - /// The spin bit value. - /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is false. - #[inline(always)] - unsafe fn short_spin_bit_unchecked(&self) -> bool { - (self.hdr.first_byte & SHORT_SPIN_BIT_MASK) != 0 + /// The encoded length (`actual_length - 1`), a value from 0 to 3. + #[inline] + pub fn pn_length_bits_long(&self) -> u8 { + self.first_byte & PN_LENGTH_BITS_MASK } - /// Sets the Spin Bit (Short Header) in `first_byte` without checking a header form. + /// Sets the encoded Packet Number Length (bits 1-0). This is only meaningful for Long Headers. /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is false. - #[inline(always)] - unsafe fn set_short_spin_bit_unchecked(&mut self, b: bool) { - if b { - self.hdr.first_byte |= SHORT_SPIN_BIT_MASK; - } else { - self.hdr.first_byte &= !SHORT_SPIN_BIT_MASK; - } + /// # Parameters + /// * `val`: The encoded length (`actual_length - 1`). Masked to 2 bits. + #[inline] + pub fn set_pn_length_bits_long(&mut self, val: u8) { + self.first_byte = (self.first_byte & !PN_LENGTH_BITS_MASK) | (val & PN_LENGTH_BITS_MASK); } - /// Gets the Reserved Bits (Short Header) from `first_byte` without checking a header form. + /// Gets the decoded Packet Number Length in bytes (1-4) if this is a Long Header. /// /// # Returns - /// The reserved bits value (0-3). - /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is false. - #[inline(always)] - unsafe fn short_reserved_bits_unchecked(&self) -> u8 { - (self.hdr.first_byte & SHORT_RESERVED_BITS_MASK) >> SHORT_RESERVED_BITS_SHIFT + /// The actual length of the Packet Number field in bytes. + #[inline] + pub fn packet_number_length_long(&self) -> usize { + (self.pn_length_bits_long() + 1) as usize } - /// Sets the Reserved Bits (Short Header) in `first_byte` without checking a header form. + /// Sets the Packet Number Length from a length in bytes (1-4) for a Long Header. /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is false. - #[inline(always)] - unsafe fn set_short_reserved_bits_unchecked(&mut self, v: u8) { - self.hdr.first_byte = (self.hdr.first_byte & !SHORT_RESERVED_BITS_MASK) - | ((v & 0x03) << SHORT_RESERVED_BITS_SHIFT); + /// # Parameters + /// * `len`: The desired length in bytes (1-4). Values less than 1 are treated as 1, greater than 4 as 4. + #[inline] + pub fn set_packet_number_length_long(&mut self, len: usize) { + let encoded_val = match len { + 1 => 0b00, + 2 => 0b01, + 3 => 0b10, + 4 => 0b11, + _ if len < 1 => 0b00, + _ => 0b11, + }; + self.set_pn_length_bits_long(encoded_val); } - /// Gets the Key Phase bit (Short Header) from `first_byte` without checking a header form. + /// Gets the Spin Bit (bit 5) if this is a Short Header. /// /// # Returns - /// The key phase bit value. - /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is false. - #[inline(always)] - unsafe fn short_key_phase_unchecked(&self) -> bool { - (self.hdr.first_byte & SHORT_KEY_PHASE_BIT_MASK) != 0 + /// `true` if the bit is 1, `false` otherwise. + #[inline] + pub fn short_spin_bit(&self) -> bool { + (self.first_byte & SHORT_SPIN_BIT_MASK) != 0 } - /// Sets the Key Phase bit (Short Header) in `first_byte` without checking a header form. + /// Sets the Spin Bit (bit 5). This is only meaningful for Short Headers. /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is false. - #[inline(always)] - unsafe fn set_short_key_phase_unchecked(&mut self, b: bool) { - if b { - self.hdr.first_byte |= SHORT_KEY_PHASE_BIT_MASK; + /// # Parameters + /// * `spin`: The desired state of the spin bit (`true` for 1, `false` for 0). + #[inline] + pub fn set_short_spin_bit(&mut self, spin: bool) { + if spin { + self.first_byte |= SHORT_SPIN_BIT_MASK; } else { - self.hdr.first_byte &= !SHORT_KEY_PHASE_BIT_MASK; + self.first_byte &= !SHORT_SPIN_BIT_MASK; } } - /// Gets the encoded Packet Number Length bits (Short Header) from `first_byte` without checking a header form. + /// Gets the Reserved Bits (bits 4-3) if this is a Short Header. These must be 0. /// /// # Returns - /// The encoded PN length bits (0-3). - /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is false. - #[inline(always)] - unsafe fn short_pn_length_bits_unchecked(&self) -> u8 { - self.hdr.first_byte & PN_LENGTH_BITS_MASK - } - - /// Sets the encoded Packet Number Length bits (Short Header) in `first_byte` without checking a header form. - /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is false. - #[inline(always)] - unsafe fn set_short_pn_length_bits_unchecked(&mut self, v: u8) { - self.hdr.first_byte = - (self.hdr.first_byte & !PN_LENGTH_BITS_MASK) | (v & PN_LENGTH_BITS_MASK); - } - - /// Sets the Packet Number Length (Short Header) in `first_byte` without checking header form or length validity. - /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is false and `1 <= n <= 4`. - #[inline(always)] - unsafe fn set_short_packet_number_length_unchecked(&mut self, n: usize) { - self.set_short_pn_length_bits_unchecked((n - 1) as u8); + /// The decoded value of the reserved bits. + #[inline] + pub fn short_reserved_bits(&self) -> u8 { + (self.first_byte & SHORT_RESERVED_BITS_MASK) >> SHORT_RESERVED_BITS_SHIFT } - /// Sets the Fixed Bit in `first_byte` without any checks. + /// Sets the Reserved Bits (bits 4-3). This is only meaningful for Short Headers. + /// Per RFC 9000, these bits must be set to 0. This function enforces that. /// - /// # Safety - /// This is always safe from a memory perspective, but the caller must ensure the resulting byte is valid. - #[inline(always)] - unsafe fn set_fixed_bit_unchecked(&mut self, v: u8) { - self.hdr.first_byte = (self.hdr.first_byte & !FIXED_BIT_MASK) | ((v & 1) << 6); + /// # Parameters + /// * `_reserved`: This parameter is ignored; the bits are always cleared. + #[inline] + pub fn set_short_reserved_bits(&mut self, _reserved: u8) { + self.first_byte &= !SHORT_RESERVED_BITS_MASK; } - /// Sets the QUIC version without checking a header form. + /// Gets the Key Phase bit (bit 2) if this is a Short Header. /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is true. - #[inline(always)] - unsafe fn set_version_unchecked(&mut self, v: u32) { - self.hdr.inner.long.set_version(v); + /// # Returns + /// `true` if the bit is 1, `false` otherwise. + #[inline] + pub fn short_key_phase(&self) -> bool { + (self.first_byte & SHORT_KEY_PHASE_BIT_MASK) != 0 } - /// Sets the effective DCID length without any checks. + /// Sets the Key Phase bit (bit 2). This is only meaningful for Short Headers. /// - /// # Safety - /// Caller must ensure the correct `header_type` is handled and that the length is valid. - #[inline(always)] - unsafe fn set_dc_id_effective_len_unchecked(&mut self, l: u8) { - let l = cmp::min(l, QUIC_MAX_CID_LEN as u8); - match &mut self.header_type { - QuicHeaderType::QuicLong => { - self.hdr.inner.long.dst.len = l; - } - QuicHeaderType::QuicShort { dc_id_len } => *dc_id_len = l, + /// # Parameters + /// * `key_phase`: The desired state of the key phase bit (`true` for 1, `false` for 0). + #[inline] + pub fn set_short_key_phase(&mut self, key_phase: bool) { + if key_phase { + self.first_byte |= SHORT_KEY_PHASE_BIT_MASK; + } else { + self.first_byte &= !SHORT_KEY_PHASE_BIT_MASK; } } - /// Sets the DCID without any checks. + /// Gets the encoded Packet Number Length (bits 1-0) if this is a Short Header. /// - /// # Safety - /// Caller must ensure the correct `header_type` is handled. - #[inline(always)] - unsafe fn set_dc_id_unchecked(&mut self, d: &[u8]) { - match &mut self.header_type { - QuicHeaderType::QuicLong => self.hdr.inner.long.dst.set(d), - QuicHeaderType::QuicShort { dc_id_len } => { - self.hdr.inner.short.dst.set(d); - *dc_id_len = self.hdr.inner.short.dst.len(); - } - } + /// # Returns + /// The encoded length (`actual_length - 1`), a value from 0 to 3. + #[inline] + pub fn short_pn_length_bits(&self) -> u8 { + self.first_byte & PN_LENGTH_BITS_MASK } - /// Sets the on-the-wire SCID length without checking a header form. + /// Sets the encoded Packet Number Length (bits 1-0). This is only meaningful for Short Headers. /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is true. - #[inline(always)] - unsafe fn set_sc_id_len_on_wire_unchecked(&mut self, l: u8) { - self.hdr.inner.long.src.len = cmp::min(l, QUIC_MAX_CID_LEN as u8); + /// # Parameters + /// * `val`: The encoded length (`actual_length - 1`). Masked to 2 bits. + #[inline] + pub fn set_short_pn_length_bits(&mut self, val: u8) { + self.first_byte = (self.first_byte & !PN_LENGTH_BITS_MASK) | (val & PN_LENGTH_BITS_MASK); } - /// Sets the SCID without checking a header form. + /// Gets the decoded Packet Number Length in bytes (1-4) if this is a Short Header. /// - /// # Safety - /// Caller must ensure `self.is_long_header()` is true. - #[inline(always)] - unsafe fn set_sc_id_unchecked(&mut self, d: &[u8]) { - self.hdr.inner.long.src.set(d); - } -} - -impl fmt::Debug for QuicHdr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("QuicHdr") - .field("first_byte", &format_args!("{:#04x}", self.first_byte)) - .field("is_long", &self.is_long_header()) - .finish() - } -} -impl<'a> fmt::Debug for ParsedQuicHdr<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut s = f.debug_struct("ParsedQuicHdr"); - s.field("first_byte", &format_args!("{:#04x}", self.first_byte())) - .field( - "form", - &if self.is_long_header() { - "Long" - } else { - "Short" - }, - ) - .field("fixed", &self.fixed_bit()); - match self.header_type { - QuicHeaderType::QuicLong => { - s.field("version", &self.version().ok()) - .field("dc_id", &self.dc_id()) - .field("sc_id", &self.sc_id().ok()) - .field("ptype", &self.long_packet_type().ok()); - } - QuicHeaderType::QuicShort { .. } => { - s.field("dc_id", &self.dc_id()) - .field("spin", &self.short_spin_bit().ok()) - .field("phase", &self.short_key_phase().ok()); - } - }; - s.finish() + /// # Returns + /// The actual length of the Packet Number field in bytes. + #[inline] + pub fn short_packet_number_length(&self) -> usize { + (self.short_pn_length_bits() + 1) as usize } -} -impl TryFrom for QuicPacketType { - type Error = QuicHdrError; - - /// Converts a byte to a `QuicPacketType`. + /// Sets the Packet Number Length from a length in bytes (1-4) for a Short Header. /// - /// # Returns - /// `Ok(QuicPacketType)` on a valid value, `Err(QuicHdrError::InvalidPacketTypeBits)` otherwise. - fn try_from(v: u8) -> Result { - match v { - 0x00 => Ok(QuicPacketType::Initial), - 0x01 => Ok(QuicPacketType::ZeroRTT), - 0x02 => Ok(QuicPacketType::Handshake), - 0x03 => Ok(QuicPacketType::Retry), - _ => Err(QuicHdrError::InvalidPacketTypeBits), - } - } -} -impl From for u8 { - #[inline(always)] - fn from(p: QuicPacketType) -> Self { - p as u8 + /// # Parameters + /// * `len`: The desired length in bytes (1-4). Values less than 1 are treated as 1, greater than 4 as 4. + #[inline] + pub fn set_short_packet_number_length(&mut self, len: usize) { + let encoded_val = match len { + 1 => 0b00, + 2 => 0b01, + 3 => 0b10, + 4 => 0b11, + _ if len < 1 => 0b00, + _ => 0b11, + }; + self.set_short_pn_length_bits(encoded_val); } } -#[cfg(feature = "serde")] -mod serde_header_impl { - use super::*; - extern crate alloc; - use alloc::vec::Vec; - - use serde::{ - de::{self, Deserializer, Visitor}, - ser::{Serialize, Serializer}, - }; - - impl Serialize for QuicHdr { - fn serialize(&self, ser: S) -> Result - where - S: Serializer, - { - let mut v = Vec::with_capacity(1 + 4 + 1 + QUIC_MAX_CID_LEN + 1 + QUIC_MAX_CID_LEN); - v.push(self.first_byte); - if self.is_long_header() { - // Safety: `is_long_header` is true, so `inner.long` is the active variant. - let long = unsafe { &self.inner.long }; - v.extend_from_slice(&long.version); - v.push(long.dst.len()); - v.extend_from_slice(long.dst.as_slice()); - v.push(long.src.len()); - v.extend_from_slice(long.src.as_slice()); - } else { - // Safety: `is_long_header` is false, so `inner.short` is the active variant. - let short = unsafe { &self.inner.short }; - v.extend_from_slice(short.dst.as_slice()); - } - ser.serialize_bytes(&v) - } - } - - struct HdrVisitor; - impl<'de> Visitor<'de> for HdrVisitor { - type Value = QuicHdr; - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("a QUIC header") +/// Tries to convert a u8 into a `QuicFirstByteHdr`, validating required bits. +/// +/// According to RFC 9000, the "Fixed Bit" (0x40) must be 1. +/// The "Reserved Bits" for both Long and Short headers must be 0. +/// This implementation checks these constraints and returns an error if they are not met. +impl TryFrom for QuicFirstByteHdr { + type Error = u8; + + fn try_from(value: u8) -> Result { + // The fixed bit MUST be 1. + if (value & FIXED_BIT_MASK) == 0 { + return Err(value); } - - fn visit_bytes(self, b: &[u8]) -> Result - where - E: de::Error, - { - if b.is_empty() { - return Err(E::custom("empty header")); - } - let first = b[0]; - let mut cur = &b[1..]; - let mut hdr = QuicHdr { - first_byte: 0, - inner: QuicHdrUn::new(), - }; - hdr.first_byte = first; - - #[inline(always)] - fn take<'a, E>(buf: &mut &'a [u8], n: usize, msg: &'static str) -> Result<&'a [u8], E> - where - E: de::Error, - { - if buf.len() < n { - Err(E::custom(msg)) - } else { - let (h, t) = buf.split_at(n); - *buf = t; - Ok(h) - } - } - - if (first & HEADER_FORM_BIT) == 0 { - // Safety: This is a short header, so we can write to `inner.short`. - unsafe { hdr.inner.short.dst.set(cur) }; - return Ok(hdr); - } - - if b.len() < QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE { - return Err(E::custom("truncated long header")); + // Check reserved bits based on header form. + if (value & HEADER_FORM_BIT) != 0 { + // Long Header: Reserved bits (3-2) MUST be 0. + if (value & RESERVED_BITS_LONG_MASK) != 0 { + return Err(value); } - // Safety: This is a long header, so we can write to `inner.long`. - let long = unsafe { &mut hdr.inner.long }; - long.version.copy_from_slice(take(&mut cur, 4, "version")?); - - let dcl = take(&mut cur, 1, "dcil")?[0] as usize; - if dcl > QUIC_MAX_CID_LEN || cur.len() < dcl { - return Err(E::custom("dcid len")); - } - long.dst.set(take(&mut cur, dcl, "dcid")?); - - let scl = take(&mut cur, 1, "scil")?[0] as usize; - if scl > QUIC_MAX_CID_LEN || cur.len() < scl { - return Err(E::custom("scid len")); + } else { + // Short Header: Reserved bits (4-3) MUST be 0. + if (value & SHORT_RESERVED_BITS_MASK) != 0 { + return Err(value); } - long.src.set(take(&mut cur, scl, "scid")?); - - Ok(hdr) - } - } - - impl<'de> de::Deserialize<'de> for QuicHdr { - fn deserialize(de: D) -> Result - where - D: Deserializer<'de>, - { - de.deserialize_bytes(HdrVisitor) } + Ok(QuicFirstByteHdr { first_byte: value }) } } #[cfg(test)] mod tests { - use core::ptr::{addr_of_mut, write}; - - #[cfg(feature = "serde")] - use bincode; - #[cfg(feature = "serde")] - use serde_test::{assert_tokens, Token}; - use super::*; - #[cfg(feature = "serde")] - extern crate alloc; + use core::mem; + + #[test] + fn test_long_hdr_accessors() { + let first_byte = QuicFirstByteHdr::new(0, 0, 3); + let fixed_hdr = QuicFixedLongHdr::new(1, 8); + let mut hdr = QuicLongHdr::new(first_byte, fixed_hdr); + assert!(hdr.set_token_len(10).is_ok()); + assert_eq!(hdr.token_len().unwrap(), 10); + let mut hdr_invalid = hdr; + hdr_invalid.first_byte.set_long_packet_type(1); + assert!(hdr_invalid.set_token_len(10).is_err()); + assert!(hdr_invalid.token_len().is_err()); + assert_eq!(hdr.length().unwrap(), 0); + let mut hdr_retry = hdr; + hdr_retry.first_byte.set_long_packet_type(3); + assert!(hdr_retry.length().is_err()); + assert!(hdr.set_pn(12345).is_ok()); + assert_eq!(hdr.pn().unwrap(), 12345); + assert!(hdr_retry.set_pn(54321).is_err()); + assert!(hdr_retry.pn().is_err()); + } #[test] - fn test_min_header_len_constants() { - assert_eq!(QuicHdr::MIN_LONG_HDR_LEN_ON_WIRE, 7); - assert_eq!(QuicHdr::MIN_SHORT_HDR_LEN_ON_WIRE, 1); + fn test_short_hdr_accessors() { + let first_byte = QuicFirstByteHdr { first_byte: 0 }; + let mut hdr = QuicShortHdr::new(8, first_byte); + hdr.set_dc_id_len(10); + assert_eq!(hdr.dc_id_len(), 10); + let dc_id = [1; QUIC_MAX_CID_LEN]; + hdr.set_dc_id(dc_id); + assert_eq!(hdr.dc_id(), dc_id); + hdr.set_pn(12345); + assert_eq!(hdr.pn(), 12345); + hdr.set_spin_bit(true); + assert!(hdr.spin_bit()); + hdr.set_spin_bit(false); + assert!(!hdr.spin_bit()); + hdr.set_key_phase(true); + assert!(hdr.key_phase()); + hdr.set_key_phase(false); + assert!(!hdr.key_phase()); + hdr.set_packet_number_length(4); + assert_eq!(hdr.packet_number_length(), 4); } #[test] - fn test_long_header_creation_and_accessors() { - let mut storage = QuicHdr::new(QuicHeaderType::QuicLong); - let mut hdr = storage.parse(0).unwrap(); + fn test_first_byte_new() { + let packet_type = 0b01; + let reserved_bits = 0b10; + let pn_len_bits = 0b11; + let hdr = QuicFirstByteHdr::new(packet_type, reserved_bits, pn_len_bits); + assert_eq!( + hdr.first_byte, + HEADER_FORM_BIT + | FIXED_BIT_MASK + | (packet_type << LONG_PACKET_TYPE_SHIFT) + | (reserved_bits << RESERVED_BITS_LONG_SHIFT) + | pn_len_bits + ); assert!(hdr.is_long_header()); - assert_eq!(hdr.first_byte() & 0xC0, HEADER_FORM_BIT | FIXED_BIT_MASK); - assert!(hdr.set_long_packet_type(QuicPacketType::ZeroRTT).is_ok()); - assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::ZeroRTT)); - assert!(hdr.set_reserved_bits_long(0b00).is_ok()); - assert_eq!(hdr.reserved_bits_long(), Ok(0b00)); - assert!(hdr.set_packet_number_length_long(4).is_ok()); - assert_eq!(hdr.pn_length_bits_long(), Ok(0b11)); - assert_eq!(hdr.packet_number_length_long(), Ok(4)); - assert!(hdr.set_version(0x0000_0001).is_ok()); - assert_eq!(hdr.version(), Ok(0x0000_0001)); - let dcid_data = [1, 2, 3, 4, 5, 6, 7, 8]; - let scid_data = [0xA, 0xB, 0xC, 0xD]; - hdr.set_dc_id(&dcid_data); - assert!(hdr.set_sc_id(&scid_data).is_ok()); - assert_eq!(hdr.dc_id_len(), 8); - assert_eq!(hdr.dc_id_len_on_wire(), Ok(8)); - assert_eq!(hdr.dc_id(), &dcid_data); - assert_eq!(hdr.sc_id_len_on_wire(), Ok(4)); - assert_eq!(hdr.sc_id().unwrap(), &scid_data); - unsafe { - assert_eq!(hdr.hdr.inner.long.dst.len(), 8); - assert_eq!(hdr.hdr.inner.long.dst.as_slice(), &dcid_data); - assert_eq!(hdr.hdr.inner.long.src.len(), 4); - assert_eq!(hdr.hdr.inner.long.src.as_slice(), &scid_data); - } + assert_eq!(hdr.long_packet_type(), packet_type); + assert_eq!(hdr.reserved_bits_long(), reserved_bits); + assert_eq!(hdr.pn_length_bits_long(), pn_len_bits); + assert_eq!(hdr.packet_number_length_long(), (pn_len_bits + 1) as usize); } #[test] - fn test_short_header_creation_and_accessors() { - let dcid_data = [0xAA, 0xBB, 0xCC]; - let mut storage = QuicHdr::new(QuicHeaderType::QuicShort { - dc_id_len: dcid_data.len() as u8, - }); - let mut hdr = storage.parse(dcid_data.len() as u8).unwrap(); + fn test_first_byte_new_short_header() { + let first_byte = QuicFirstByteHdr::new_short_header_first_byte(true, true, 0b11); + let hdr = QuicFirstByteHdr { first_byte }; assert!(!hdr.is_long_header()); - assert_eq!(hdr.first_byte() & 0xC0, FIXED_BIT_MASK); // Form bit 0, Fixed bit 1 - assert!(hdr.set_short_spin_bit(true).is_ok()); - assert_eq!(hdr.short_spin_bit(), Ok(true)); - assert!(hdr.set_short_reserved_bits(0b00).is_ok()); - assert_eq!(hdr.short_reserved_bits(), Ok(0b00)); - assert!(hdr.set_short_key_phase(false).is_ok()); - assert_eq!(hdr.short_key_phase(), Ok(false)); - assert!(hdr.set_short_packet_number_length(1).is_ok()); - assert_eq!(hdr.short_pn_length_bits(), Ok(0b00)); - assert_eq!(hdr.short_packet_number_length(), Ok(1)); - hdr.set_dc_id(&dcid_data); - if let QuicHeaderType::QuicShort { dc_id_len } = hdr.header_type() { - assert_eq!(dc_id_len, dcid_data.len() as u8); - } else { - panic!("Header type mismatch after setting DCID for short header."); - } - assert_eq!(hdr.dc_id_len(), dcid_data.len() as u8); - assert_eq!(hdr.dc_id(), &dcid_data); - assert!(hdr.sc_id_len_on_wire().is_err()); - assert!(hdr.sc_id().is_err()); - assert!(hdr.version().is_err()); + assert!(hdr.short_spin_bit()); + assert!(hdr.short_key_phase()); + assert_eq!(hdr.short_pn_length_bits(), 0b11); + assert_eq!(hdr.short_packet_number_length(), 4); } #[test] - fn test_stateless_parsing_logic() { - let mut storage = QuicHdr::new(QuicHeaderType::QuicLong); - { - let mut parsed_long = storage.parse(0).unwrap(); - parsed_long.set_dc_id(&[1, 2, 3]); - assert!(parsed_long.set_version(123).is_ok()); - assert_eq!(parsed_long.dc_id(), &[1, 2, 3]); - } - storage.first_byte = FIXED_BIT_MASK; // a short header's first byte - unsafe { storage.inner.short.dst.set(&[4, 5]) }; - { - let parsed_short = storage.parse(2).unwrap(); - assert!(!parsed_short.is_long_header()); - assert_eq!(parsed_short.dc_id(), &[4, 5]); - assert!(parsed_short.version().is_err()); - } + fn test_first_byte_accessors() { + let mut hdr = QuicFirstByteHdr::new(0, 0, 0); + assert!(hdr.is_long_header()); + hdr.set_header_form(false); + assert!(!hdr.is_long_header()); + hdr.set_header_form(true); + assert!(hdr.is_long_header()); + hdr.set_first_byte(42); + assert_eq!(hdr.first_byte(), 42); } - #[cfg(feature = "serde")] - #[test] - fn test_long_header_serde_roundtrip() { - let mut storage = QuicHdr::new(QuicHeaderType::QuicLong); - let mut hdr = storage.parse(0).unwrap(); - hdr.set_first_byte(0xC0 | (0b00 << 4) | (0b00 << 2) | 0b01); - assert!(hdr.set_version(0x01020304).is_ok()); - hdr.set_dc_id(&[0xAA; 8]); - assert!(hdr.set_sc_id(&[0xBB; 4]).is_ok()); - let expected_first_byte = hdr.first_byte(); - assert_eq!(expected_first_byte, 0xC1); - let config = bincode::config::standard().with_fixed_int_encoding(); - let bytes = bincode::serde::encode_to_vec(&storage, config).expect("Serialization failed"); - let on_wire_len = 1 + 4 + 1 + 8 + 1 + 4; - assert_eq!(bytes.len(), 8 + on_wire_len); - let header_bytes = &bytes[8..]; - assert_eq!(header_bytes[0], expected_first_byte); - assert_eq!(&header_bytes[1..5], &0x01020304u32.to_be_bytes()); // Version - assert_eq!(header_bytes[5], 8); // DCIL - assert_eq!(&header_bytes[6..14], &[0xAA; 8]); // DCID - assert_eq!(header_bytes[14], 4); // SCIL - assert_eq!(&header_bytes[15..19], &[0xBB; 4]); // SCID - assert_eq!(header_bytes.len(), on_wire_len); - let (mut de_storage, len): (QuicHdr, usize) = - bincode::serde::decode_from_slice(&bytes, config).expect("Deserialization failed"); - assert_eq!(len, bytes.len()); - let de = de_storage.parse(0).unwrap(); - assert_eq!(de.first_byte(), expected_first_byte); - assert!(de.is_long_header()); - assert_eq!(de.header_type(), QuicHeaderType::QuicLong); - assert_eq!(de.version().unwrap(), 0x01020304); - assert_eq!(de.dc_id_len(), 8); - assert_eq!(de.dc_id(), &[0xAA; 8]); - assert_eq!(de.sc_id_len_on_wire().unwrap(), 4); - assert_eq!(de.sc_id().unwrap(), &[0xBB; 4]); - assert_eq!(de.long_packet_type(), Ok(QuicPacketType::Initial)); - assert_eq!(de.reserved_bits_long(), Ok(0b00)); - assert_eq!(de.pn_length_bits_long(), Ok(0b01)); // PNLEN=2 - } - - #[cfg(feature = "serde")] #[test] - fn test_short_header_serde_roundtrip() { - let dcid_data = [0xCC, 0xDD, 0xEE, 0xFF, 0x11]; - let mut storage = QuicHdr::new(QuicHeaderType::QuicShort { - dc_id_len: dcid_data.len() as u8, - }); - let mut hdr = storage.parse(dcid_data.len() as u8).unwrap(); - hdr.set_first_byte(0x40 | SHORT_SPIN_BIT_MASK | SHORT_KEY_PHASE_BIT_MASK | 0b00); - hdr.set_dc_id(&dcid_data); - let expected_first_byte = hdr.first_byte(); - assert_eq!(expected_first_byte, 0x40 | 0x20 | 0x04 | 0b00); // 0x64 - let config = bincode::config::standard().with_fixed_int_encoding(); - let bytes = bincode::serde::encode_to_vec(&storage, config).expect("Serialization failed"); - let on_wire_len = 1 + dcid_data.len(); - assert_eq!(bytes.len(), 8 + on_wire_len); - let header_bytes = &bytes[8..]; - assert_eq!(header_bytes[0], expected_first_byte); - assert_eq!(&header_bytes[1..], &dcid_data); - assert_eq!(header_bytes.len(), on_wire_len); - let (mut de_storage, len): (QuicHdr, usize) = - bincode::serde::decode_from_slice(&bytes, config).expect("Deserialization failed"); - assert_eq!(len, bytes.len()); - let de = de_storage.parse(dcid_data.len() as u8).unwrap(); - assert_eq!(de.first_byte(), expected_first_byte); - assert!(!de.is_long_header()); - if let QuicHeaderType::QuicShort { dc_id_len } = de.header_type() { - assert_eq!(dc_id_len, dcid_data.len() as u8); - } else { - panic!("Deserialized to wrong header type: {:?}", de.header_type()); - } - assert_eq!(de.dc_id_len(), dcid_data.len() as u8); - assert_eq!(de.dc_id(), &dcid_data); - assert_eq!(de.short_spin_bit(), Ok(true)); - assert_eq!(de.short_reserved_bits(), Ok(0b00)); - assert_eq!(de.short_key_phase(), Ok(true)); - assert_eq!(de.short_pn_length_bits(), Ok(0b00)); + fn test_first_byte_long_header_accessors() { + let mut hdr = QuicFirstByteHdr::new(0, 0, 0); + assert!(hdr.is_long_header()); + hdr.set_long_packet_type(0b11); + assert_eq!(hdr.long_packet_type(), 0b11); + hdr.set_reserved_bits_long(0b01); + assert_eq!(hdr.reserved_bits_long(), 0b01); + hdr.set_pn_length_bits_long(0b01); + assert_eq!(hdr.pn_length_bits_long(), 0b01); + assert_eq!(hdr.packet_number_length_long(), 2); } #[test] - fn test_cid_struct_helpers() { - let mut cid_long = QuicDstConnLong::new(); - assert!(cid_long.is_empty()); - cid_long.set(&[0xDE, 0xAD, 0xBE, 0xEF]); - assert_eq!(cid_long.len(), 4); - assert!(!cid_long.is_empty()); - assert_eq!(cid_long.as_slice(), &[0xDE, 0xAD, 0xBE, 0xEF]); - let mut full_cid_bytes_expected = [0u8; QUIC_MAX_CID_LEN]; - full_cid_bytes_expected[0..4].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]); - assert_eq!(cid_long.bytes, full_cid_bytes_expected); - let mut cid_short = QuicDstConnShort::new(); - cid_short.set(&[0x11, 0x22]); - assert_eq!(cid_short.len(), 2); - assert_eq!(cid_short.as_slice(), &[0x11, 0x22]); - } - - #[cfg(feature = "serde")] - #[test] - fn test_cid_serde_long_direct() { - let mut cid = QuicDstConnLong::new(); - cid.set(&[1, 2, 3]); - assert_tokens(&cid, &[Token::Bytes(&[3, 1, 2, 3])]); + fn test_first_byte_short_header_accessors() { + let mut hdr = QuicFirstByteHdr { + first_byte: FIXED_BIT_MASK, + }; + assert!(!hdr.is_long_header()); + hdr.set_short_spin_bit(true); + assert!(hdr.short_spin_bit()); + hdr.set_short_spin_bit(false); + assert!(!hdr.short_spin_bit()); + hdr.set_short_key_phase(true); + assert!(hdr.short_key_phase()); + hdr.set_short_key_phase(false); + assert!(!hdr.short_key_phase()); + hdr.set_short_pn_length_bits(0b01); + assert_eq!(hdr.short_pn_length_bits(), 0b01); + assert_eq!(hdr.short_packet_number_length(), 2); } - #[cfg(feature = "serde")] #[test] - fn test_cid_serde_short_direct() { - let mut cid = QuicDstConnShort::new(); - cid.set(&[1, 2, 3, 4]); - assert_tokens(&cid, &[Token::Bytes(&[1, 2, 3, 4])]); + fn test_fixed_long_hdr_accessors() { + let mut hdr = QuicFixedLongHdr::new(0xaaaaaaaa, 8); + assert_eq!(hdr.version(), 0xaaaaaaaa); + assert_eq!(hdr.dc_id_len(), 8); + hdr.set_version(0xbbbbbbbb); + assert_eq!(hdr.version(), 0xbbbbbbbb); + hdr.set_dc_id_len(4); + assert_eq!(hdr.dc_id_len(), 4); } #[test] - fn test_ebpf_like_agent_parsing_long_header() { - let packet_bytes: &[u8] = &[ - 0xC1, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, 0x08, - 0x05, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xAB, 0xCD, - ]; - let first_byte = packet_bytes[0]; - assert_eq!((first_byte & HEADER_FORM_BIT), HEADER_FORM_BIT); - let mut hdr_storage = QuicHdr { - first_byte: 0, - inner: QuicHdrUn::new(), - }; - hdr_storage.first_byte = first_byte; - unsafe { - let long = &mut hdr_storage.inner.long; - long.version.copy_from_slice(&packet_bytes[1..5]); - long.dst.len = packet_bytes[5]; - long.dst.bytes[..8].copy_from_slice(&packet_bytes[6..14]); - long.src.len = packet_bytes[14]; - long.src.bytes[..5].copy_from_slice(&packet_bytes[15..20]); - } - let parsed = hdr_storage.parse(0).unwrap(); - assert!(parsed.is_long_header()); - assert_eq!(parsed.long_packet_type(), Ok(QuicPacketType::Initial)); - assert_eq!(parsed.packet_number_length_long(), Ok(2)); - assert_eq!(parsed.version(), Ok(0x00000001)); - assert_eq!( - parsed.dc_id(), - &[0x83, 0x94, 0xC8, 0xF0, 0x3E, 0x51, 0x57, 0x08] - ); - assert_eq!(parsed.sc_id().unwrap(), &[0xAA, 0xBB, 0xCC, 0xDD, 0xEE]); + fn test_long_hdr_new() { + let first_byte = QuicFirstByteHdr::new(0, 0, 0); + let fixed_hdr = QuicFixedLongHdr::new(1, 8); + let long_hdr = QuicLongHdr::new(first_byte, fixed_hdr); + assert_eq!(long_hdr.first_byte, first_byte); + assert_eq!(long_hdr.fixed_hdr, fixed_hdr); + assert_eq!(long_hdr.dc_id, [0; QUIC_MAX_CID_LEN]); + assert_eq!(long_hdr.sc_id_len, 0); + assert_eq!(long_hdr.sc_id, [0; QUIC_MAX_CID_LEN]); + assert_eq!(long_hdr.pn, [0; 4]); } #[test] - fn test_ebpf_like_agent_parsing_short_header() { - let known_dcid_value_from_context = [0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]; - let known_dcid_len = known_dcid_value_from_context.len() as u8; - let packet_bytes: &[u8] = &[0x45, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]; - let mut hdr_storage = QuicHdr { - first_byte: 0, - inner: QuicHdrUn::new(), + fn test_short_hdr_new() { + let first_byte = QuicFirstByteHdr { + first_byte: QuicFirstByteHdr::new_short_header_first_byte(false, false, 0), }; - hdr_storage.first_byte = packet_bytes[0]; - unsafe { - hdr_storage.inner.short.dst.bytes[..known_dcid_len as usize] - .copy_from_slice(&packet_bytes[1..]); - } - let parsed = hdr_storage.parse(known_dcid_len).unwrap(); - assert!(!parsed.is_long_header()); - assert_eq!(parsed.short_spin_bit(), Ok(false)); - assert_eq!(parsed.short_key_phase(), Ok(true)); - assert_eq!(parsed.short_packet_number_length(), Ok(2)); - assert_eq!(parsed.dc_id_len(), known_dcid_len); - assert_eq!(parsed.dc_id(), &known_dcid_value_from_context); + let dc_id_len = 8; + let short_hdr = QuicShortHdr::new(dc_id_len, first_byte); + assert_eq!(short_hdr.dc_id_len, dc_id_len); + assert_eq!(short_hdr.first_byte, first_byte); + assert_eq!(short_hdr.dc_id, [0; QUIC_MAX_CID_LEN]); + assert_eq!(short_hdr.pn, [0; 4]); } - #[test] - fn test_long_packet_type_enum() { - let mut storage = QuicHdr::new(QuicHeaderType::QuicLong); - let mut hdr = storage.parse(0).unwrap(); - hdr.set_first_byte(0xC0); - assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::Initial)); - hdr.set_first_byte(0xD0); - assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::ZeroRTT)); - hdr.set_first_byte(0xE0); - assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::Handshake)); - hdr.set_first_byte(0xF0); - assert_eq!(hdr.long_packet_type(), Ok(QuicPacketType::Retry)); + struct MockCtx<'a> { + buf: &'a [u8], } - #[test] - fn test_cid_set_truncation() { - let mut cid = QuicDstConnShort::new(); - let oversized_data = [0u8; QUIC_MAX_CID_LEN + 5]; - cid.set(&oversized_data); - assert_eq!(cid.len() as usize, QUIC_MAX_CID_LEN); - assert_eq!(cid.as_slice(), &oversized_data[..QUIC_MAX_CID_LEN]); + impl<'a> MockCtx<'a> { + fn load(&self, offset: usize) -> Result { + if offset + mem::size_of::() > self.buf.len() { + return Err(()); + } + let mut t = mem::MaybeUninit::::uninit(); + unsafe { + let ptr = self.buf.as_ptr().add(offset) as *const u8; + core::ptr::copy_nonoverlapping(ptr, t.as_mut_ptr() as *mut u8, mem::size_of::()); + Ok(t.assume_init()) + } + } } #[test] - fn test_parse_errors() { - let mut storage_long_dcid = QuicHdr::new(QuicHeaderType::QuicLong); - unsafe { - write( - addr_of_mut!(storage_long_dcid.inner.long.dst.len), - (QUIC_MAX_CID_LEN + 1) as u8, - ); - } - - assert_eq!( - storage_long_dcid.parse(0).unwrap_err(), - QuicHdrError::InvalidLength - ); - let mut storage_long_scid = QuicHdr::new(QuicHeaderType::QuicLong); - unsafe { - write( - addr_of_mut!(storage_long_scid.inner.long.src.len), - (QUIC_MAX_CID_LEN + 1) as u8, - ); + fn test_parse_long_initial_header() { + let buf = [ + 0xc3, // Long Header, Type Initial, PN len 4 + 0x00, 0x00, 0x00, 0x01, // Version 1 + 0x08, // DCID Len + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // DCID + 0x08, // SCID Len + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // SCID + 0x00, // Token Length (0) + 0x0a, // Length (10) + 0x0a, 0x0b, 0x0c, 0x0d, // Packet Number + ]; + let ctx = MockCtx { buf: &buf }; + let mut offset = 0; + let hdr = parse_quic_hdr!(&ctx, offset, 8).unwrap(); + let mut expected: QuicLongHdr = unsafe { mem::zeroed() }; + expected.first_byte.first_byte = 0xc3; + expected.fixed_hdr.version = [0x00, 0x00, 0x00, 0x01]; + expected.fixed_hdr.dc_id_len = 8; + expected.dc_id[..8].copy_from_slice(&[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]); + expected.sc_id_len = 8; + expected.sc_id[..8].copy_from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + expected.token_len[0] = 0; + expected.length[0] = 10; + expected.pn.copy_from_slice(&[0x0a, 0x0b, 0x0c, 0x0d]); + if let QuicHdr::Long(long_hdr) = hdr { + assert_eq!(long_hdr, expected); + } else { + panic!("Expected Long Header"); } - assert_eq!( - storage_long_scid.parse(0).unwrap_err(), - QuicHdrError::InvalidLength - ); - let mut storage_short = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: 8 }); - assert_eq!( - storage_short - .parse((QUIC_MAX_CID_LEN + 1) as u8) - .unwrap_err(), - QuicHdrError::InvalidLength - ); } #[test] - fn test_invalid_header_form_errors() { - let mut long_storage = QuicHdr::new(QuicHeaderType::QuicLong); - let mut long_hdr = long_storage.parse(0).unwrap(); - assert_eq!( - long_hdr.short_spin_bit(), - Err(QuicHdrError::InvalidHeaderForm) - ); - assert_eq!( - long_hdr.short_key_phase(), - Err(QuicHdrError::InvalidHeaderForm) - ); - assert_eq!( - long_hdr.set_short_spin_bit(true), - Err(QuicHdrError::InvalidHeaderForm) - ); - let mut short_storage = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: 8 }); - let mut short_hdr = short_storage.parse(8).unwrap(); - assert_eq!(short_hdr.version(), Err(QuicHdrError::InvalidHeaderForm)); - assert_eq!( - short_hdr.long_packet_type(), - Err(QuicHdrError::InvalidHeaderForm) - ); - assert_eq!(short_hdr.sc_id(), Err(QuicHdrError::InvalidHeaderForm)); - assert_eq!( - short_hdr.set_version(1), - Err(QuicHdrError::InvalidHeaderForm) - ); + fn test_parse_long_retry_header() { + let buf = [ + 0xf0, // Long Header, Type Retry, PN len 1 (unused) + 0x00, 0x00, 0x00, 0x01, // Version 1 + 0x08, // DCID Len + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // DCID + 0x08, // SCID Len + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // SCID + 0xAA, // "Packet Number" but actually first byte of Retry Token + ]; + let ctx = MockCtx { buf: &buf }; + let mut offset = 0; + let hdr = parse_quic_hdr!(&ctx, offset, 8).unwrap(); + let mut expected: QuicLongHdr = unsafe { mem::zeroed() }; + expected.first_byte.first_byte = 0xf0; + expected.fixed_hdr.version = [0x00, 0x00, 0x00, 0x01]; + expected.fixed_hdr.dc_id_len = 8; + expected.dc_id[..8].copy_from_slice(&[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]); + expected.sc_id_len = 8; + expected.sc_id[..8].copy_from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + expected.pn[0] = 0xAA; // Parser reads first byte of token as PN + if let QuicHdr::Long(long_hdr) = hdr { + assert_eq!(long_hdr, expected); + } else { + panic!("Expected Long Header"); + } } #[test] - fn test_quic_packet_type_try_from_invalid() { - assert_eq!( - QuicPacketType::try_from(0x04), - Err(QuicHdrError::InvalidPacketTypeBits) - ); - assert_eq!( - QuicPacketType::try_from(0xFF), - Err(QuicHdrError::InvalidPacketTypeBits) - ); + fn test_parse_long_handshake_header() { + let buf = [ + 0xe2, // Long Header, Type Handshake, PN len 3 + 0x00, 0x00, 0x00, 0x01, // Version 1 + 0x08, // DCID Len + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // DCID + 0x08, // SCID Len + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // SCID + 0x0a, // Length (10) + 0x0a, 0x0b, 0x0c, // Packet Number + ]; + let ctx = MockCtx { buf: &buf }; + let mut offset = 0; + let hdr = parse_quic_hdr!(&ctx, offset, 8).unwrap(); + let mut expected: QuicLongHdr = unsafe { mem::zeroed() }; + expected.first_byte.first_byte = 0xe2; + expected.fixed_hdr.version = [0x00, 0x00, 0x00, 0x01]; + expected.fixed_hdr.dc_id_len = 8; + expected.dc_id[..8].copy_from_slice(&[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]); + expected.sc_id_len = 8; + expected.sc_id[..8].copy_from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + expected.length[0] = 10; + expected.pn[..3].copy_from_slice(&[0x0a, 0x0b, 0x0c]); + if let QuicHdr::Long(long_hdr) = hdr { + assert_eq!(long_hdr, expected); + } else { + panic!("Expected Long Header"); + } } #[test] - fn test_display_and_debug_impls() { - use core::fmt::Write; - - struct StringWriter { - buffer: [u8; N], - len: usize, - } - impl StringWriter { - fn new() -> Self { - Self { - buffer: [0; N], - len: 0, - } - } - fn as_str(&self) -> &str { - core::str::from_utf8(&self.buffer[..self.len]).unwrap() - } - } - impl Write for StringWriter { - fn write_str(&mut self, s: &str) -> core::fmt::Result { - let bytes = s.as_bytes(); - if self.len + bytes.len() > self.buffer.len() { - return Err(core::fmt::Error); - } - self.buffer[self.len..self.len + bytes.len()].copy_from_slice(bytes); - self.len += bytes.len(); - Ok(()) - } + fn test_parse_long_rtt_header() { + let buf = [ + 0xd1, // Long Header, Type 0-RTT, PN len 2 + 0x00, 0x00, 0x00, 0x01, // Version 1 + 0x08, // DCID Len + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // DCID + 0x08, // SCID Len + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // SCID + 0x0a, // Length (10) + 0x0a, 0x0b, // Packet Number + ]; + let ctx = MockCtx { buf: &buf }; + let mut offset = 0; + let hdr = parse_quic_hdr!(&ctx, offset, 8).unwrap(); + let mut expected: QuicLongHdr = unsafe { mem::zeroed() }; + expected.first_byte.first_byte = 0xd1; + expected.fixed_hdr.version = [0x00, 0x00, 0x00, 0x01]; + expected.fixed_hdr.dc_id_len = 8; + expected.dc_id[..8].copy_from_slice(&[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]); + expected.sc_id_len = 8; + expected.sc_id[..8].copy_from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + expected.length[0] = 10; + expected.pn[..2].copy_from_slice(&[0x0a, 0x0b]); + if let QuicHdr::Long(long_hdr) = hdr { + assert_eq!(long_hdr, expected); + } else { + panic!("Expected Long Header"); } - let err = QuicHdrError::InvalidHeaderForm; - let mut writer = StringWriter::<128>::new(); - write!(writer, "{}", err).unwrap(); - assert_eq!( - writer.as_str(), - "Operation invalid for current QUIC header form" - ); - let mut cid = QuicDstConnShort::new(); - cid.set(&[0xDE, 0xAD, 0xBE, 0xEF]); - let mut writer = StringWriter::<128>::new(); - write!(writer, "{:?}", cid).unwrap(); - assert_eq!(writer.as_str(), "QuicDstConnShort(de:ad:be:ef)"); - let long_storage = QuicHdr::new(QuicHeaderType::QuicLong); - let mut writer = StringWriter::<128>::new(); - write!(writer, "{:?}", long_storage).unwrap(); - assert!(writer.as_str().contains("is_long: true")); } #[test] - fn test_set_packet_number_length_edge_cases() { - let mut storage = QuicHdr::new(QuicHeaderType::QuicLong); - let mut hdr = storage.parse(0).unwrap(); - assert_eq!( - hdr.set_packet_number_length_long(0), - Err(QuicHdrError::InvalidLength) - ); - assert_eq!( - hdr.set_packet_number_length_long(5), - Err(QuicHdrError::InvalidLength) - ); - assert!(hdr.set_packet_number_length_long(1).is_ok()); - assert_eq!(hdr.packet_number_length_long(), Ok(1)); - assert!(hdr.set_packet_number_length_long(4).is_ok()); - assert_eq!(hdr.packet_number_length_long(), Ok(4)); + fn test_parse_short_header() { + let mut payload = [0u8; 16]; + let mut len = 0; + payload[len] = 0b0100_0001; // Short Header, PN Len: 2 + len += 1; + let dcid = [0xAA; 8]; + payload[len..len + dcid.len()].copy_from_slice(&dcid); + len += dcid.len(); + let pn = [0x1, 0x2]; + payload[len..len + pn.len()].copy_from_slice(&pn); + len += pn.len(); + let ctx = MockCtx { + buf: &payload[..len], + }; + let mut offset = 0; + let hdr = parse_quic_hdr!(&ctx, offset, 8).unwrap(); + if let QuicHdr::Short(short_hdr) = hdr { + assert_eq!(&short_hdr.dc_id[..dcid.len()], &dcid[..]); + assert_eq!(&short_hdr.pn[..pn.len()], &pn[..]); + } else { + panic!("Expected Short Header"); + } } #[test] - fn test_zero_length_cids() { - let mut storage = QuicHdr::new(QuicHeaderType::QuicLong); - let mut hdr = storage.parse(0).unwrap(); - hdr.set_dc_id(&[]); - assert!(hdr.set_sc_id(&[]).is_ok()); - assert_eq!(hdr.dc_id_len(), 0); - assert_eq!(hdr.dc_id(), &[]); - assert_eq!(hdr.sc_id_len_on_wire(), Ok(0)); - assert_eq!(hdr.sc_id().unwrap(), &[]); - let mut short_storage = QuicHdr::new(QuicHeaderType::QuicShort { dc_id_len: 0 }); - let mut short_hdr = short_storage.parse(0).unwrap(); - short_hdr.set_dc_id(&[]); - assert_eq!(short_hdr.dc_id_len(), 0); - assert_eq!(short_hdr.dc_id(), &[]); - } - - #[cfg(feature = "serde")] - #[test] - fn test_deserialization_failures() { - let config = bincode::config::standard().with_fixed_int_encoding(); - let run_test = |bytes: &[u8]| -> Result<(QuicHdr, usize), _> { - let encoded = bincode::serde::encode_to_vec(bytes, config).unwrap(); - bincode::serde::decode_from_slice(&encoded, config) - }; - assert!( - run_test(&[]).is_err(), - "Deserializing empty bytes should fail" - ); - assert!( - run_test(&[0xC0, 1, 2, 3, 4, 5]).is_err(), - "Deserializing truncated long header should fail" - ); - let truncated_dcid = &[0xC0, 0, 0, 0, 1, 8, 4, 1, 2, 3, 4]; - assert!( - run_test(truncated_dcid).is_err(), - "Deserializing long header with truncated DCID should fail" - ); - let oversized_dcil = &[0xC0, 0, 0, 0, 1, (QUIC_MAX_CID_LEN + 1) as u8, 0]; - assert!( - run_test(oversized_dcil).is_err(), - "Deserializing long header with oversized DCIL should fail" - ); - let oversized_scil = &[0xC0, 0, 0, 0, 1, 0, (QUIC_MAX_CID_LEN + 1) as u8]; - assert!( - run_test(oversized_scil).is_err(), - "Deserializing long header with oversized SCIL should fail" - ); + fn test_parse_header_too_short() { + let payload = [0b1100_0000]; // Just a single byte + let ctx = MockCtx { buf: &payload }; + let mut offset = 0; + let result = parse_quic_hdr!(&ctx, offset, 8); + assert!(result.is_err()); } } From b40981bd005143ef5026fdc80086a2c7c04fd176 Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Thu, 26 Jun 2025 14:54:15 -0500 Subject: [PATCH 10/14] Renamed `quic_fixed_hdr` to `quic_first_byte` for clarity and consistency across QUIC header parsing logic. --- src/quic.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/quic.rs b/src/quic.rs index cd61569..166d740 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -135,15 +135,15 @@ macro_rules! parse_quic_hdr { (|| -> Result<$crate::quic::QuicHdr, ()> { use $crate::quic; use $crate::{read_var_buf_32, read_var_buf_from_len_byte_16}; - let quic_fixed_hdr: quic::QuicFirstByteHdr = $ctx.load($off).map_err(|_| ())?; + let quic_first_byte: quic::QuicFirstByteHdr = $ctx.load($off).map_err(|_| ())?; $off += quic::QuicFirstByteHdr::LEN; - match quic_fixed_hdr.is_long_header() { + match quic_first_byte.is_long_header() { true => { let quic_fixed_long_hdr: quic::QuicFixedLongHdr = $ctx.load($off).map_err(|_| ())?; $off += quic::QuicFixedLongHdr::LEN; let mut quic_long_hdr = - quic::QuicLongHdr::new(quic_fixed_hdr, quic_fixed_long_hdr); + quic::QuicLongHdr::new(quic_first_byte, quic_fixed_long_hdr); read_var_buf_32!( $ctx, $off, @@ -162,7 +162,7 @@ macro_rules! parse_quic_hdr { quic::QUIC_MAX_CID_LEN ) .map_err(|_| ())?; - if quic_fixed_hdr.long_packet_type() == 0 { + if quic_first_byte.long_packet_type() == 0 { let token_len_byte: u8 = $ctx.load($off).map_err(|_| ())?; $off += 1; read_var_buf_from_len_byte_16!( @@ -176,7 +176,7 @@ macro_rules! parse_quic_hdr { let token_len_val = quic_long_hdr.token_len().map_err(|_| ())?; $off += token_len_val; } - if quic_fixed_hdr.long_packet_type() < 3 { + if quic_first_byte.long_packet_type() < 3 { let len_byte: u8 = $ctx.load($off).map_err(|_| ())?; $off += 1; read_var_buf_from_len_byte_16!( @@ -200,7 +200,7 @@ macro_rules! parse_quic_hdr { } false => { let mut quic_short_hdr = - quic::QuicShortHdr::new($short_dc_id_len, quic_fixed_hdr); + quic::QuicShortHdr::new($short_dc_id_len, quic_first_byte); read_var_buf_32!( $ctx, $off, From d9ef7228478cf95bf2660c913408b8a1ba75daec Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Thu, 26 Jun 2025 23:02:13 -0500 Subject: [PATCH 11/14] Added improved support for getting and setting variable-length fields in using new macros `read_var_u32_from_slice` and `write_var_u32_to_slice`; enhanced QUIC headers variable field getters/setters and updated tests. --- src/macros.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/quic.rs | 19 +++++++++++----- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index d7e9667..67c99e6 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -80,3 +80,66 @@ macro_rules! read_var_buf_from_len_byte_16 { })() }; } + +/// Reads a variable-length integer from a buffer and converts it to a u32. +/// +/// This macro is designed to be eBPF-verifier-friendly by using a `match` +/// statement to handle different lengths, avoiding loops or dynamic indexing +/// that the verifier might reject. It assumes the buffer holds a big-endian integer. +/// +/// # Arguments +/// * `$len`: The length of the variable-length integer in bytes (1 to 4). +/// * `$buf`: The buffer containing the integer bytes. +/// +/// # Returns +/// A `Result` which is `Ok(u32)` on success, or +/// `Err(usize)` with the invalid length on failure. +#[macro_export] +macro_rules! read_var_u32_from_slice { + ($len:expr, $buf:expr) => { + match $len { + 1 => Ok(u32::from_be_bytes([0, 0, 0, $buf[0]])), + 2 => Ok(u32::from_be_bytes([0, 0, $buf[0], $buf[1]])), + 3 => Ok(u32::from_be_bytes([0, $buf[0], $buf[1], $buf[2]])), + 4 => Ok(u32::from_be_bytes([$buf[0], $buf[1], $buf[2], $buf[3]])), + invalid_len => Err(invalid_len), + } + }; +} + +/// Writes a u32 value to a slice as a variable-length integer. +/// +/// This macro determines the minimum number of bytes required to represent +/// the given `u32` value and writes those bytes in big-endian order to the +/// provided buffer. It is designed to be eBPF-verifier friendly. +/// +/// # Arguments +/// * `$val`: The `u32` value to write. +/// * `$buf`: The destination buffer slice. The buffer must be at least 4 bytes long. +/// +/// # Returns +/// The number of bytes written to the buffer (`usize`). +#[macro_export] +macro_rules! write_var_u32_to_slice { + ($val:expr, $buf:expr) => {{ + let val = $val; + let bytes = val.to_be_bytes(); + let buf = $buf; + if val < 1 << 8 { + buf[0] = bytes[3]; + 1 + } else if val < 1 << 16 { + buf[0] = bytes[2]; + buf[1] = bytes[3]; + 2 + } else if val < 1 << 24 { + buf[0] = bytes[1]; + buf[1] = bytes[2]; + buf[2] = bytes[3]; + 3 + } else { + buf.copy_from_slice(&bytes); + 4 + } + }}; +} diff --git a/src/quic.rs b/src/quic.rs index 166d740..9f3bd82 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -1,4 +1,5 @@ use core::mem; +use crate::{read_var_u32_from_slice, write_var_u32_to_slice}; /// The maximum supported length for a QUIC Connection ID (CID), as per RFC 9000. pub const QUIC_MAX_CID_LEN: usize = 20; @@ -33,6 +34,7 @@ const PN_LENGTH_BITS_MASK: u8 = 0x03; #[derive(Debug)] pub enum QuicError { InvalidQuicType, + InvalidQuicFieldLength(usize), } /// Parses a QUIC header from a network buffer within an eBPF context. @@ -472,7 +474,8 @@ impl QuicLongHdr { if self.packet_type() == 3 { return Err(QuicError::InvalidQuicType); } - Ok(u32::from_be_bytes(self.pn)) + read_var_u32_from_slice!(self.first_byte.packet_number_length_long(), self.pn) + .map_err(QuicError::InvalidQuicFieldLength) } /// Sets the Packet Number in the header. @@ -488,8 +491,10 @@ impl QuicLongHdr { if self.packet_type() == 3 { return Err(QuicError::InvalidQuicType); } - self.pn = pn.to_be_bytes(); + let len = write_var_u32_to_slice!(pn, &mut self.pn); + self.first_byte.set_packet_number_length_long(len); Ok(()) + } } @@ -576,8 +581,9 @@ impl QuicShortHdr { /// # Returns /// The 32-bit packet number. #[inline] - pub fn pn(&self) -> u32 { - u32::from_be_bytes(self.pn) + pub fn pn(&self) -> Result { + read_var_u32_from_slice!(self.first_byte.packet_number_length_long(), self.pn) + .map_err(QuicError::InvalidQuicFieldLength) } /// Sets the Packet Number in the header. @@ -586,7 +592,8 @@ impl QuicShortHdr { /// * `pn`: The 32-bit packet number to set. #[inline] pub fn set_pn(&mut self, pn: u32) { - self.pn = pn.to_be_bytes(); + let len = write_var_u32_to_slice!(pn, &mut self.pn); + self.set_packet_number_length(len); } /// Gets the Spin Bit, used for passive RTT measurement. @@ -1080,7 +1087,7 @@ mod tests { hdr.set_dc_id(dc_id); assert_eq!(hdr.dc_id(), dc_id); hdr.set_pn(12345); - assert_eq!(hdr.pn(), 12345); + assert_eq!(hdr.pn().unwrap(), 12345); hdr.set_spin_bit(true); assert!(hdr.spin_bit()); hdr.set_spin_bit(false); From 4922f066cb433456c7bf0fc6c6705dfe520b0b9a Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Thu, 26 Jun 2025 23:44:53 -0500 Subject: [PATCH 12/14] Changed `macros` module to public in `src/lib.rs` for improved access. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 678359a..2ba5417 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,4 +15,4 @@ pub mod udp; pub mod vlan; pub mod vxlan; pub mod llc; -mod macros; +pub mod macros; From 3a26dee7f46f4d0d13e9ee0e7ccc6da9ee1fffe8 Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Thu, 26 Jun 2025 23:47:29 -0500 Subject: [PATCH 13/14] Refactored `src/quic.rs` to remove unnecessary whitespace, reordered imports for consistency, and updated `src/lib.rs` to adjust module declarations. --- src/lib.rs | 4 ++-- src/quic.rs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2ba5417..8a9e47a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,9 @@ pub mod bitfield; pub mod eth; pub mod icmp; pub mod ip; +pub mod llc; pub mod mac; +pub mod macros; pub mod mpls; pub mod quic; pub mod sctp; @@ -14,5 +16,3 @@ pub mod tcp; pub mod udp; pub mod vlan; pub mod vxlan; -pub mod llc; -pub mod macros; diff --git a/src/quic.rs b/src/quic.rs index 9f3bd82..71c8b62 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -1,5 +1,5 @@ -use core::mem; use crate::{read_var_u32_from_slice, write_var_u32_to_slice}; +use core::mem; /// The maximum supported length for a QUIC Connection ID (CID), as per RFC 9000. pub const QUIC_MAX_CID_LEN: usize = 20; @@ -494,7 +494,6 @@ impl QuicLongHdr { let len = write_var_u32_to_slice!(pn, &mut self.pn); self.first_byte.set_packet_number_length_long(len); Ok(()) - } } From 38cfd77aa5ad1e3f7ad5657f8a401c4883f92ca2 Mon Sep 17 00:00:00 2001 From: Connor Sanders Date: Tue, 1 Jul 2025 14:15:41 -0500 Subject: [PATCH 14/14] Reorder and adjust module declarations in `src/lib.rs`, fixing duplicate and inconsistent entries --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 232bc65..2b8c731 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod arp; pub mod bitfield; pub mod eth; +pub mod geneve; pub mod icmp; pub mod ip; pub mod llc; @@ -16,5 +17,3 @@ pub mod tcp; pub mod udp; pub mod vlan; pub mod vxlan; -pub mod llc; -pub mod geneve;