@@ -25,9 +25,12 @@ const MAX_FRAME_PAYLOAD: usize = 16_777_215; // 2^24 - 1
2525const MAX_STREAMS_PER_CONN : usize = 1_000 ;
2626const MAX_HEADER_BLOCK : usize = 65_536 ;
2727const MAX_DATA_PER_STREAM : usize = 1_048_576 ; // 1 MB
28- const MAX_CONNECTIONS : usize = 10_000 ;
29- /// M1: Cap per-direction buffer to prevent unbounded memory growth.
30- const MAX_DIRECTION_BUF : usize = 16 * 1024 * 1024 ; // 16 MB
28+ /// M1: Reduced from 10,000 to 2,000 to cap compound memory usage.
29+ /// 2,000 connections * 2 MB * 2 directions = ~8 GB worst case.
30+ const MAX_CONNECTIONS : usize = 2_000 ;
31+ /// M1: Cap per-direction buffer. Reduced from 16 MB to 2 MB to keep
32+ /// aggregate memory bounded (~8 GB worst case with MAX_CONNECTIONS).
33+ const MAX_DIRECTION_BUF : usize = 2 * 1024 * 1024 ; // 2 MB
3134
3235/// Direction of data flow within a connection.
3336#[ derive( Debug , Clone , Copy , PartialEq , Eq , Hash ) ]
@@ -55,12 +58,12 @@ struct H2Connection {
5558 detected : Option < bool > ,
5659 /// Monotonic tick for LRU eviction.
5760 last_active : u64 ,
58- /// Accumulates header block fragments for discarded streams (rejected at
61+ /// M3: Accumulates header block fragments for discarded streams (rejected at
5962 /// stream limit) or PUSH_PROMISE frames that span multiple CONTINUATION
6063 /// frames. HPACK is stateful — every header block must be decoded even if
6164 /// the headers are discarded, otherwise the dynamic table becomes corrupted.
62- /// Format: (stream_id, direction, accumulated_header_bytes) .
63- discard_header_buf : Option < ( u32 , H2Direction , Vec < u8 > ) > ,
65+ /// Uses a HashMap to track multiple concurrent discarded streams .
66+ discard_header_bufs : HashMap < u32 , ( H2Direction , Vec < u8 > ) > ,
6467}
6568
6669struct H2Stream {
@@ -78,6 +81,10 @@ struct H2Stream {
7881 end_stream_headers : bool ,
7982 /// Whether END_STREAM was set on a DATA frame.
8083 end_stream_data : bool ,
84+ /// M2: Set when accumulated header block exceeds MAX_HEADER_BLOCK.
85+ /// When true, HPACK decoding is skipped to avoid partial dynamic table
86+ /// mutation from a truncated header block.
87+ header_overflow : bool ,
8188}
8289
8390impl H2Stream {
@@ -90,6 +97,7 @@ impl H2Stream {
9097 end_headers : false ,
9198 end_stream_headers : false ,
9299 end_stream_data : false ,
100+ header_overflow : false ,
93101 }
94102 }
95103}
@@ -104,7 +112,7 @@ impl H2Connection {
104112 server_buf : Vec :: new ( ) ,
105113 detected : None ,
106114 last_active : 0 ,
107- discard_header_buf : None ,
115+ discard_header_bufs : HashMap :: new ( ) ,
108116 }
109117 }
110118}
@@ -288,15 +296,18 @@ impl H2Tracker {
288296 if header_block. len ( ) <= MAX_HEADER_BLOCK {
289297 buf. extend_from_slice ( header_block) ;
290298 }
291- conn. discard_header_buf = Some ( ( stream_id, direction, buf) ) ;
299+ conn. discard_header_bufs . insert ( stream_id, ( direction, buf) ) ;
292300 }
293301 continue ;
294302 }
295303
296304 let stream = conn. streams . entry ( stream_id) . or_insert_with ( H2Stream :: new) ;
297305
306+ // M2: Track header block overflow to avoid partial HPACK decode
298307 if stream. header_buf . len ( ) + header_block. len ( ) <= MAX_HEADER_BLOCK {
299308 stream. header_buf . extend_from_slice ( header_block) ;
309+ } else {
310+ stream. header_overflow = true ;
300311 }
301312
302313 if end_stream {
@@ -305,11 +316,13 @@ impl H2Tracker {
305316
306317 if end_headers {
307318 stream. end_headers = true ;
308- let decoder = match direction {
309- H2Direction :: ClientToServer => & mut conn. client_decoder ,
310- H2Direction :: ServerToClient => & mut conn. server_decoder ,
311- } ;
312- decode_headers ( stream, decoder) ;
319+ if !stream. header_overflow {
320+ let decoder = match direction {
321+ H2Direction :: ClientToServer => & mut conn. client_decoder ,
322+ H2Direction :: ServerToClient => & mut conn. server_decoder ,
323+ } ;
324+ decode_headers ( stream, decoder) ;
325+ }
313326
314327 if stream. end_stream_headers {
315328 if let Some ( msg) = build_message ( stream) {
@@ -322,18 +335,25 @@ impl H2Tracker {
322335 FRAME_CONTINUATION => {
323336 if let Some ( stream) = conn. streams . get_mut ( & stream_id) {
324337 if !stream. end_headers {
325- if stream. header_buf . len ( ) + frame_payload. len ( ) <= MAX_HEADER_BLOCK {
338+ // M2: Track overflow instead of silently dropping
339+ if stream. header_buf . len ( ) + frame_payload. len ( ) <= MAX_HEADER_BLOCK
340+ && !stream. header_overflow
341+ {
326342 stream. header_buf . extend_from_slice ( & frame_payload) ;
343+ } else {
344+ stream. header_overflow = true ;
327345 }
328346
329347 let end_headers = flags & FLAG_END_HEADERS != 0 ;
330348 if end_headers {
331349 stream. end_headers = true ;
332- let decoder = match direction {
333- H2Direction :: ClientToServer => & mut conn. client_decoder ,
334- H2Direction :: ServerToClient => & mut conn. server_decoder ,
335- } ;
336- decode_headers ( stream, decoder) ;
350+ if !stream. header_overflow {
351+ let decoder = match direction {
352+ H2Direction :: ClientToServer => & mut conn. client_decoder ,
353+ H2Direction :: ServerToClient => & mut conn. server_decoder ,
354+ } ;
355+ decode_headers ( stream, decoder) ;
356+ }
337357
338358 if stream. end_stream_headers {
339359 if let Some ( msg) = build_message ( stream) {
@@ -347,10 +367,11 @@ impl H2Tracker {
347367 // CONTINUATION for a discarded stream or PUSH_PROMISE —
348368 // accumulate fragments and decode HPACK when complete.
349369 let end_headers = flags & FLAG_END_HEADERS != 0 ;
350- if let Some ( ( discard_sid , discard_dir , ref mut buf ) ) =
351- conn . discard_header_buf
352- && discard_sid == stream_id
370+ // M3: Use HashMap to track multiple discarded streams
371+ if let Some ( ( discard_dir , buf ) ) =
372+ conn . discard_header_bufs . get_mut ( & stream_id)
353373 {
374+ let discard_dir = * discard_dir;
354375 if buf. len ( ) + frame_payload. len ( ) <= MAX_HEADER_BLOCK {
355376 buf. extend_from_slice ( & frame_payload) ;
356377 }
@@ -360,7 +381,7 @@ impl H2Tracker {
360381 H2Direction :: ServerToClient => & mut conn. server_decoder ,
361382 } ;
362383 let _ = decoder. decode ( buf) ;
363- conn. discard_header_buf = None ;
384+ conn. discard_header_bufs . remove ( & stream_id ) ;
364385 }
365386 continue ;
366387 }
@@ -449,7 +470,7 @@ impl H2Tracker {
449470 if header_block. len ( ) <= MAX_HEADER_BLOCK {
450471 buf. extend_from_slice ( header_block) ;
451472 }
452- conn. discard_header_buf = Some ( ( stream_id, direction, buf) ) ;
473+ conn. discard_header_bufs . insert ( stream_id, ( direction, buf) ) ;
453474 }
454475 }
455476 FRAME_SETTINGS => {
@@ -575,6 +596,7 @@ fn build_message(stream: &H2Stream) -> Option<HttpMessage> {
575596 kind,
576597 headers : stream. headers . clone ( ) ,
577598 body,
599+ smuggling_risk : false ,
578600 } )
579601}
580602
0 commit comments