From e07346e7125e80483b0215de1ed9f388a540fd66 Mon Sep 17 00:00:00 2001 From: Hussenet Thomas <113238884+LeJamon@users.noreply.github.com> Date: Sat, 30 May 2026 15:40:22 +0200 Subject: [PATCH] fix(#168): don't surface raw WS response body in send error When the oneshot request receiver was dropped, sender.send returned the undeliverable message and it was embedded verbatim into MessageChannelError, leaking the raw response body into error channels. Drop the returned message and make MessageChannelError a data-free variant. --- src/asynch/clients/websocket/exceptions.rs | 4 +- .../clients/websocket/websocket_base.rs | 38 ++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/asynch/clients/websocket/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs index 2d65beaf..f10600b7 100644 --- a/src/asynch/clients/websocket/exceptions.rs +++ b/src/asynch/clients/websocket/exceptions.rs @@ -38,8 +38,8 @@ pub enum XRPLWebSocketException { MissingRequestReceiver, #[error("Invalid message.")] InvalidMessage, - #[error("Failed to send message through channel: {0:?}")] - MessageChannelError(String), + #[error("Failed to send message through channel: the receiver was dropped")] + MessageChannelError, #[error("Failed to receive message through channel: {0:?}")] Canceled(#[from] Canceled), #[cfg(feature = "std")] diff --git a/src/asynch/clients/websocket/websocket_base.rs b/src/asynch/clients/websocket/websocket_base.rs index 84974c99..fbb525e4 100644 --- a/src/asynch/clients/websocket/websocket_base.rs +++ b/src/asynch/clients/websocket/websocket_base.rs @@ -77,9 +77,12 @@ where Some(sender) => sender, None => return Err(XRPLWebSocketException::MissingRequestSender.into()), }; + // On failure `send` hands back the message that could not be + // delivered; discard it so the raw response body is not surfaced + // into error-reporting channels. sender .send(message) - .map_err(XRPLWebSocketException::MessageChannelError)?; + .map_err(|_| XRPLWebSocketException::MessageChannelError)?; } else { self.messages.send(message).await; } @@ -106,3 +109,36 @@ where } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::asynch::clients::SingleExecutorMutex; + use embassy_futures::block_on; + + #[test] + fn handle_message_routes_response_to_waiting_request() { + let mut base = WebsocketBase::::new(); + block_on(async { + base.setup_request_future("1".to_string()).await; + base.handle_message(r#"{"id":"1","result":{}}"#.to_string()) + .await + .unwrap(); + let received = base.try_recv_request("1".to_string()).await.unwrap(); + assert_eq!(received, Some(r#"{"id":"1","result":{}}"#.to_string())); + }); + } + + #[test] + fn unmatched_message_is_buffered_for_pop() { + let mut base = WebsocketBase::::new(); + block_on(async { + // No request is registered, so the message falls through to the + // general message channel rather than a request receiver. + base.handle_message(r#"{"result":{}}"#.to_string()) + .await + .unwrap(); + assert_eq!(base.pop_message().await, r#"{"result":{}}"#.to_string()); + }); + } +}