22//! JSON newline BTSP handshake relay via BearDog JSON-RPC.
33
44use std:: path:: PathBuf ;
5+ use std:: time:: Duration ;
56
67use base64:: Engine ;
78use serde:: Deserialize ;
@@ -10,6 +11,8 @@ use thiserror::Error;
1011use tokio:: io:: { AsyncRead , AsyncReadExt , AsyncWrite , AsyncWriteExt } ;
1112
1213use crate :: ToadStoolError ;
14+ use crate :: constants:: timeouts;
15+ use crate :: interned_strings:: socket_env;
1316use crate :: unix_jsonrpc:: UnixJsonRpcClient ;
1417
1518use super :: types:: BtspCipher ;
@@ -41,6 +44,10 @@ pub enum BtspJsonLineError {
4144 /// Protocol violation (version, missing field, etc.).
4245 #[ error( "BTSP JSON-line protocol: {0}" ) ]
4346 Protocol ( String ) ,
47+
48+ /// Handshake or RPC call exceeded its timeout budget.
49+ #[ error( "BTSP JSON-line timeout: {0}" ) ]
50+ Timeout ( String ) ,
4451}
4552
4653impl From < ToadStoolError > for BtspJsonLineError {
@@ -49,6 +56,20 @@ impl From<ToadStoolError> for BtspJsonLineError {
4956 }
5057}
5158
59+ fn handshake_timeout ( ) -> Duration {
60+ std:: env:: var ( socket_env:: BTSP_HANDSHAKE_TIMEOUT_SECS )
61+ . ok ( )
62+ . and_then ( |v| v. parse :: < u64 > ( ) . ok ( ) )
63+ . map_or ( timeouts:: BTSP_HANDSHAKE_TIMEOUT , Duration :: from_secs)
64+ }
65+
66+ fn rpc_timeout ( ) -> Duration {
67+ std:: env:: var ( socket_env:: BTSP_RPC_TIMEOUT_SECS )
68+ . ok ( )
69+ . and_then ( |v| v. parse :: < u64 > ( ) . ok ( ) )
70+ . map_or ( timeouts:: BTSP_RPC_TIMEOUT , Duration :: from_secs)
71+ }
72+
5273/// Check if a JSON line looks like a BTSP ClientHello.
5374///
5475/// The line must parse as JSON and carry `"protocol": "btsp"` (spacing-insensitive via serde).
@@ -199,12 +220,38 @@ async fn require_str_line<S: AsyncWrite + Unpin>(
199220/// 5. Call BearDog `btsp.session.verify` with session_token, response, client_ephemeral_pub, preferred_cipher
200221/// 6. Send HandshakeComplete JSON line
201222///
223+ /// The entire handshake is bounded by `BTSP_HANDSHAKE_TIMEOUT` (default 5s,
224+ /// override via `BTSP_HANDSHAKE_TIMEOUT_SECS`). Each BearDog RPC call is
225+ /// individually bounded by `BTSP_RPC_TIMEOUT` (default 3s, override via
226+ /// `BTSP_RPC_TIMEOUT_SECS`).
227+ ///
202228/// On error at any step, sends an error JSON line and returns `Err`.
203229pub async fn relay_json_line_handshake < S : AsyncRead + AsyncWrite + Unpin > (
204230 stream : & mut S ,
205231 first_line : & str ,
206232 family_seed : & str ,
207233 security_socket : & str ,
234+ ) -> Result < BtspSessionInfo , BtspJsonLineError > {
235+ let budget = handshake_timeout ( ) ;
236+ let Ok ( result) = tokio:: time:: timeout (
237+ budget,
238+ relay_json_line_handshake_inner ( stream, first_line, family_seed, security_socket) ,
239+ )
240+ . await
241+ else {
242+ let msg = format ! ( "BTSP handshake exceeded {budget:?} budget" ) ;
243+ tracing:: warn!( target: "btsp" , "{msg}" ) ;
244+ let _ = send_error_line ( stream, & msg) . await ;
245+ return Err ( BtspJsonLineError :: Timeout ( msg) ) ;
246+ } ;
247+ result
248+ }
249+
250+ async fn relay_json_line_handshake_inner < S : AsyncRead + AsyncWrite + Unpin > (
251+ stream : & mut S ,
252+ first_line : & str ,
253+ family_seed : & str ,
254+ security_socket : & str ,
208255) -> Result < BtspSessionInfo , BtspJsonLineError > {
209256 tracing:: info!( target: "btsp" , "JSON-line BTSP: parsing ClientHello" ) ;
210257
@@ -235,9 +282,13 @@ pub async fn relay_json_line_handshake<S: AsyncRead + AsyncWrite + Unpin>(
235282 tracing:: info!( target: "btsp" , "JSON-line BTSP: calling btsp.session.create" ) ;
236283
237284 let rpc = UnixJsonRpcClient :: new ( security_socket) ;
285+ let rpc_budget = rpc_timeout ( ) ;
238286 let family_seed_b64 = base64:: engine:: general_purpose:: STANDARD . encode ( family_seed. as_bytes ( ) ) ;
239287 let create_params = serde_json:: json!( { "family_seed" : family_seed_b64 } ) ;
240- let create_result: Value = match rpc. call ( "btsp.session.create" , create_params) . await {
288+ let create_result: Value = match rpc
289+ . call_with_timeout ( "btsp.session.create" , create_params, rpc_budget)
290+ . await
291+ {
241292 Ok ( v) => v,
242293 Err ( e) => {
243294 let msg = e. to_string ( ) ;
@@ -297,7 +348,10 @@ pub async fn relay_json_line_handshake<S: AsyncRead + AsyncWrite + Unpin>(
297348 "client_ephemeral_pub" : hello. client_ephemeral_pub,
298349 "preferred_cipher" : cr. preferred_cipher,
299350 } ) ;
300- let verify_result: Value = match rpc. call ( "btsp.session.verify" , verify_params) . await {
351+ let verify_result: Value = match rpc
352+ . call_with_timeout ( "btsp.session.verify" , verify_params, rpc_budget)
353+ . await
354+ {
301355 Ok ( v) => v,
302356 Err ( e) => {
303357 let msg = e. to_string ( ) ;
0 commit comments