1414use crate :: FRAME_HEADER_SIZE ;
1515use crate :: error:: FrameError ;
1616use rand:: Rng ;
17+ use rand:: SeedableRng ;
18+ use rand:: rngs:: SmallRng ;
19+ use std:: cell:: RefCell ;
20+
21+ thread_local ! {
22+ static PADDING_RNG : RefCell <SmallRng > = RefCell :: new(
23+ SmallRng :: from_rng( & mut rand:: thread_rng( ) ) . expect( "RNG seeding failed" )
24+ ) ;
25+ }
1726
1827/// Maximum payload size (9000 - header - auth tag = 8944)
1928const MAX_PAYLOAD_SIZE : usize = 8944 ;
@@ -64,29 +73,46 @@ pub enum FrameType {
6473 PathResponse = 0x0F ,
6574}
6675
76+ /// Lookup table for constant-time frame type validation.
77+ /// Index 0x00 = Reserved, 0x01-0x0F = valid types, 0x10-0x1F = reserved, rest = invalid.
78+ /// Value 0 = invalid/reserved, non-zero = valid FrameType discriminant.
79+ static FRAME_TYPE_TABLE : [ u8 ; 32 ] = [
80+ 0 , // 0x00: Reserved
81+ 0x01 , // 0x01: Data
82+ 0x02 , // 0x02: Ack
83+ 0x03 , // 0x03: Control
84+ 0x04 , // 0x04: Rekey
85+ 0x05 , // 0x05: Ping
86+ 0x06 , // 0x06: Pong
87+ 0x07 , // 0x07: Close
88+ 0x08 , // 0x08: Pad
89+ 0x09 , // 0x09: StreamOpen
90+ 0x0A , // 0x0A: StreamClose
91+ 0x0B , // 0x0B: StreamReset
92+ 0x0C , // 0x0C: WindowUpdate
93+ 0x0D , // 0x0D: GoAway
94+ 0x0E , // 0x0E: PathChallenge
95+ 0x0F , // 0x0F: PathResponse
96+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , // 0x10-0x17: Reserved
97+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , // 0x18-0x1F: Reserved
98+ ] ;
99+
67100impl TryFrom < u8 > for FrameType {
68101 type Error = FrameError ;
69102
70103 fn try_from ( value : u8 ) -> Result < Self , Self :: Error > {
71- match value {
72- 0x00 => Err ( FrameError :: ReservedFrameType ) ,
73- 0x01 => Ok ( Self :: Data ) ,
74- 0x02 => Ok ( Self :: Ack ) ,
75- 0x03 => Ok ( Self :: Control ) ,
76- 0x04 => Ok ( Self :: Rekey ) ,
77- 0x05 => Ok ( Self :: Ping ) ,
78- 0x06 => Ok ( Self :: Pong ) ,
79- 0x07 => Ok ( Self :: Close ) ,
80- 0x08 => Ok ( Self :: Pad ) ,
81- 0x09 => Ok ( Self :: StreamOpen ) ,
82- 0x0A => Ok ( Self :: StreamClose ) ,
83- 0x0B => Ok ( Self :: StreamReset ) ,
84- 0x0C => Ok ( Self :: WindowUpdate ) ,
85- 0x0D => Ok ( Self :: GoAway ) ,
86- 0x0E => Ok ( Self :: PathChallenge ) ,
87- 0x0F => Ok ( Self :: PathResponse ) ,
88- 0x10 ..=0x1F => Err ( FrameError :: ReservedFrameType ) ,
89- _ => Err ( FrameError :: InvalidFrameType ( value) ) ,
104+ if value < 32 {
105+ let entry = FRAME_TYPE_TABLE [ value as usize ] ;
106+ if entry != 0 {
107+ // SAFETY: entry matches a valid FrameType discriminant (0x01..=0x0F)
108+ Ok ( unsafe { core:: mem:: transmute :: < u8 , FrameType > ( entry) } )
109+ } else if value == 0x00 || ( 0x10 ..=0x1F ) . contains ( & value) {
110+ Err ( FrameError :: ReservedFrameType )
111+ } else {
112+ Err ( FrameError :: InvalidFrameType ( value) )
113+ }
114+ } else {
115+ Err ( FrameError :: InvalidFrameType ( value) )
90116 }
91117 }
92118}
@@ -617,12 +643,9 @@ pub fn build_into_from_parts(
617643 // Write payload
618644 buf[ FRAME_HEADER_SIZE ..FRAME_HEADER_SIZE + payload_len] . copy_from_slice ( payload) ;
619645
620- // Write random padding
646+ // Write random padding using fast PRNG (padding is AEAD-encrypted)
621647 if padding_len > 0 {
622- rand:: Rng :: fill (
623- & mut rand:: thread_rng ( ) ,
624- & mut buf[ FRAME_HEADER_SIZE + payload_len..] ,
625- ) ;
648+ PADDING_RNG . with_borrow_mut ( |rng| rng. fill ( & mut buf[ FRAME_HEADER_SIZE + payload_len..] ) ) ;
626649 }
627650
628651 Ok ( total_size)
@@ -730,9 +753,10 @@ impl FrameBuilder {
730753 // Write payload
731754 buf[ FRAME_HEADER_SIZE ..FRAME_HEADER_SIZE + payload_len] . copy_from_slice ( & self . payload ) ;
732755
733- // Write random padding
756+ // Write random padding using fast PRNG (padding is AEAD-encrypted)
734757 if padding_len > 0 {
735- rand:: thread_rng ( ) . fill ( & mut buf[ FRAME_HEADER_SIZE + payload_len..] ) ;
758+ PADDING_RNG
759+ . with_borrow_mut ( |rng| rng. fill ( & mut buf[ FRAME_HEADER_SIZE + payload_len..] ) ) ;
736760 }
737761
738762 Ok ( total_size)
@@ -743,41 +767,16 @@ impl FrameBuilder {
743767 /// # Errors
744768 ///
745769 /// Returns [`FrameError::PayloadOverflow`] if `total_size` is too small for header + payload.
770+ #[ allow( clippy:: uninit_vec) ]
746771 pub fn build ( self , total_size : usize ) -> Result < Vec < u8 > , FrameError > {
747- let frame_type = self . frame_type . unwrap_or ( FrameType :: Data ) ;
748- let payload_len = self . payload . len ( ) ;
749-
750- if total_size < FRAME_HEADER_SIZE + payload_len {
751- return Err ( FrameError :: PayloadOverflow ) ;
752- }
753-
754- let padding_len = total_size - FRAME_HEADER_SIZE - payload_len;
755772 let mut buf = Vec :: with_capacity ( total_size) ;
756-
757- // Write header
758- buf. extend_from_slice ( & self . nonce ) ;
759- buf. push ( frame_type as u8 ) ;
760- buf. push ( self . flags . as_u8 ( ) ) ;
761- buf. extend_from_slice ( & self . stream_id . to_be_bytes ( ) ) ;
762- buf. extend_from_slice ( & self . sequence . to_be_bytes ( ) ) ;
763- buf. extend_from_slice ( & self . offset . to_be_bytes ( ) ) ;
764- #[ allow( clippy:: cast_possible_truncation) ]
765- let payload_len_u16 = payload_len as u16 ;
766- buf. extend_from_slice ( & payload_len_u16. to_be_bytes ( ) ) ;
767- buf. extend_from_slice ( & [ 0u8 ; 2 ] ) ; // Reserved
768-
769- // Write payload
770- buf. extend_from_slice ( & self . payload ) ;
771-
772- // Write random padding using thread-local PRNG (fast, non-syscall).
773- // Padding bytes are encrypted by AEAD before transmission, so
774- // cryptographic-quality randomness is not required here.
775- if padding_len > 0 {
776- let start = buf. len ( ) ;
777- buf. resize ( start + padding_len, 0 ) ;
778- rand:: thread_rng ( ) . fill ( & mut buf[ start..] ) ;
779- }
780-
773+ // SAFETY: build_into() writes every byte of the buffer:
774+ // - Header (28 bytes): nonce, type, flags, stream_id, sequence, offset, payload_len, reserved
775+ // - Payload: copied from self.payload
776+ // - Padding: filled with random bytes from PADDING_RNG
777+ // Total = FRAME_HEADER_SIZE + payload_len + padding_len = total_size
778+ unsafe { buf. set_len ( total_size) } ;
779+ self . build_into ( & mut buf) ?;
781780 Ok ( buf)
782781 }
783782}
0 commit comments