@@ -17,6 +17,20 @@ use crate::{
1717 NegotiationFailure , Sequence , State , Written , encode_x224_packet, general_err, reason_err,
1818} ;
1919
20+ /// Outcome of a single multitransport bootstrapping request, passed to
21+ /// [`ClientConnector::complete_multitransport()`].
22+ ///
23+ /// The connector uses this to build the response PDU internally, paired with
24+ /// the request ID and cookie from the server's original request.
25+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
26+ pub enum MultitransportResult {
27+ /// UDP transport was established successfully (`S_OK`).
28+ Success ,
29+ /// UDP transport failed. The `u32` is the HRESULT error code (typically
30+ /// [`MultitransportResponsePdu::E_ABORT`](rdp::multitransport::MultitransportResponsePdu::E_ABORT)).
31+ Failure ( u32 ) ,
32+ }
33+
2034#[ derive( Debug ) ]
2135pub struct ConnectionResult {
2236 pub io_channel_id : u16 ,
@@ -70,9 +84,32 @@ pub enum ClientConnectorState {
7084 user_channel_id : u16 ,
7185 license_exchange : LicenseExchangeSequence ,
7286 } ,
87+ /// Reading the server's optional Initiate Multitransport Request PDU(s).
88+ ///
89+ /// The server may send 0, 1, or 2 requests (one per transport protocol).
90+ /// If the first PDU on the IO channel after licensing is a Demand Active
91+ /// (capabilities exchange), the server sent no multitransport requests and
92+ /// the connector transitions directly to `CapabilitiesExchange`.
7393 MultitransportBootstrapping {
7494 io_channel_id : u16 ,
7595 user_channel_id : u16 ,
96+ /// Multitransport requests received from the server so far.
97+ requests : Vec < rdp:: multitransport:: MultitransportRequestPdu > ,
98+ } ,
99+ /// The server sent multitransport request(s) and the connector is paused
100+ /// waiting for the application to establish UDP transport or decline.
101+ ///
102+ /// Call [`ClientConnector::complete_multitransport()`] or
103+ /// [`ClientConnector::skip_multitransport()`] to advance. The buffered
104+ /// Demand Active PDU is replayed internally — no re-feeding needed.
105+ MultitransportPending {
106+ io_channel_id : u16 ,
107+ user_channel_id : u16 ,
108+ requests : Vec < rdp:: multitransport:: MultitransportRequestPdu > ,
109+ /// The raw Demand Active PDU bytes that arrived after the last
110+ /// multitransport request. Replayed through the activation sequence
111+ /// when the application completes or skips multitransport.
112+ buffered_demand_active : Vec < u8 > ,
76113 } ,
77114 CapabilitiesExchange {
78115 connection_activation : ConnectionActivationSequence ,
@@ -100,6 +137,7 @@ impl State for ClientConnectorState {
100137 Self :: ConnectTimeAutoDetection { .. } => "ConnectTimeAutoDetection" ,
101138 Self :: LicensingExchange { .. } => "LicensingExchange" ,
102139 Self :: MultitransportBootstrapping { .. } => "MultitransportBootstrapping" ,
140+ Self :: MultitransportPending { .. } => "MultitransportPending" ,
103141 Self :: CapabilitiesExchange {
104142 connection_activation, ..
105143 } => connection_activation. state ( ) . name ( ) ,
@@ -198,6 +236,141 @@ impl ClientConnector {
198236 debug_assert ! ( !self . should_perform_credssp( ) ) ;
199237 assert_eq ! ( res, Written :: Nothing ) ;
200238 }
239+
240+ /// Returns `true` when the connector has collected all multitransport
241+ /// requests from the server and is waiting for the application to either
242+ /// establish the UDP transport(s) or decline them.
243+ ///
244+ /// The application should:
245+ ///
246+ /// 1. Call [`multitransport_requests()`](Self::multitransport_requests) to
247+ /// get the server's request(s)
248+ /// 2. Establish UDP transport (RDPEUDP2 + TLS + RDPEMT) for each, or decide
249+ /// not to
250+ /// 3. Call [`complete_multitransport()`](Self::complete_multitransport) with
251+ /// a [`MultitransportResult`] for each request, or
252+ /// [`skip_multitransport()`](Self::skip_multitransport) to decline all
253+ pub fn should_perform_multitransport ( & self ) -> bool {
254+ matches ! ( self . state, ClientConnectorState :: MultitransportPending { .. } )
255+ }
256+
257+ /// Returns the multitransport request PDUs received from the server.
258+ ///
259+ /// Only meaningful when
260+ /// [`should_perform_multitransport()`](Self::should_perform_multitransport)
261+ /// returns `true`.
262+ pub fn multitransport_requests ( & self ) -> & [ rdp:: multitransport:: MultitransportRequestPdu ] {
263+ match & self . state {
264+ ClientConnectorState :: MultitransportPending { requests, .. } => requests,
265+ _ => & [ ] ,
266+ }
267+ }
268+
269+ /// Send multitransport response PDU(s) and advance past the bootstrapping
270+ /// phase to capabilities exchange.
271+ ///
272+ /// Pass one [`MultitransportResult`] per request (in the same order as
273+ /// [`multitransport_requests()`](Self::multitransport_requests)). The
274+ /// connector builds the response PDUs internally using the stored request
275+ /// IDs and cookies, then replays the buffered Demand Active PDU through
276+ /// the activation sequence.
277+ ///
278+ /// Returns an error if the connector is not in `MultitransportPending`
279+ /// state, or if `results.len()` does not match the number of pending
280+ /// requests.
281+ pub fn complete_multitransport (
282+ & mut self ,
283+ results : & [ MultitransportResult ] ,
284+ output : & mut WriteBuf ,
285+ ) -> ConnectorResult < Written > {
286+ let ClientConnectorState :: MultitransportPending {
287+ io_channel_id,
288+ user_channel_id,
289+ requests,
290+ buffered_demand_active,
291+ } = mem:: replace ( & mut self . state , ClientConnectorState :: Consumed )
292+ else {
293+ return Err ( general_err ! (
294+ "complete_multitransport called outside MultitransportPending state"
295+ ) ) ;
296+ } ;
297+
298+ if results. len ( ) != requests. len ( ) {
299+ return Err ( general_err ! (
300+ "multitransport results count does not match requests count"
301+ ) ) ;
302+ }
303+
304+ let mut total_written = 0 ;
305+
306+ for ( request, result) in requests. iter ( ) . zip ( results) {
307+ let response = match result {
308+ MultitransportResult :: Success => {
309+ rdp:: multitransport:: MultitransportResponsePdu :: success ( request. request_id )
310+ }
311+ MultitransportResult :: Failure ( hr) => rdp:: multitransport:: MultitransportResponsePdu {
312+ security_header : rdp:: headers:: BasicSecurityHeader {
313+ flags : rdp:: headers:: BasicSecurityHeaderFlags :: TRANSPORT_RSP ,
314+ } ,
315+ request_id : request. request_id ,
316+ hr_response : * hr,
317+ } ,
318+ } ;
319+ total_written += encode_send_data_request ( user_channel_id, io_channel_id, & response, output) ?;
320+ }
321+
322+ // Replay the buffered Demand Active through the activation sequence
323+ let mut connection_activation =
324+ ConnectionActivationSequence :: new ( self . config . clone ( ) , io_channel_id, user_channel_id) ;
325+ let replay_written = connection_activation. step ( & buffered_demand_active, output) ?;
326+ total_written += replay_written. size ( ) . unwrap_or ( 0 ) ;
327+
328+ self . state = match connection_activation. connection_activation_state ( ) {
329+ ConnectionActivationState :: ConnectionFinalization { .. } => {
330+ ClientConnectorState :: ConnectionFinalization { connection_activation }
331+ }
332+ _ => ClientConnectorState :: CapabilitiesExchange { connection_activation } ,
333+ } ;
334+
335+ Written :: from_size ( total_written)
336+ }
337+
338+ /// Skip multitransport bootstrapping without sending any responses.
339+ ///
340+ /// Use this when the application doesn't support or doesn't want UDP
341+ /// transport. The server will continue with TCP-only operation.
342+ ///
343+ /// The buffered Demand Active PDU is replayed internally.
344+ ///
345+ /// Returns an error if the connector is not in `MultitransportPending`
346+ /// state.
347+ pub fn skip_multitransport ( & mut self , output : & mut WriteBuf ) -> ConnectorResult < Written > {
348+ let ClientConnectorState :: MultitransportPending {
349+ io_channel_id,
350+ user_channel_id,
351+ buffered_demand_active,
352+ ..
353+ } = mem:: replace ( & mut self . state , ClientConnectorState :: Consumed )
354+ else {
355+ return Err ( general_err ! (
356+ "skip_multitransport called outside MultitransportPending state"
357+ ) ) ;
358+ } ;
359+
360+ // Replay the buffered Demand Active through the activation sequence
361+ let mut connection_activation =
362+ ConnectionActivationSequence :: new ( self . config . clone ( ) , io_channel_id, user_channel_id) ;
363+ let written = connection_activation. step ( & buffered_demand_active, output) ?;
364+
365+ self . state = match connection_activation. connection_activation_state ( ) {
366+ ConnectionActivationState :: ConnectionFinalization { .. } => {
367+ ClientConnectorState :: ConnectionFinalization { connection_activation }
368+ }
369+ _ => ClientConnectorState :: CapabilitiesExchange { connection_activation } ,
370+ } ;
371+
372+ Ok ( written)
373+ }
201374}
202375
203376impl Sequence for ClientConnector {
@@ -214,7 +387,8 @@ impl Sequence for ClientConnector {
214387 ClientConnectorState :: SecureSettingsExchange { .. } => None ,
215388 ClientConnectorState :: ConnectTimeAutoDetection { .. } => None ,
216389 ClientConnectorState :: LicensingExchange { license_exchange, .. } => license_exchange. next_pdu_hint ( ) ,
217- ClientConnectorState :: MultitransportBootstrapping { .. } => None ,
390+ ClientConnectorState :: MultitransportBootstrapping { .. } => Some ( & ironrdp_pdu:: X224_HINT ) ,
391+ ClientConnectorState :: MultitransportPending { .. } => None ,
218392 ClientConnectorState :: CapabilitiesExchange {
219393 connection_activation, ..
220394 } => connection_activation. next_pdu_hint ( ) ,
@@ -519,6 +693,7 @@ impl Sequence for ClientConnector {
519693 ClientConnectorState :: MultitransportBootstrapping {
520694 io_channel_id,
521695 user_channel_id,
696+ requests : Vec :: new ( ) ,
522697 }
523698 } else {
524699 ClientConnectorState :: LicensingExchange {
@@ -532,20 +707,88 @@ impl Sequence for ClientConnector {
532707 }
533708
534709 //== Optional Multitransport Bootstrapping ==//
535- // NOTE: our implementation is not expecting the Auto-Detect Request PDU from server
710+ //
711+ // The server may send 0, 1, or 2 Initiate Multitransport Request PDUs
712+ // after licensing. We distinguish them from the Demand Active PDU by
713+ // attempting to decode as MultitransportRequestPdu first — it has a
714+ // distinctive structure (SEC_TRANSPORT_REQ flag + request_id + protocol
715+ // + cookie). If decode fails, this is the Demand Active.
536716 ClientConnectorState :: MultitransportBootstrapping {
537717 io_channel_id,
538718 user_channel_id,
539- } => (
540- Written :: Nothing ,
541- ClientConnectorState :: CapabilitiesExchange {
542- connection_activation : ConnectionActivationSequence :: new (
543- self . config . clone ( ) ,
544- io_channel_id,
545- user_channel_id,
546- ) ,
547- } ,
548- ) ,
719+ mut requests,
720+ } => {
721+ let ctx = crate :: legacy:: decode_send_data_indication ( input) ?;
722+
723+ // Try decoding as a multitransport request. The decoder validates
724+ // the SEC_TRANSPORT_REQ flag, so a Demand Active PDU will fail
725+ // cleanly without false positives.
726+ match decode :: < rdp:: multitransport:: MultitransportRequestPdu > ( ctx. user_data ) {
727+ Ok ( pdu) => {
728+ debug ! (
729+ request_id = pdu. request_id,
730+ protocol = ?pdu. requested_protocol,
731+ "Received Initiate Multitransport Request"
732+ ) ;
733+
734+ requests. push ( pdu) ;
735+
736+ // Stay in this state to read more requests (server may send a second)
737+ (
738+ Written :: Nothing ,
739+ ClientConnectorState :: MultitransportBootstrapping {
740+ io_channel_id,
741+ user_channel_id,
742+ requests,
743+ } ,
744+ )
745+ }
746+ Err ( _) if !requests. is_empty ( ) => {
747+ // Decode failed → this is the Demand Active PDU. Buffer it
748+ // and pause for the application to handle multitransport.
749+ info ! (
750+ count = requests. len( ) ,
751+ "Multitransport bootstrapping: pausing for application"
752+ ) ;
753+
754+ (
755+ Written :: Nothing ,
756+ ClientConnectorState :: MultitransportPending {
757+ io_channel_id,
758+ user_channel_id,
759+ requests,
760+ buffered_demand_active : input. to_vec ( ) ,
761+ } ,
762+ )
763+ }
764+ Err ( _) => {
765+ // No multitransport requests — server went straight to
766+ // capabilities exchange. Forward the PDU.
767+ let mut connection_activation =
768+ ConnectionActivationSequence :: new ( self . config . clone ( ) , io_channel_id, user_channel_id) ;
769+ let written = connection_activation. step ( input, output) ?;
770+
771+ match connection_activation. connection_activation_state ( ) {
772+ ConnectionActivationState :: ConnectionFinalization { .. } => (
773+ written,
774+ ClientConnectorState :: ConnectionFinalization { connection_activation } ,
775+ ) ,
776+ _ => (
777+ written,
778+ ClientConnectorState :: CapabilitiesExchange { connection_activation } ,
779+ ) ,
780+ }
781+ }
782+ }
783+ }
784+
785+ // MultitransportPending: application should call complete_multitransport()
786+ // or skip_multitransport() instead of step()
787+ ClientConnectorState :: MultitransportPending { .. } => {
788+ return Err ( general_err ! (
789+ "multitransport pending: call complete_multitransport() or skip_multitransport()"
790+ ) ) ;
791+ }
549792
550793 //== Capabilities Exchange ==/
551794 // The server sends the set of capabilities it supports to the client.
0 commit comments