@@ -69,6 +69,7 @@ struct SessionBuilderInner {
6969
7070 use_display_control : bool ,
7171 enable_credssp : bool ,
72+ outbound_message_size_limit : Option < u32 > ,
7273}
7374
7475impl Default for SessionBuilderInner {
@@ -97,6 +98,7 @@ impl Default for SessionBuilderInner {
9798
9899 use_display_control : false ,
99100 enable_credssp : true ,
101+ outbound_message_size_limit : None ,
100102 }
101103 }
102104}
@@ -219,6 +221,16 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder {
219221 |kdc_proxy_url: String | { self . 0 . borrow_mut( ) . kdc_proxy_url = Some ( kdc_proxy_url) } ;
220222 |display_control: bool | { self . 0 . borrow_mut( ) . use_display_control = display_control } ;
221223 |enable_credssp: bool | { self . 0 . borrow_mut( ) . enable_credssp = enable_credssp } ;
224+ |outbound_message_size_limit: f64 | {
225+ let limit = if outbound_message_size_limit >= 0.0 && outbound_message_size_limit <= f64 :: from( u32 :: MAX ) {
226+ #[ expect( clippy:: cast_possible_truncation, clippy:: cast_sign_loss) ]
227+ { outbound_message_size_limit as u32 }
228+ } else {
229+ warn!( outbound_message_size_limit, "Invalid outbound message size limit; fallback to unlimited" ) ;
230+ 0 // Fallback to no limit for invalid values.
231+ } ;
232+ self . 0 . borrow_mut( ) . outbound_message_size_limit = if limit > 0 { Some ( limit) } else { None } ;
233+ } ;
222234 }
223235
224236 self . clone ( )
@@ -242,6 +254,7 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder {
242254 remote_clipboard_changed_callback,
243255 remote_received_format_list_callback,
244256 force_clipboard_update_callback,
257+ outbound_message_size_limit,
245258 ) ;
246259
247260 {
@@ -271,6 +284,7 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder {
271284 remote_clipboard_changed_callback = inner. remote_clipboard_changed_callback . clone ( ) ;
272285 remote_received_format_list_callback = inner. remote_received_format_list_callback . clone ( ) ;
273286 force_clipboard_update_callback = inner. force_clipboard_update_callback . clone ( ) ;
287+ outbound_message_size_limit = inner. outbound_message_size_limit ;
274288 }
275289
276290 info ! ( "Connect to RDP host" ) ;
@@ -293,9 +307,9 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder {
293307 )
294308 } ) ;
295309
296- let ws = WebSocket :: open ( & proxy_address) . context ( "Couldn’ t open WebSocket" ) ?;
310+ let ws = WebSocket :: open ( & proxy_address) . context ( "couldn' t open WebSocket" ) ?;
297311
298- // NOTE: ideally, when the WebSocket can’ t be opened, the above call should fail with details on why is that
312+ // NOTE: ideally, when the WebSocket can' t be opened, the above call should fail with details on why is that
299313 // (e.g., the proxy hostname could not be resolved, proxy service is not running), but errors are neved
300314 // bubbled up in practice, so instead we poll the WebSocket state until we know its connected (i.e., the
301315 // WebSocket handshake is a success and user data can be exchanged).
@@ -339,7 +353,7 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder {
339353
340354 let ( writer_tx, writer_rx) = mpsc:: unbounded ( ) ;
341355
342- spawn_local ( writer_task ( writer_rx, rdp_writer) ) ;
356+ spawn_local ( writer_task ( writer_rx, rdp_writer, outbound_message_size_limit ) ) ;
343357
344358 Ok ( Session {
345359 desktop_size : connection_result. desktop_size ,
@@ -885,22 +899,39 @@ fn build_config(
885899 }
886900}
887901
888- async fn writer_task ( rx : mpsc:: UnboundedReceiver < Vec < u8 > > , rdp_writer : WriteHalf < WebSocket > ) {
902+ async fn writer_task (
903+ rx : mpsc:: UnboundedReceiver < Vec < u8 > > ,
904+ rdp_writer : WriteHalf < WebSocket > ,
905+ outbound_limit : Option < u32 > ,
906+ ) {
889907 debug ! ( "writer task started" ) ;
890908
891909 async fn inner (
892910 mut rx : mpsc:: UnboundedReceiver < Vec < u8 > > ,
893911 mut rdp_writer : WriteHalf < WebSocket > ,
912+ outbound_limit : Option < u32 > ,
894913 ) -> anyhow:: Result < ( ) > {
895914 while let Some ( frame) = rx. next ( ) . await {
896- rdp_writer. write_all ( & frame) . await . context ( "Couldn’t write frame" ) ?;
897- rdp_writer. flush ( ) . await . context ( "Couldn’t flush" ) ?;
915+ match outbound_limit {
916+ Some ( max_size) if frame. len ( ) > max_size as usize => {
917+ // Send in chunks.
918+ for chunk in frame. chunks ( max_size as usize ) {
919+ rdp_writer. write_all ( chunk) . await . context ( "couldn't write chunk" ) ?;
920+ rdp_writer. flush ( ) . await . context ( "couldn't flush chunk" ) ?;
921+ }
922+ }
923+ _ => {
924+ // Send complete frame (default case).
925+ rdp_writer. write_all ( & frame) . await . context ( "couldn't write frame" ) ?;
926+ rdp_writer. flush ( ) . await . context ( "couldn't flush frame" ) ?;
927+ }
928+ }
898929 }
899930
900931 Ok ( ( ) )
901932 }
902933
903- match inner ( rx, rdp_writer) . await {
934+ match inner ( rx, rdp_writer, outbound_limit ) . await {
904935 Ok ( ( ) ) => debug ! ( "writer task ended gracefully" ) ,
905936 Err ( e) => error ! ( "writer task ended unexpectedly: {e:#}" ) ,
906937 }
@@ -960,7 +991,7 @@ async fn connect(
960991 . ok ( )
961992 . map ( |url| KerberosConfig {
962993 kdc_proxy_url : Some ( url) ,
963- // HACK: It’ s supposed to be the computer name of the client, but since it’ s not easy to retrieve this information in the browser,
994+ // HACK: It' s supposed to be the computer name of the client, but since it' s not easy to retrieve this information in the browser,
964995 // we set the destination hostname instead because it happens to work.
965996 hostname : Some ( destination) ,
966997 } ) ,
@@ -1030,7 +1061,7 @@ where
10301061 framed
10311062 . write_all ( & rdcleanpath_req)
10321063 . await
1033- . context ( "couldn’ t write RDCleanPath request" ) ?;
1064+ . context ( "couldn' t write RDCleanPath request" ) ?;
10341065 }
10351066
10361067 {
0 commit comments