@@ -2276,3 +2276,192 @@ async fn test_fd_batching_many_fds_small_batches() -> Result<()> {
22762276 server_handle. abort ( ) ;
22772277 Ok ( ( ) )
22782278}
2279+
2280+ /// Test that the receiver correctly waits for batched FDs from the server.
2281+ ///
2282+ /// When the server responds with many FDs using a small batch size, the
2283+ /// receiver may parse the JSON message before all FDs have arrived. The
2284+ /// receiver must buffer the parsed message and keep reading until enough
2285+ /// FDs are available, rather than returning a MismatchedCount error.
2286+ #[ tokio:: test]
2287+ async fn test_receiver_waits_for_batched_response_fds ( ) -> Result < ( ) > {
2288+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
2289+ let socket_path = temp_dir. path ( ) . join ( "test_receiver_batch.sock" ) ;
2290+
2291+ let num_fds = 5 ;
2292+
2293+ let listener = tokio:: net:: UnixListener :: bind ( & socket_path) . unwrap ( ) ;
2294+
2295+ let server_handle = tokio:: spawn ( async move {
2296+ let ( stream, _) = listener. accept ( ) . await . unwrap ( ) ;
2297+ let transport = UnixSocketTransport :: new ( stream) ;
2298+ let ( mut sender, mut receiver) = transport. split ( ) ;
2299+
2300+ // Force small batches on the server side so the client's
2301+ // receiver sees FDs arriving across multiple recvmsg() calls.
2302+ sender. set_max_fds_per_sendmsg ( NonZeroUsize :: new ( 1 ) . unwrap ( ) ) ;
2303+
2304+ // Read the request.
2305+ let request = receiver. receive ( ) . await . unwrap ( ) ;
2306+ assert ! ( request. file_descriptors. is_empty( ) ) ;
2307+
2308+ // Build a response with many FDs.
2309+ let mut fds: Vec < OwnedFd > = Vec :: new ( ) ;
2310+ for i in 0 ..num_fds {
2311+ let mut temp_file = NamedTempFile :: new ( ) . unwrap ( ) ;
2312+ write ! ( temp_file, "response file {i}" ) . unwrap ( ) ;
2313+ temp_file. flush ( ) . unwrap ( ) ;
2314+ temp_file. seek ( SeekFrom :: Start ( 0 ) ) . unwrap ( ) ;
2315+ fds. push ( temp_file. into_file ( ) . into ( ) ) ;
2316+ }
2317+
2318+ let response = jsonrpc_fdpass:: JsonRpcResponse :: success (
2319+ Value :: String ( "here are your files" . to_string ( ) ) ,
2320+ Value :: Number ( 1 . into ( ) ) ,
2321+ ) ;
2322+ let msg = MessageWithFds :: new ( JsonRpcMessage :: Response ( response) , fds) ;
2323+ sender. send ( msg) . await . unwrap ( ) ;
2324+ } ) ;
2325+
2326+ // Client side: send request, receive response with batched FDs.
2327+ let stream = tokio:: net:: UnixStream :: connect ( & socket_path) . await . unwrap ( ) ;
2328+ let transport = UnixSocketTransport :: new ( stream) ;
2329+ let ( mut sender, mut receiver) = transport. split ( ) ;
2330+
2331+ let request = JsonRpcRequest :: new ( "get_files" . to_string ( ) , None , Value :: Number ( 1 . into ( ) ) ) ;
2332+ sender
2333+ . send ( MessageWithFds :: new (
2334+ JsonRpcMessage :: Request ( request) ,
2335+ Vec :: new ( ) ,
2336+ ) )
2337+ . await ?;
2338+
2339+ // This is the critical part: the receiver must wait for all FDs
2340+ // instead of failing with MismatchedCount.
2341+ let response = receiver. receive ( ) . await ?;
2342+ assert_eq ! (
2343+ response. file_descriptors. len( ) ,
2344+ num_fds,
2345+ "Expected {num_fds} FDs in batched response"
2346+ ) ;
2347+
2348+ // Verify FD contents are correct and in order.
2349+ for ( i, fd) in response. file_descriptors . into_iter ( ) . enumerate ( ) {
2350+ let mut file = File :: from ( fd) ;
2351+ let mut contents = String :: new ( ) ;
2352+ file. seek ( SeekFrom :: Start ( 0 ) ) . unwrap ( ) ;
2353+ file. read_to_string ( & mut contents) . unwrap ( ) ;
2354+ assert_eq ! ( contents, format!( "response file {i}" ) ) ;
2355+ }
2356+
2357+ server_handle. await . unwrap ( ) ;
2358+ Ok ( ( ) )
2359+ }
2360+
2361+ /// Test that the receiver returns MismatchedCount when the sender starts a new
2362+ /// message before delivering all FDs for the current one (protocol violation).
2363+ #[ tokio:: test]
2364+ async fn test_receiver_errors_on_next_message_before_fds ( ) -> Result < ( ) > {
2365+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
2366+ let socket_path = temp_dir. path ( ) . join ( "test_next_msg_violation.sock" ) ;
2367+
2368+ let listener = tokio:: net:: UnixListener :: bind ( & socket_path) . unwrap ( ) ;
2369+
2370+ let server_handle = tokio:: spawn ( async move {
2371+ let ( stream, _) = listener. accept ( ) . await . unwrap ( ) ;
2372+ let transport = UnixSocketTransport :: new ( stream) ;
2373+ let ( _sender, mut receiver) = transport. split ( ) ;
2374+
2375+ // The client will claim fds but send a second message before
2376+ // delivering them. We expect a MismatchedCount error.
2377+ match receiver. receive ( ) . await {
2378+ Err ( jsonrpc_fdpass:: Error :: MismatchedCount { expected, found } ) => {
2379+ assert_eq ! ( expected, 2 ) ;
2380+ assert_eq ! ( found, 0 ) ;
2381+ }
2382+ Err ( e) => panic ! ( "Expected MismatchedCount, got: {e:?}" ) ,
2383+ Ok ( _) => panic ! ( "Should have failed with MismatchedCount" ) ,
2384+ }
2385+ } ) ;
2386+
2387+ // Connect and send a message claiming 2 FDs, then immediately send
2388+ // a second message without delivering any FDs.
2389+ let stream = tokio:: net:: UnixStream :: connect ( & socket_path) . await . unwrap ( ) ;
2390+
2391+ use tokio:: io:: AsyncWriteExt ;
2392+ let mut stream = stream;
2393+
2394+ let first = serde_json:: json!( {
2395+ "jsonrpc" : "2.0" ,
2396+ "method" : "need_fds" ,
2397+ "params" : { } ,
2398+ "id" : 1 ,
2399+ "fds" : 2
2400+ } ) ;
2401+ let second = serde_json:: json!( {
2402+ "jsonrpc" : "2.0" ,
2403+ "method" : "violation" ,
2404+ "params" : { } ,
2405+ "id" : 2
2406+ } ) ;
2407+
2408+ // Send both messages back-to-back without any FDs.
2409+ let mut payload = serde_json:: to_vec ( & first) . unwrap ( ) ;
2410+ payload. extend_from_slice ( & serde_json:: to_vec ( & second) . unwrap ( ) ) ;
2411+ stream. write_all ( & payload) . await . unwrap ( ) ;
2412+ stream. flush ( ) . await . unwrap ( ) ;
2413+
2414+ server_handle. await . unwrap ( ) ;
2415+ Ok ( ( ) )
2416+ }
2417+
2418+ /// Test that the receiver returns MismatchedCount when the connection is
2419+ /// closed while waiting for batched FDs.
2420+ #[ tokio:: test]
2421+ async fn test_receiver_errors_on_close_while_pending ( ) -> Result < ( ) > {
2422+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
2423+ let socket_path = temp_dir. path ( ) . join ( "test_close_pending.sock" ) ;
2424+
2425+ let listener = tokio:: net:: UnixListener :: bind ( & socket_path) . unwrap ( ) ;
2426+
2427+ let server_handle = tokio:: spawn ( async move {
2428+ let ( stream, _) = listener. accept ( ) . await . unwrap ( ) ;
2429+ let transport = UnixSocketTransport :: new ( stream) ;
2430+ let ( _sender, mut receiver) = transport. split ( ) ;
2431+
2432+ match receiver. receive ( ) . await {
2433+ Err ( jsonrpc_fdpass:: Error :: MismatchedCount { expected, found } ) => {
2434+ assert_eq ! ( expected, 3 ) ;
2435+ assert_eq ! ( found, 0 ) ;
2436+ }
2437+ Err ( e) => panic ! ( "Expected MismatchedCount, got: {e:?}" ) ,
2438+ Ok ( _) => panic ! ( "Should have failed with MismatchedCount" ) ,
2439+ }
2440+ } ) ;
2441+
2442+ // Connect, send a message claiming 3 FDs, then drop the connection.
2443+ let stream = tokio:: net:: UnixStream :: connect ( & socket_path) . await . unwrap ( ) ;
2444+
2445+ use tokio:: io:: AsyncWriteExt ;
2446+ let mut stream = stream;
2447+
2448+ let msg = serde_json:: json!( {
2449+ "jsonrpc" : "2.0" ,
2450+ "method" : "test" ,
2451+ "params" : { } ,
2452+ "id" : 1 ,
2453+ "fds" : 3
2454+ } ) ;
2455+
2456+ stream
2457+ . write_all ( & serde_json:: to_vec ( & msg) . unwrap ( ) )
2458+ . await
2459+ . unwrap ( ) ;
2460+ stream. flush ( ) . await . unwrap ( ) ;
2461+
2462+ // Close the connection without sending any FDs.
2463+ drop ( stream) ;
2464+
2465+ server_handle. await . unwrap ( ) ;
2466+ Ok ( ( ) )
2467+ }
0 commit comments