@@ -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 ) ]
2135#[ cfg_attr( feature = "arbitrary" , derive( arbitrary:: Arbitrary ) ) ]
2236pub struct ConnectionResult {
@@ -72,9 +86,32 @@ pub enum ClientConnectorState {
7286 user_channel_id : u16 ,
7387 license_exchange : LicenseExchangeSequence ,
7488 } ,
89+ /// Reading the server's optional Initiate Multitransport Request PDU(s).
90+ ///
91+ /// The server may send 0, 1, or 2 requests (one per transport protocol).
92+ /// If the first PDU on the IO channel after licensing is a Demand Active
93+ /// (capabilities exchange), the server sent no multitransport requests and
94+ /// the connector transitions directly to `CapabilitiesExchange`.
7595 MultitransportBootstrapping {
7696 io_channel_id : u16 ,
7797 user_channel_id : u16 ,
98+ /// Multitransport requests received from the server so far.
99+ requests : Vec < rdp:: multitransport:: MultitransportRequestPdu > ,
100+ } ,
101+ /// The server sent multitransport request(s) and the connector is paused
102+ /// waiting for the application to establish UDP transport or decline.
103+ ///
104+ /// Call [`ClientConnector::complete_multitransport()`] or
105+ /// [`ClientConnector::skip_multitransport()`] to advance. The buffered
106+ /// Demand Active PDU is replayed internally — no re-feeding needed.
107+ MultitransportPending {
108+ io_channel_id : u16 ,
109+ user_channel_id : u16 ,
110+ requests : Vec < rdp:: multitransport:: MultitransportRequestPdu > ,
111+ /// The raw Demand Active PDU bytes that arrived after the last
112+ /// multitransport request. Replayed through the activation sequence
113+ /// when the application completes or skips multitransport.
114+ buffered_demand_active : Vec < u8 > ,
78115 } ,
79116 CapabilitiesExchange {
80117 connection_activation : ConnectionActivationSequence ,
@@ -102,6 +139,7 @@ impl State for ClientConnectorState {
102139 Self :: ConnectTimeAutoDetection { .. } => "ConnectTimeAutoDetection" ,
103140 Self :: LicensingExchange { .. } => "LicensingExchange" ,
104141 Self :: MultitransportBootstrapping { .. } => "MultitransportBootstrapping" ,
142+ Self :: MultitransportPending { .. } => "MultitransportPending" ,
105143 Self :: CapabilitiesExchange {
106144 connection_activation, ..
107145 } => connection_activation. state ( ) . name ( ) ,
@@ -201,6 +239,141 @@ impl ClientConnector {
201239 debug_assert ! ( !self . should_perform_credssp( ) ) ;
202240 assert_eq ! ( res, Written :: Nothing ) ;
203241 }
242+
243+ /// Returns `true` when the connector has collected all multitransport
244+ /// requests from the server and is waiting for the application to either
245+ /// establish the UDP transport(s) or decline them.
246+ ///
247+ /// The application should:
248+ ///
249+ /// 1. Call [`multitransport_requests()`](Self::multitransport_requests) to
250+ /// get the server's request(s)
251+ /// 2. Establish UDP transport (RDPEUDP2 + TLS + RDPEMT) for each, or decide
252+ /// not to
253+ /// 3. Call [`complete_multitransport()`](Self::complete_multitransport) with
254+ /// a [`MultitransportResult`] for each request, or
255+ /// [`skip_multitransport()`](Self::skip_multitransport) to decline all
256+ pub fn should_perform_multitransport ( & self ) -> bool {
257+ matches ! ( self . state, ClientConnectorState :: MultitransportPending { .. } )
258+ }
259+
260+ /// Returns the multitransport request PDUs received from the server.
261+ ///
262+ /// Only meaningful when
263+ /// [`should_perform_multitransport()`](Self::should_perform_multitransport)
264+ /// returns `true`.
265+ pub fn multitransport_requests ( & self ) -> & [ rdp:: multitransport:: MultitransportRequestPdu ] {
266+ match & self . state {
267+ ClientConnectorState :: MultitransportPending { requests, .. } => requests,
268+ _ => & [ ] ,
269+ }
270+ }
271+
272+ /// Send multitransport response PDU(s) and advance past the bootstrapping
273+ /// phase to capabilities exchange.
274+ ///
275+ /// Pass one [`MultitransportResult`] per request (in the same order as
276+ /// [`multitransport_requests()`](Self::multitransport_requests)). The
277+ /// connector builds the response PDUs internally using the stored request
278+ /// IDs and cookies, then replays the buffered Demand Active PDU through
279+ /// the activation sequence.
280+ ///
281+ /// Returns an error if the connector is not in `MultitransportPending`
282+ /// state, or if `results.len()` does not match the number of pending
283+ /// requests.
284+ pub fn complete_multitransport (
285+ & mut self ,
286+ results : & [ MultitransportResult ] ,
287+ output : & mut WriteBuf ,
288+ ) -> ConnectorResult < Written > {
289+ let ClientConnectorState :: MultitransportPending {
290+ io_channel_id,
291+ user_channel_id,
292+ requests,
293+ buffered_demand_active,
294+ } = mem:: replace ( & mut self . state , ClientConnectorState :: Consumed )
295+ else {
296+ return Err ( general_err ! (
297+ "complete_multitransport called outside MultitransportPending state"
298+ ) ) ;
299+ } ;
300+
301+ if results. len ( ) != requests. len ( ) {
302+ return Err ( general_err ! (
303+ "multitransport results count does not match requests count"
304+ ) ) ;
305+ }
306+
307+ let mut total_written = 0 ;
308+
309+ for ( request, result) in requests. iter ( ) . zip ( results) {
310+ let response = match result {
311+ MultitransportResult :: Success => {
312+ rdp:: multitransport:: MultitransportResponsePdu :: success ( request. request_id )
313+ }
314+ MultitransportResult :: Failure ( hr) => rdp:: multitransport:: MultitransportResponsePdu {
315+ security_header : rdp:: headers:: BasicSecurityHeader {
316+ flags : rdp:: headers:: BasicSecurityHeaderFlags :: TRANSPORT_RSP ,
317+ } ,
318+ request_id : request. request_id ,
319+ hr_response : * hr,
320+ } ,
321+ } ;
322+ total_written += encode_send_data_request ( user_channel_id, io_channel_id, & response, output) ?;
323+ }
324+
325+ // Replay the buffered Demand Active through the activation sequence
326+ let mut connection_activation =
327+ ConnectionActivationSequence :: new ( self . config . clone ( ) , io_channel_id, user_channel_id) ;
328+ let replay_written = connection_activation. step ( & buffered_demand_active, output) ?;
329+ total_written += replay_written. size ( ) . unwrap_or ( 0 ) ;
330+
331+ self . state = match connection_activation. connection_activation_state ( ) {
332+ ConnectionActivationState :: ConnectionFinalization { .. } => {
333+ ClientConnectorState :: ConnectionFinalization { connection_activation }
334+ }
335+ _ => ClientConnectorState :: CapabilitiesExchange { connection_activation } ,
336+ } ;
337+
338+ Written :: from_size ( total_written)
339+ }
340+
341+ /// Skip multitransport bootstrapping without sending any responses.
342+ ///
343+ /// Use this when the application doesn't support or doesn't want UDP
344+ /// transport. The server will continue with TCP-only operation.
345+ ///
346+ /// The buffered Demand Active PDU is replayed internally.
347+ ///
348+ /// Returns an error if the connector is not in `MultitransportPending`
349+ /// state.
350+ pub fn skip_multitransport ( & mut self , output : & mut WriteBuf ) -> ConnectorResult < Written > {
351+ let ClientConnectorState :: MultitransportPending {
352+ io_channel_id,
353+ user_channel_id,
354+ buffered_demand_active,
355+ ..
356+ } = mem:: replace ( & mut self . state , ClientConnectorState :: Consumed )
357+ else {
358+ return Err ( general_err ! (
359+ "skip_multitransport called outside MultitransportPending state"
360+ ) ) ;
361+ } ;
362+
363+ // Replay the buffered Demand Active through the activation sequence
364+ let mut connection_activation =
365+ ConnectionActivationSequence :: new ( self . config . clone ( ) , io_channel_id, user_channel_id) ;
366+ let written = connection_activation. step ( & buffered_demand_active, output) ?;
367+
368+ self . state = match connection_activation. connection_activation_state ( ) {
369+ ConnectionActivationState :: ConnectionFinalization { .. } => {
370+ ClientConnectorState :: ConnectionFinalization { connection_activation }
371+ }
372+ _ => ClientConnectorState :: CapabilitiesExchange { connection_activation } ,
373+ } ;
374+
375+ Ok ( written)
376+ }
204377}
205378
206379impl Sequence for ClientConnector {
@@ -217,7 +390,8 @@ impl Sequence for ClientConnector {
217390 ClientConnectorState :: SecureSettingsExchange { .. } => None ,
218391 ClientConnectorState :: ConnectTimeAutoDetection { .. } => None ,
219392 ClientConnectorState :: LicensingExchange { license_exchange, .. } => license_exchange. next_pdu_hint ( ) ,
220- ClientConnectorState :: MultitransportBootstrapping { .. } => None ,
393+ ClientConnectorState :: MultitransportBootstrapping { .. } => Some ( & ironrdp_pdu:: X224_HINT ) ,
394+ ClientConnectorState :: MultitransportPending { .. } => None ,
221395 ClientConnectorState :: CapabilitiesExchange {
222396 connection_activation, ..
223397 } => connection_activation. next_pdu_hint ( ) ,
@@ -522,6 +696,7 @@ impl Sequence for ClientConnector {
522696 ClientConnectorState :: MultitransportBootstrapping {
523697 io_channel_id,
524698 user_channel_id,
699+ requests : Vec :: new ( ) ,
525700 }
526701 } else {
527702 ClientConnectorState :: LicensingExchange {
@@ -535,20 +710,88 @@ impl Sequence for ClientConnector {
535710 }
536711
537712 //== Optional Multitransport Bootstrapping ==//
538- // NOTE: our implementation is not expecting the Auto-Detect Request PDU from server
713+ //
714+ // The server may send 0, 1, or 2 Initiate Multitransport Request PDUs
715+ // after licensing. We distinguish them from the Demand Active PDU by
716+ // attempting to decode as MultitransportRequestPdu first — it has a
717+ // distinctive structure (SEC_TRANSPORT_REQ flag + request_id + protocol
718+ // + cookie). If decode fails, this is the Demand Active.
539719 ClientConnectorState :: MultitransportBootstrapping {
540720 io_channel_id,
541721 user_channel_id,
542- } => (
543- Written :: Nothing ,
544- ClientConnectorState :: CapabilitiesExchange {
545- connection_activation : ConnectionActivationSequence :: new (
546- self . config . clone ( ) ,
547- io_channel_id,
548- user_channel_id,
549- ) ,
550- } ,
551- ) ,
722+ mut requests,
723+ } => {
724+ let ctx = crate :: legacy:: decode_send_data_indication ( input) ?;
725+
726+ // Try decoding as a multitransport request. The decoder validates
727+ // the SEC_TRANSPORT_REQ flag, so a Demand Active PDU will fail
728+ // cleanly without false positives.
729+ match decode :: < rdp:: multitransport:: MultitransportRequestPdu > ( ctx. user_data ) {
730+ Ok ( pdu) => {
731+ debug ! (
732+ request_id = pdu. request_id,
733+ protocol = ?pdu. requested_protocol,
734+ "Received Initiate Multitransport Request"
735+ ) ;
736+
737+ requests. push ( pdu) ;
738+
739+ // Stay in this state to read more requests (server may send a second)
740+ (
741+ Written :: Nothing ,
742+ ClientConnectorState :: MultitransportBootstrapping {
743+ io_channel_id,
744+ user_channel_id,
745+ requests,
746+ } ,
747+ )
748+ }
749+ Err ( _) if !requests. is_empty ( ) => {
750+ // Decode failed → this is the Demand Active PDU. Buffer it
751+ // and pause for the application to handle multitransport.
752+ info ! (
753+ count = requests. len( ) ,
754+ "Multitransport bootstrapping: pausing for application"
755+ ) ;
756+
757+ (
758+ Written :: Nothing ,
759+ ClientConnectorState :: MultitransportPending {
760+ io_channel_id,
761+ user_channel_id,
762+ requests,
763+ buffered_demand_active : input. to_vec ( ) ,
764+ } ,
765+ )
766+ }
767+ Err ( _) => {
768+ // No multitransport requests — server went straight to
769+ // capabilities exchange. Forward the PDU.
770+ let mut connection_activation =
771+ ConnectionActivationSequence :: new ( self . config . clone ( ) , io_channel_id, user_channel_id) ;
772+ let written = connection_activation. step ( input, output) ?;
773+
774+ match connection_activation. connection_activation_state ( ) {
775+ ConnectionActivationState :: ConnectionFinalization { .. } => (
776+ written,
777+ ClientConnectorState :: ConnectionFinalization { connection_activation } ,
778+ ) ,
779+ _ => (
780+ written,
781+ ClientConnectorState :: CapabilitiesExchange { connection_activation } ,
782+ ) ,
783+ }
784+ }
785+ }
786+ }
787+
788+ // MultitransportPending: application should call complete_multitransport()
789+ // or skip_multitransport() instead of step()
790+ ClientConnectorState :: MultitransportPending { .. } => {
791+ return Err ( general_err ! (
792+ "multitransport pending: call complete_multitransport() or skip_multitransport()"
793+ ) ) ;
794+ }
552795
553796 //== Capabilities Exchange ==/
554797 // The server sends the set of capabilities it supports to the client.
0 commit comments