@@ -71,9 +71,28 @@ pub enum ClientConnectorState {
7171 user_channel_id : u16 ,
7272 license_exchange : LicenseExchangeSequence ,
7373 } ,
74+ /// Reading the server's optional Initiate Multitransport Request PDU(s).
75+ ///
76+ /// The server may send 0, 1, or 2 requests (one per transport protocol).
77+ /// If the first PDU on the IO channel after licensing is a Demand Active
78+ /// (capabilities exchange), the server sent no multitransport requests and
79+ /// the connector transitions directly to `CapabilitiesExchange`.
7480 MultitransportBootstrapping {
7581 io_channel_id : u16 ,
7682 user_channel_id : u16 ,
83+ /// Multitransport requests received from the server so far.
84+ requests : Vec < rdp:: multitransport:: MultitransportRequestPdu > ,
85+ } ,
86+ /// The server sent multitransport request(s) and the connector is paused
87+ /// waiting for the application to establish UDP transport or decline.
88+ ///
89+ /// The buffered Demand Active PDU must be re-fed to the connector after
90+ /// the application calls [`ClientConnector::complete_multitransport()`] or
91+ /// [`ClientConnector::skip_multitransport()`].
92+ MultitransportPending {
93+ io_channel_id : u16 ,
94+ user_channel_id : u16 ,
95+ requests : Vec < rdp:: multitransport:: MultitransportRequestPdu > ,
7796 } ,
7897 CapabilitiesExchange {
7998 connection_activation : ConnectionActivationSequence ,
@@ -101,6 +120,7 @@ impl State for ClientConnectorState {
101120 Self :: ConnectTimeAutoDetection { .. } => "ConnectTimeAutoDetection" ,
102121 Self :: LicensingExchange { .. } => "LicensingExchange" ,
103122 Self :: MultitransportBootstrapping { .. } => "MultitransportBootstrapping" ,
123+ Self :: MultitransportPending { .. } => "MultitransportPending" ,
104124 Self :: CapabilitiesExchange {
105125 connection_activation, ..
106126 } => connection_activation. state ( ) . name ( ) ,
@@ -200,6 +220,117 @@ impl ClientConnector {
200220 debug_assert ! ( !self . should_perform_credssp( ) ) ;
201221 assert_eq ! ( res, Written :: Nothing ) ;
202222 }
223+
224+ /// Returns `true` when the connector has collected all multitransport
225+ /// requests from the server and is waiting for the application to either
226+ /// establish the UDP transport(s) or decline them.
227+ ///
228+ /// The application should:
229+ ///
230+ /// 1. Call [`multitransport_requests()`](Self::multitransport_requests) to
231+ /// get the server's request(s)
232+ /// 2. Establish UDP transport (RDPEUDP2 + TLS + RDPEMT) for each, or decide
233+ /// not to
234+ /// 3. Call [`complete_multitransport()`](Self::complete_multitransport) with
235+ /// the response for each request
236+ pub fn should_perform_multitransport ( & self ) -> bool {
237+ matches ! ( self . state, ClientConnectorState :: MultitransportPending { .. } )
238+ }
239+
240+ /// Returns the multitransport request PDUs received from the server.
241+ ///
242+ /// Only meaningful when
243+ /// [`should_perform_multitransport()`](Self::should_perform_multitransport)
244+ /// returns `true`.
245+ pub fn multitransport_requests ( & self ) -> & [ rdp:: multitransport:: MultitransportRequestPdu ] {
246+ match & self . state {
247+ ClientConnectorState :: MultitransportPending { requests, .. } => requests,
248+ _ => & [ ] ,
249+ }
250+ }
251+
252+ /// Send multitransport response PDU(s) and advance past the bootstrapping
253+ /// phase to capabilities exchange.
254+ ///
255+ /// Pass one response per request. Use
256+ /// [`MultitransportResponsePdu::success()`] or
257+ /// [`MultitransportResponsePdu::failure()`] to construct responses.
258+ ///
259+ /// After calling this, re-feed the buffered Demand Active PDU to the
260+ /// connector (via `step()`).
261+ ///
262+ /// # Panics
263+ ///
264+ /// Panics if state is not [`ClientConnectorState::MultitransportPending`].
265+ pub fn complete_multitransport (
266+ & mut self ,
267+ responses : & [ rdp:: multitransport:: MultitransportResponsePdu ] ,
268+ output : & mut WriteBuf ,
269+ ) -> ConnectorResult < Written > {
270+ assert ! ( self . should_perform_multitransport( ) ) ;
271+
272+ let ClientConnectorState :: MultitransportPending {
273+ io_channel_id,
274+ user_channel_id,
275+ ..
276+ } = self . state
277+ else {
278+ unreachable ! ( )
279+ } ;
280+
281+ let mut total_written = 0 ;
282+
283+ for response in responses {
284+ let written = encode_send_data_request ( user_channel_id, io_channel_id, response, output) ?;
285+ total_written += written;
286+ }
287+
288+ self . state = ClientConnectorState :: CapabilitiesExchange {
289+ connection_activation : ConnectionActivationSequence :: new (
290+ self . config . clone ( ) ,
291+ io_channel_id,
292+ user_channel_id,
293+ ) ,
294+ } ;
295+
296+ if total_written > 0 {
297+ Written :: from_size ( total_written)
298+ } else {
299+ Ok ( Written :: Nothing )
300+ }
301+ }
302+
303+ /// Skip multitransport bootstrapping without sending any responses.
304+ ///
305+ /// Use this when the application doesn't support or doesn't want UDP
306+ /// transport. The server will continue with TCP-only operation.
307+ ///
308+ /// After calling this, re-feed the buffered Demand Active PDU to the
309+ /// connector (via `step()`).
310+ ///
311+ /// # Panics
312+ ///
313+ /// Panics if state is not [`ClientConnectorState::MultitransportPending`].
314+ pub fn skip_multitransport ( & mut self ) {
315+ assert ! ( self . should_perform_multitransport( ) ) ;
316+
317+ let ClientConnectorState :: MultitransportPending {
318+ io_channel_id,
319+ user_channel_id,
320+ ..
321+ } = self . state
322+ else {
323+ unreachable ! ( )
324+ } ;
325+
326+ self . state = ClientConnectorState :: CapabilitiesExchange {
327+ connection_activation : ConnectionActivationSequence :: new (
328+ self . config . clone ( ) ,
329+ io_channel_id,
330+ user_channel_id,
331+ ) ,
332+ } ;
333+ }
203334}
204335
205336impl Sequence for ClientConnector {
@@ -216,7 +347,8 @@ impl Sequence for ClientConnector {
216347 ClientConnectorState :: SecureSettingsExchange { .. } => None ,
217348 ClientConnectorState :: ConnectTimeAutoDetection { .. } => None ,
218349 ClientConnectorState :: LicensingExchange { license_exchange, .. } => license_exchange. next_pdu_hint ( ) ,
219- ClientConnectorState :: MultitransportBootstrapping { .. } => None ,
350+ ClientConnectorState :: MultitransportBootstrapping { .. } => Some ( & ironrdp_pdu:: X224_HINT ) ,
351+ ClientConnectorState :: MultitransportPending { .. } => None ,
220352 ClientConnectorState :: CapabilitiesExchange {
221353 connection_activation, ..
222354 } => connection_activation. next_pdu_hint ( ) ,
@@ -521,6 +653,7 @@ impl Sequence for ClientConnector {
521653 ClientConnectorState :: MultitransportBootstrapping {
522654 io_channel_id,
523655 user_channel_id,
656+ requests : Vec :: new ( ) ,
524657 }
525658 } else {
526659 ClientConnectorState :: LicensingExchange {
@@ -534,20 +667,99 @@ impl Sequence for ClientConnector {
534667 }
535668
536669 //== Optional Multitransport Bootstrapping ==//
537- // NOTE: our implementation is not expecting the Auto-Detect Request PDU from server
670+ //
671+ // The server may send 0, 1, or 2 Initiate Multitransport Request PDUs
672+ // after licensing. These use BasicSecurityHeader (SEC_TRANSPORT_REQ)
673+ // rather than ShareControlHeader, so we peek at the first 2 bytes to
674+ // distinguish them from the Demand Active PDU that starts capabilities
675+ // exchange.
538676 ClientConnectorState :: MultitransportBootstrapping {
539677 io_channel_id,
540678 user_channel_id,
541- } => (
542- Written :: Nothing ,
543- ClientConnectorState :: CapabilitiesExchange {
544- connection_activation : ConnectionActivationSequence :: new (
545- self . config . clone ( ) ,
546- io_channel_id,
547- user_channel_id,
548- ) ,
549- } ,
550- ) ,
679+ mut requests,
680+ } => {
681+ use ironrdp_pdu:: rdp:: headers:: BasicSecurityHeaderFlags ;
682+
683+ let ctx = crate :: legacy:: decode_send_data_indication ( input) ?;
684+
685+ // Peek at the BasicSecurityHeader flags field (first 2 bytes of
686+ // user_data) to check for SEC_TRANSPORT_REQ
687+ let is_multitransport = ctx. user_data . len ( ) >= 2 && {
688+ let flags_raw = u16:: from_le_bytes ( [ ctx. user_data [ 0 ] , ctx. user_data [ 1 ] ] ) ;
689+ BasicSecurityHeaderFlags :: from_bits ( flags_raw)
690+ . is_some_and ( |flags| flags. contains ( BasicSecurityHeaderFlags :: TRANSPORT_REQ ) )
691+ } ;
692+
693+ if is_multitransport {
694+ let pdu = decode :: < rdp:: multitransport:: MultitransportRequestPdu > ( ctx. user_data )
695+ . map_err ( ConnectorError :: decode) ?;
696+
697+ debug ! (
698+ request_id = pdu. request_id,
699+ protocol = ?pdu. requested_protocol,
700+ "Received Initiate Multitransport Request"
701+ ) ;
702+
703+ requests. push ( pdu) ;
704+
705+ // Stay in this state to read more requests (server may send a second)
706+ (
707+ Written :: Nothing ,
708+ ClientConnectorState :: MultitransportBootstrapping {
709+ io_channel_id,
710+ user_channel_id,
711+ requests,
712+ } ,
713+ )
714+ } else if !requests. is_empty ( ) {
715+ // Server is done sending multitransport requests. Transition
716+ // to MultitransportPending so the application can establish
717+ // UDP transport(s) via should_perform_multitransport() /
718+ // complete_multitransport().
719+ //
720+ // The Demand Active PDU that triggered this branch must be
721+ // re-fed to the connector after the application calls
722+ // complete_multitransport() or skip_multitransport().
723+ info ! (
724+ count = requests. len( ) ,
725+ "Multitransport bootstrapping: pausing for application"
726+ ) ;
727+
728+ (
729+ Written :: Nothing ,
730+ ClientConnectorState :: MultitransportPending {
731+ io_channel_id,
732+ user_channel_id,
733+ requests,
734+ } ,
735+ )
736+ } else {
737+ // No multitransport requests — server went straight to
738+ // capabilities exchange. Forward the PDU.
739+ let mut connection_activation =
740+ ConnectionActivationSequence :: new ( self . config . clone ( ) , io_channel_id, user_channel_id) ;
741+ let written = connection_activation. step ( input, output) ?;
742+
743+ match connection_activation. connection_activation_state ( ) {
744+ ConnectionActivationState :: ConnectionFinalization { .. } => (
745+ written,
746+ ClientConnectorState :: ConnectionFinalization { connection_activation } ,
747+ ) ,
748+ _ => (
749+ written,
750+ ClientConnectorState :: CapabilitiesExchange { connection_activation } ,
751+ ) ,
752+ }
753+ }
754+ }
755+
756+ // MultitransportPending: application should call complete_multitransport()
757+ // or skip_multitransport() instead of step()
758+ ClientConnectorState :: MultitransportPending { .. } => {
759+ return Err ( general_err ! (
760+ "multitransport pending: call complete_multitransport() or skip_multitransport()"
761+ ) )
762+ }
551763
552764 //== Capabilities Exchange ==/
553765 // The server sends the set of capabilities it supports to the client.
0 commit comments