use ironrdp_core::{WriteBuf, decode}; use ironrdp_dvc::{DrdynvcClient, DvcProcessor, DynamicVirtualChannel}; use ironrdp_pdu::mcs::{DisconnectProviderUltimatum, DisconnectReason, McsMessage, SendDataIndicationCtx}; use ironrdp_pdu::rdp::autodetect::{AutoDetectReqPdu, AutoDetectRequest, AutoDetectResponse, AutoDetectRspPdu}; use ironrdp_pdu::rdp::headers::ShareDataPdu; use ironrdp_pdu::rdp::multitransport::MultitransportRequestPdu; use ironrdp_pdu::rdp::server_error_info::{ErrorInfo, ProtocolIndependentCode, ServerSetErrorInfoPdu}; use ironrdp_pdu::x224::X224; use ironrdp_svc::{StaticChannelSet, SvcMessage, SvcProcessor, SvcProcessorMessages, client_encode_svc_messages}; use tracing::debug; use crate::{SessionError, SessionErrorExt as _, SessionResult, reason_err}; /// X224 Processor output #[derive(Debug, Clone)] pub enum ProcessorOutput { /// A buffer with encoded data to send to the server. ResponseFrame(Vec), /// A graceful disconnect notification. Client should close the connection upon receiving this. Disconnect(DisconnectDescription), /// Received a [`ironrdp_pdu::rdp::headers::ServerDeactivateAll`] PDU. Client should execute the /// [Deactivation-Reactivation Sequence]. /// /// [Deactivation-Reactivation Sequence]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dfc234ce-481a-4674-9a5d-2a7bafb14432 DeactivateAll, /// Server Initiate Multitransport Request. The application should establish a /// sideband UDP transport using the request ID and security cookie, then send /// a [`MultitransportResponsePdu`] back on the IO channel. /// /// See [\[MS-RDPBCGR\] 2.2.15.1]. /// /// [\[MS-RDPBCGR\] 2.2.15.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/de783158-8b01-4818-8fb0-62523a5b3490 /// [`MultitransportResponsePdu`]: ironrdp_pdu::rdp::multitransport::MultitransportResponsePdu MultitransportRequest(MultitransportRequestPdu), /// Auto-detect network characteristics from server ([\[MS-RDPBCGR\] 2.2.14]). /// /// Currently only surfaces [`AutoDetectRequest::NetworkCharacteristicsResult`]. /// RTT requests are handled internally with automatic responses. /// /// [\[MS-RDPBCGR\] 2.2.14]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dc672839-4f4e-40b1-a71c-cd6a959baa38 AutoDetect(AutoDetectRequest), /// Slow-path graphics update ([MS-RDPBCGR] 2.2.9.1.1.3). /// Raw update payload starting with `updateType(u16)`. GraphicsUpdate(Vec), /// Slow-path pointer update ([MS-RDPBCGR] 2.2.9.1.1.4). /// Raw pointer payload starting with `messageType(u16) + pad(u16)`. PointerUpdate(Vec), } #[derive(Debug, Clone)] pub enum DisconnectDescription { /// Includes the reason from the MCS Disconnect Provider Ultimatum. /// This is the least-specific disconnect reason and is only used /// when a more specific disconnect code is not available. McsDisconnect(DisconnectReason), /// Includes the error information sent by the RDP server when there /// is a connection or disconnection failure. ErrorInfo(ErrorInfo), } pub struct Processor { static_channels: StaticChannelSet, user_channel_id: u16, io_channel_id: u16, message_channel_id: Option, share_id: u32, } impl Processor { pub fn new( static_channels: StaticChannelSet, user_channel_id: u16, io_channel_id: u16, message_channel_id: Option, share_id: u32, ) -> Self { Self { static_channels, user_channel_id, io_channel_id, message_channel_id, share_id, } } pub fn set_share_id(&mut self, share_id: u32) { self.share_id = share_id; } pub fn get_svc_processor(&self) -> Option<&T> { self.static_channels .get_by_type::() .and_then(|svc| svc.channel_processor_downcast_ref()) } pub fn get_svc_processor_mut(&mut self) -> Option<&mut T> { self.static_channels .get_by_type_mut::() .and_then(|svc| svc.channel_processor_downcast_mut()) } /// Completes user's SVC request with data, required to sent it over the network and returns /// a buffer with encoded data. pub fn process_svc_processor_messages( &self, messages: SvcProcessorMessages, ) -> SessionResult> { let channel_id = self .static_channels .get_channel_id_by_type::() .ok_or_else(|| reason_err!("SVC", "channel not found"))?; process_svc_messages(messages.into(), channel_id, self.user_channel_id) } pub fn get_dvc(&self) -> Option<&DynamicVirtualChannel> { self.get_svc_processor::()?.get_dvc_by_type_id::() } pub fn get_dvc_by_channel_id(&self, channel_id: u32) -> Option<&DynamicVirtualChannel> { self.get_svc_processor::()? .get_dvc_by_channel_id(channel_id) } /// Processes a received PDU. Returns a vector of [`ProcessorOutput`] that must be processed /// in the returned order. pub fn process(&mut self, frame: &[u8]) -> SessionResult> { let data_ctx: SendDataIndicationCtx<'_> = ironrdp_pdu::mcs::decode_send_data_indication(frame).map_err(SessionError::decode)?; let channel_id = data_ctx.channel_id; if channel_id == self.io_channel_id { self.process_io_channel(data_ctx) } else if self.message_channel_id == Some(channel_id) { self.process_message_channel(data_ctx) } else if let Some(svc) = self.static_channels.get_by_channel_id_mut(channel_id) { let response_pdus = svc.process(data_ctx.user_data).map_err(SessionError::pdu)?; process_svc_messages(response_pdus, channel_id, data_ctx.initiator_id) .map(|data| vec![ProcessorOutput::ResponseFrame(data)]) } else { Err(reason_err!("X224", "unexpected channel received: ID {channel_id}")) } } fn process_io_channel(&self, data_ctx: SendDataIndicationCtx<'_>) -> SessionResult> { debug_assert_eq!(data_ctx.channel_id, self.io_channel_id); let io_channel = ironrdp_pdu::rdp::headers::decode_io_channel(data_ctx).map_err(SessionError::decode)?; match io_channel { ironrdp_pdu::rdp::headers::IoChannelPdu::Data(ctx) => { match ctx.pdu { ShareDataPdu::SaveSessionInfo(session_info) => { debug!("Got Session Save Info PDU: {session_info:?}"); Ok(Vec::new()) } // FIXME: workaround fix to not terminate the session on "unhandled PDU: Set Keyboard Indicators PDU" ShareDataPdu::SetKeyboardIndicators(data) => { debug!("Got Keyboard Indicators PDU: {data:?}"); Ok(Vec::new()) } ShareDataPdu::ServerSetErrorInfo(ServerSetErrorInfoPdu(ErrorInfo::ProtocolIndependentCode( ProtocolIndependentCode::None, ))) => { debug!("Received None server error"); Ok(Vec::new()) } ShareDataPdu::ServerSetErrorInfo(ServerSetErrorInfoPdu(e)) => { // This is a part of server-side graceful disconnect procedure defined // in [MS-RDPBCGR]. // // [MS-RDPBCGR]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/149070b0-ecec-4c20-af03-934bbc48adb8 let desc = DisconnectDescription::ErrorInfo(e); Ok(vec![ProcessorOutput::Disconnect(desc)]) } ShareDataPdu::ShutdownDenied => { debug!("ShutdownDenied received, session will be closed"); // As defined in [MS-RDPBCGR], when `ShareDataPdu::ShutdownDenied` is received, we // need to send a disconnect ultimatum to the server if we want to proceed with the // session shutdown. // // [MS-RDPBCGR]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/27915739-8f77-487e-9927-55008af7fd68 let ultimatum = McsMessage::DisconnectProviderUltimatum( DisconnectProviderUltimatum::from_reason(DisconnectReason::UserRequested), ); let encoded_pdu = ironrdp_core::encode_vec(&X224(ultimatum)).map_err(SessionError::encode); Ok(vec![ ProcessorOutput::ResponseFrame(encoded_pdu?), ProcessorOutput::Disconnect(DisconnectDescription::McsDisconnect( DisconnectReason::UserRequested, )), ]) } // TODO: slow-path payloads may be bulk-compressed when // ClientInfoFlags::COMPRESSION is negotiated. Decompression // should happen here before passing data downstream. Currently // IronRDP does not wire bulk decompression into this path. // FIXME: until this is wired, the client deliberately defaults to the simple, // stateless-friendly MPPC 64K (RDP5) compression level rather than XCRUSH; a // stateful codec would risk silent corruption on slow-path updates. ShareDataPdu::Update(data) => { debug!("Got slow-path graphics update ({} bytes)", data.len()); Ok(vec![ProcessorOutput::GraphicsUpdate(data)]) } ShareDataPdu::Pointer(data) => { debug!("Got slow-path pointer update ({} bytes)", data.len()); Ok(vec![ProcessorOutput::PointerUpdate(data)]) } _ => Err(reason_err!( "IO channel", "unhandled PDU: {:?}", ctx.pdu.as_short_name() )), } } ironrdp_pdu::rdp::headers::IoChannelPdu::MultitransportRequest(pdu) => { debug!( "Received Initiate Multitransport Request: request_id={}", pdu.request_id ); Ok(vec![ProcessorOutput::MultitransportRequest(pdu)]) } ironrdp_pdu::rdp::headers::IoChannelPdu::DeactivateAll(_) => Ok(vec![ProcessorOutput::DeactivateAll]), } } /// Process an auto-detect request received on the MCS message channel. /// /// During continuous auto-detection ([MS-RDPBCGR] 2.2.14) the server sends /// RTT (and bandwidth) requests on the message channel; the client answers /// RTT requests and surfaces the final Network Characteristics Result. fn process_message_channel(&self, data_ctx: SendDataIndicationCtx<'_>) -> SessionResult> { let Some(message_channel_id) = self.message_channel_id else { return Err(reason_err!("message channel", "no message channel negotiated")); }; let req = decode::(data_ctx.user_data).map_err(SessionError::decode)?; match req.request { AutoDetectRequest::RttRequest { sequence_number, .. } => { let response = AutoDetectRspPdu::new(AutoDetectResponse::RttResponse { sequence_number }); let mut frame = WriteBuf::new(); ironrdp_pdu::mcs::encode_send_data_request( self.user_channel_id, message_channel_id, &response, &mut frame, ) .map_err(SessionError::encode)?; debug!(sequence_number, "Responded to auto-detect RTT request"); Ok(vec![ProcessorOutput::ResponseFrame(frame.into_inner())]) } req @ AutoDetectRequest::NetworkCharacteristicsResult { .. } => { debug!(?req, "Received network characteristics from server"); Ok(vec![ProcessorOutput::AutoDetect(req)]) } req => { debug!(?req, "Auto-detect request not yet implemented"); Ok(Vec::new()) } } } /// Send a pdu on the static global channel. Typically used to send input events pub fn encode_static(&self, output: &mut WriteBuf, pdu: ShareDataPdu) -> SessionResult { let written = ironrdp_pdu::rdp::headers::encode_share_data( self.user_channel_id, self.io_channel_id, self.share_id, pdu, output, ) .map_err(SessionError::encode)?; Ok(written) } } /// Processes a vector of [`SvcMessage`] in preparation for sending them to the server on the `channel_id` channel. /// /// This includes chunkifying the messages, adding MCS, x224, and tpkt headers, and encoding them into a buffer. /// The messages returned here are ready to be sent to the server. /// /// The caller is responsible for ensuring that the `channel_id` corresponds to the correct channel. fn process_svc_messages(messages: Vec, channel_id: u16, initiator_id: u16) -> SessionResult> { client_encode_svc_messages(messages, channel_id, initiator_id).map_err(SessionError::encode) }