@@ -243,6 +243,149 @@ mod client_side_driver {
243243 assert_eq ! ( helper. driver. stream_map. len( ) , 0 ) ;
244244 }
245245
246+ /// An idle connection (and a request/response that exchanges only
247+ /// headers) must never allocate the body receive buffer.
248+ #[ test]
249+ fn client_body_recv_buf_not_allocated_when_idle ( ) {
250+ let mut helper = DriverTestHelper :: < ClientHooks > :: new ( ) . unwrap ( ) ;
251+ helper. complete_handshake ( ) . unwrap ( ) ;
252+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
253+
254+ // Idle: never received body bytes, so the buffer is not allocated.
255+ assert ! ( helper. driver. body_recv_buf. is_none( ) ) ;
256+
257+ // client sends a request with fin (no request body)
258+ let stream_id = helper
259+ . driver_send_request ( make_request_headers ( "GET" ) , true )
260+ . unwrap ( ) ;
261+
262+ // server reads request and sends response headers (no body)
263+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
264+ assert_matches ! (
265+ helper. peer_server_poll( ) . unwrap( ) ,
266+ ( 0 , h3:: Event :: Headers { .. } )
267+ ) ;
268+ helper. peer_server_send_response ( 0 , false ) . unwrap ( ) ;
269+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
270+
271+ // Client receives response headers
272+ let resp = assert_matches ! (
273+ helper. driver_recv_core_event( ) . unwrap( ) ,
274+ H3Event :: IncomingHeaders ( headers) => { headers }
275+ ) ;
276+ assert_eq ! ( resp. stream_id, stream_id) ;
277+
278+ // Only headers have been exchanged; still no body buffer.
279+ assert ! ( helper. driver. body_recv_buf. is_none( ) ) ;
280+ }
281+
282+ /// The body receive buffer is lazily allocated on the first body read
283+ /// and released once the last stream is cleaned up.
284+ #[ test]
285+ fn client_body_recv_buf_allocated_on_body_and_released_on_close ( ) {
286+ let mut helper = DriverTestHelper :: < ClientHooks > :: new ( ) . unwrap ( ) ;
287+ helper. complete_handshake ( ) . unwrap ( ) ;
288+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
289+
290+ let stream_id = helper
291+ . driver_send_request ( make_request_headers ( "GET" ) , true )
292+ . unwrap ( ) ;
293+
294+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
295+ assert_matches ! (
296+ helper. peer_server_poll( ) . unwrap( ) ,
297+ ( 0 , h3:: Event :: Headers { .. } )
298+ ) ;
299+ helper. peer_server_send_response ( 0 , false ) . unwrap ( ) ;
300+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
301+
302+ let resp = assert_matches ! (
303+ helper. driver_recv_core_event( ) . unwrap( ) ,
304+ H3Event :: IncomingHeaders ( headers) => { headers }
305+ ) ;
306+ assert_eq ! ( resp. stream_id, stream_id) ;
307+ let mut from_server = resp. recv ;
308+
309+ // No body yet: the buffer is still unallocated.
310+ assert ! ( helper. driver. body_recv_buf. is_none( ) ) ;
311+
312+ // Server sends a body chunk (not fin).
313+ helper. peer_server_send_body ( 0 , & [ 7 ; 10 ] , false ) . unwrap ( ) ;
314+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
315+ assert_eq ! ( helper. driver_try_recv_body( & mut from_server) . 0 , vec![ 7 ; 10 ] ) ;
316+
317+ // The first body read lazily allocated the buffer.
318+ assert ! ( helper. driver. body_recv_buf. is_some( ) ) ;
319+
320+ // Server finishes the stream.
321+ helper. peer_server_send_body ( 0 , & [ 8 ; 10 ] , true ) . unwrap ( ) ;
322+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
323+ let ( body, fin, _) = helper. driver_try_recv_body ( & mut from_server) ;
324+ assert_eq ! ( body, vec![ 8 ; 10 ] ) ;
325+ assert ! ( fin) ;
326+
327+ // Stream cleaned up on both-directions-close, buffer released.
328+ assert_eq ! ( helper. driver. stream_map. len( ) , 0 ) ;
329+ assert ! ( helper. driver. body_recv_buf. is_none( ) ) ;
330+ }
331+
332+ /// A body larger than the receive buffer exercises the reallocation
333+ /// branch; the buffer stays allocated across reallocations and is
334+ /// released once the stream closes.
335+ #[ test]
336+ fn client_body_recv_buf_reallocates_and_releases ( ) {
337+ let mut helper = DriverTestHelper :: < ClientHooks > :: new ( ) . unwrap ( ) ;
338+ helper. complete_handshake ( ) . unwrap ( ) ;
339+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
340+
341+ let stream_id = helper
342+ . driver_send_request ( make_request_headers ( "GET" ) , true )
343+ . unwrap ( ) ;
344+
345+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
346+ assert_matches ! (
347+ helper. peer_server_poll( ) . unwrap( ) ,
348+ ( 0 , h3:: Event :: Headers { .. } )
349+ ) ;
350+ helper. peer_server_send_response ( 0 , false ) . unwrap ( ) ;
351+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
352+
353+ let resp = assert_matches ! (
354+ helper. driver_recv_core_event( ) . unwrap( ) ,
355+ H3Event :: IncomingHeaders ( headers) => { headers }
356+ ) ;
357+ assert_eq ! ( resp. stream_id, stream_id) ;
358+ let mut from_server = resp. recv ;
359+
360+ // Force a small receive buffer so the body exhausts it and the
361+ // reallocation branch (`*body_recv_buf = ...`) runs.
362+ helper. driver_set_body_buf_size ( 20 ) ;
363+
364+ // Send 40 bytes across the 20-byte buffer, exhausting it repeatedly.
365+ helper. peer_server_send_body ( 0 , & [ 1 ; 10 ] , false ) . unwrap ( ) ;
366+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
367+ assert_eq ! ( helper. driver_try_recv_body( & mut from_server) . 0 , vec![ 1 ; 10 ] ) ;
368+ helper. peer_server_send_body ( 0 , & [ 2 ; 10 ] , false ) . unwrap ( ) ;
369+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
370+ assert_eq ! ( helper. driver_try_recv_body( & mut from_server) . 0 , vec![ 2 ; 10 ] ) ;
371+ helper. peer_server_send_body ( 0 , & [ 3 ; 10 ] , false ) . unwrap ( ) ;
372+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
373+ assert_eq ! ( helper. driver_try_recv_body( & mut from_server) . 0 , vec![ 3 ; 10 ] ) ;
374+ // Buffer remains allocated across reallocation.
375+ assert ! ( helper. driver. body_recv_buf. is_some( ) ) ;
376+
377+ // Final chunk with fin.
378+ helper. peer_server_send_body ( 0 , & [ 4 ; 10 ] , true ) . unwrap ( ) ;
379+ helper. advance_and_run_loop ( ) . unwrap ( ) ;
380+ let ( body, fin, _) = helper. driver_try_recv_body ( & mut from_server) ;
381+ assert_eq ! ( body, vec![ 4 ; 10 ] ) ;
382+ assert ! ( fin) ;
383+
384+ // Stream cleaned up, buffer released.
385+ assert_eq ! ( helper. driver. stream_map. len( ) , 0 ) ;
386+ assert ! ( helper. driver. body_recv_buf. is_none( ) ) ;
387+ }
388+
246389 /// Test that dropping the OutboundFrame channel causes the driver to
247390 /// send a RESET_STREAM frame to the peer.
248391 #[ test]
0 commit comments