From 777609415c956167e4fd679e5a3ba3b06466fec1 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Fri, 8 Mar 2024 18:55:52 -0800 Subject: [PATCH 01/84] Creates IoChannelPdu enum to be used to account for the potential of receiving a ServerDeactivateAll on the I/O channel --- Cargo.toml | 1 + crates/ironrdp-connector/src/legacy.rs | 32 ++++- crates/ironrdp-graphics/Cargo.toml | 2 +- crates/ironrdp-pdu/Cargo.toml | 2 +- crates/ironrdp-pdu/src/rdp/capability_sets.rs | 9 ++ crates/ironrdp-pdu/src/rdp/headers.rs | 43 ++++++- crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs | 6 + crates/ironrdp-session/src/x224/mod.rs | 109 ++++++++++-------- 8 files changed, 149 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e8938c6c4..a79ce9f7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ tracing = { version = "0.1", features = ["log"] } thiserror = "1.0" png = "0.17" bitflags = "2.4" +byteorder = "1.5" [profile.dev] opt-level = 1 diff --git a/crates/ironrdp-connector/src/legacy.rs b/crates/ironrdp-connector/src/legacy.rs index fc694173b..7f4e4ca3d 100644 --- a/crates/ironrdp-connector/src/legacy.rs +++ b/crates/ironrdp-connector/src/legacy.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; +use ironrdp_pdu::rdp::headers::ServerDeactivateAll; use ironrdp_pdu::write_buf::WriteBuf; use ironrdp_pdu::{rdp, x224, PduParsing}; @@ -182,7 +183,7 @@ pub fn decode_share_data(ctx: SendDataIndicationCtx<'_>) -> ConnectorResult) -> ConnectorResult) -> ConnectorResult { + let ctx = decode_share_control(ctx)?; + + match ctx.pdu { + rdp::headers::ShareControlPdu::ServerDeactivateAll(deactivate_all) => { + Ok(IoChannelPdu::DeactivateAll(deactivate_all)) + } + rdp::headers::ShareControlPdu::Data(share_data_header) => { + let share_data_ctx = ShareDataCtx { + initiator_id: ctx.initiator_id, + channel_id: ctx.channel_id, + share_id: ctx.share_id, + pdu_source: ctx.pdu_source, + pdu: share_data_header.share_data_pdu, + }; + + Ok(IoChannelPdu::Data(share_data_ctx)) + } + _ => Err(general_err!( + "received unexpected Share Control Pdu (expected Share Data Header or Server Deactivate All)" + )), + } +} + impl ironrdp_error::legacy::CatchAllKind for crate::ConnectorErrorKind { const CATCH_ALL_VALUE: Self = crate::ConnectorErrorKind::General; } diff --git a/crates/ironrdp-graphics/Cargo.toml b/crates/ironrdp-graphics/Cargo.toml index 6fe765262..21850254b 100644 --- a/crates/ironrdp-graphics/Cargo.toml +++ b/crates/ironrdp-graphics/Cargo.toml @@ -19,7 +19,7 @@ doctest = false bit_field = "0.10" bitflags.workspace = true bitvec = "1.0" -byteorder = "1.5" +byteorder.workspace = true ironrdp-error.workspace = true ironrdp-pdu = { workspace = true, features = ["std"] } lazy_static = "1.4" diff --git a/crates/ironrdp-pdu/Cargo.toml b/crates/ironrdp-pdu/Cargo.toml index 53b0b1c56..ab38f33b5 100644 --- a/crates/ironrdp-pdu/Cargo.toml +++ b/crates/ironrdp-pdu/Cargo.toml @@ -27,7 +27,7 @@ tap = "1" # TODO: get rid of these dependencies (related code should probably go into another crate) bit_field = "0.10" -byteorder = "1.5" +byteorder.workspace = true der-parser = "8.2" thiserror.workspace = true md5 = { package = "md-5", version = "0.10" } diff --git a/crates/ironrdp-pdu/src/rdp/capability_sets.rs b/crates/ironrdp-pdu/src/rdp/capability_sets.rs index 86dffe5ce..5d694cb89 100644 --- a/crates/ironrdp-pdu/src/rdp/capability_sets.rs +++ b/crates/ironrdp-pdu/src/rdp/capability_sets.rs @@ -59,6 +59,9 @@ const ORIGINATOR_ID_FIELD_SIZE: usize = 2; const NULL_TERMINATOR: &str = "\0"; +/// [2.2.1.13.1] Server Demand Active PDU +/// +/// [2.2.1.13.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/a07abad1-38bb-4a1a-96c9-253e3d5440df #[derive(Debug, Clone, PartialEq, Eq)] pub struct ServerDemandActive { pub pdu: DemandActive, @@ -86,6 +89,9 @@ impl PduParsing for ServerDemandActive { } } +/// [2.2.1.13.2] Client Confirm Active PDU +/// +/// [2.2.1.13.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/4c3c2710-0bf0-4c54-8e69-aff40ffcde66 #[derive(Debug, Clone, PartialEq, Eq)] pub struct ClientConfirmActive { /// According to [MSDN](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/4e9722c3-ad83-43f5-af5a-529f73d88b48), @@ -119,6 +125,9 @@ impl PduParsing for ClientConfirmActive { } } +/// 2.2.1.13.1.1 Demand Active PDU Data (TS_DEMAND_ACTIVE_PDU) +/// +/// [2.2.1.13.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/bd612af5-cb54-43a2-9646-438bc3ecf5db #[derive(Debug, Clone, PartialEq, Eq)] pub struct DemandActive { pub source_descriptor: String, diff --git a/crates/ironrdp-pdu/src/rdp/headers.rs b/crates/ironrdp-pdu/src/rdp/headers.rs index 130ab98cf..2843e412a 100644 --- a/crates/ironrdp-pdu/src/rdp/headers.rs +++ b/crates/ironrdp-pdu/src/rdp/headers.rs @@ -1,4 +1,4 @@ -use std::io; +use std::io::{self, Read, Write}; use bitflags::bitflags; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; @@ -16,6 +16,8 @@ use crate::rdp::suppress_output::SuppressOutputPdu; use crate::rdp::{client_info, RdpError}; use crate::PduParsing; +use super::capability_sets::CapabilitySetsError; + pub const BASIC_SECURITY_HEADER_SIZE: usize = 4; pub const SHARE_DATA_HEADER_COMPRESSION_MASK: u8 = 0xF; const SHARE_CONTROL_HEADER_MASK: u16 = 0xF; @@ -133,6 +135,7 @@ pub enum ShareControlPdu { ServerDemandActive(ServerDemandActive), ClientConfirmActive(ClientConfirmActive), Data(ShareDataHeader), + ServerDeactivateAll(ServerDeactivateAll), } impl ShareControlPdu { @@ -141,6 +144,7 @@ impl ShareControlPdu { ShareControlPdu::ServerDemandActive(_) => "Server Demand Active PDU", ShareControlPdu::ClientConfirmActive(_) => "Client Confirm Active PDU", ShareControlPdu::Data(_) => "Data PDU", + ShareControlPdu::ServerDeactivateAll(_) => "Server Deactivate All PDU", } } } @@ -155,6 +159,9 @@ impl ShareControlPdu { ClientConfirmActive::from_buffer(&mut stream)?, )), ShareControlPduType::DataPdu => Ok(ShareControlPdu::Data(ShareDataHeader::from_buffer(&mut stream)?)), + ShareControlPduType::DeactivateAllPdu => Ok(ShareControlPdu::ServerDeactivateAll( + ServerDeactivateAll::from_buffer(&mut stream)?, + )), _ => Err(RdpError::UnexpectedShareControlPdu(share_type)), } } @@ -163,6 +170,7 @@ impl ShareControlPdu { ShareControlPdu::ServerDemandActive(pdu) => pdu.to_buffer(&mut stream).map_err(RdpError::from), ShareControlPdu::ClientConfirmActive(pdu) => pdu.to_buffer(&mut stream).map_err(RdpError::from), ShareControlPdu::Data(share_data_header) => share_data_header.to_buffer(&mut stream), + ShareControlPdu::ServerDeactivateAll(pdu) => pdu.to_buffer(&mut stream).map_err(RdpError::from), } } pub fn buffer_length(&self) -> usize { @@ -170,6 +178,7 @@ impl ShareControlPdu { ShareControlPdu::ServerDemandActive(pdu) => pdu.buffer_length(), ShareControlPdu::ClientConfirmActive(pdu) => pdu.buffer_length(), ShareControlPdu::Data(share_data_header) => share_data_header.buffer_length(), + ShareControlPdu::ServerDeactivateAll(pdu) => pdu.buffer_length(), } } pub fn share_header_type(&self) -> ShareControlPduType { @@ -177,6 +186,7 @@ impl ShareControlPdu { ShareControlPdu::ServerDemandActive(_) => ShareControlPduType::DemandActivePdu, ShareControlPdu::ClientConfirmActive(_) => ShareControlPduType::ConfirmActivePdu, ShareControlPdu::Data(_) => ShareControlPduType::DataPdu, + ShareControlPdu::ServerDeactivateAll(_) => ShareControlPduType::DeactivateAllPdu, } } } @@ -462,3 +472,34 @@ bitflags! { const FLUSHED = 0x80; } } + +/// 2.2.3.1 Server Deactivate All PDU +/// +/// [2.2.3.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/8a29971a-df3c-48da-add2-8ed9a05edc89 +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ServerDeactivateAll; + +impl PduParsing for ServerDeactivateAll { + type Error = CapabilitySetsError; + + fn from_buffer(mut stream: impl Read) -> Result { + // A 16-bit, unsigned integer. The size in bytes of the sourceDescriptor field. + let length_source_descriptor = stream.read_u16::()?; + let mut v = vec![0u8; length_source_descriptor.into()]; + // Variable number of bytes. The source descriptor. This field SHOULD be set to 0x00. + stream.read_exact(v.as_mut_slice())?; + Ok(ServerDeactivateAll {}) + } + + fn to_buffer(&self, mut stream: impl Write) -> Result<(), Self::Error> { + // A 16-bit, unsigned integer. The size in bytes of the sourceDescriptor field. + stream.write_u16::(1)?; + // Variable number of bytes. The source descriptor. This field SHOULD be set to 0x00. + stream.write_u8(0x00)?; + Ok(()) + } + + fn buffer_length(&self) -> usize { + 2 /* length_source_descriptor */ + 1 /* source_descriptor */ + } +} diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs index c042ba3b3..0e9ed9a1d 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs @@ -62,6 +62,9 @@ pub enum Orientation { PortraitFlipped = 270, } +/// [2.2.2.2.1] DISPLAYCONTROL_MONITOR_LAYOUT_PDU +/// +/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c #[derive(Debug, Clone, PartialEq, Eq)] pub struct Monitor { pub flags: MonitorFlags, @@ -130,6 +133,9 @@ impl PduParsing for Monitor { } } +/// [2.2.2.2] DISPLAYCONTROL_MONITOR_LAYOUT_PDU +/// +/// [2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/22741217-12a0-4fb8-b5a0-df43905aaf06 #[derive(Debug, Clone, PartialEq, Eq)] pub struct MonitorLayoutPdu { pub monitors: Vec, diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index e3e5f2c69..752fb10e4 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -5,7 +5,7 @@ use std::cmp; use std::collections::HashMap; use ironrdp_connector::legacy::SendDataIndicationCtx; -use ironrdp_connector::GraphicsConfig; +use ironrdp_connector::{ClientConnector, GraphicsConfig}; use ironrdp_pdu::dvc::FieldType; use ironrdp_pdu::mcs::{DisconnectProviderUltimatum, DisconnectReason, McsMessage}; use ironrdp_pdu::rdp::headers::ShareDataPdu; @@ -29,6 +29,8 @@ pub enum ProcessorOutput { ResponseFrame(Vec), /// A graceful disconnect notification. Client should close the connection upon receiving this. Disconnect(DisconnectReason), + /// Received a [`ironrdp_pdu::rdp::headers::ServerDeactivateAll`] PDU. + DeactivateAll(ClientConnector), } pub struct Processor { @@ -123,58 +125,63 @@ impl Processor { fn process_io_channel(&self, data_ctx: SendDataIndicationCtx<'_>) -> SessionResult> { debug_assert_eq!(data_ctx.channel_id, self.io_channel_id); - let ctx = ironrdp_connector::legacy::decode_share_data(data_ctx).map_err(crate::legacy::map_error)?; - - match ctx.pdu { - ShareDataPdu::SaveSessionInfo(session_info) => { - debug!("Got Session Save Info PDU: {session_info:?}"); - 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 graceful_disconnect = error_info_to_graceful_disconnect_reason(&e); - - if let Some(reason) = graceful_disconnect { - debug!("Received server-side graceful disconnect request: {reason}"); - - Ok(vec![ProcessorOutput::Disconnect(reason)]) - } else { - Err(reason_err!("ServerSetErrorInfo", "{}", e.description())) + let io_channel = ironrdp_connector::legacy::decode_io_channel(data_ctx).map_err(crate::legacy::map_error)?; + + match io_channel { + ironrdp_connector::legacy::IoChannelPdu::Data(ctx) => { + match ctx.pdu { + ShareDataPdu::SaveSessionInfo(session_info) => { + debug!("Got Session Save Info PDU: {session_info:?}"); + 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 graceful_disconnect = error_info_to_graceful_disconnect_reason(&e); + + if let Some(reason) = graceful_disconnect { + debug!("Received server-side graceful disconnect request: {reason}"); + + Ok(vec![ProcessorOutput::Disconnect(reason)]) + } else { + Err(reason_err!("ServerSetErrorInfo", "{}", e.description())) + } + } + 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_pdu::encode_vec(&ultimatum).map_err(SessionError::pdu); + + Ok(vec![ + ProcessorOutput::ResponseFrame(encoded_pdu?), + ProcessorOutput::Disconnect(DisconnectReason::UserRequested), + ]) + } + _ => Err(reason_err!( + "IO channel", + "unexpected PDU: expected Session Save Info PDU, got: {:?}", + ctx.pdu.as_short_name() + )), } } - 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_pdu::encode_vec(&ultimatum).map_err(SessionError::pdu); - - Ok(vec![ - ProcessorOutput::ResponseFrame(encoded_pdu?), - ProcessorOutput::Disconnect(DisconnectReason::UserRequested), - ]) - } - _ => Err(reason_err!( - "IO channel", - "unexpected PDU: expected Session Save Info PDU, got: {:?}", - ctx.pdu.as_short_name() - )), + ironrdp_connector::legacy::IoChannelPdu::DeactivateAll(_) => todo!(), } } From 4dbb2b3d5a2f52011822a7127187052fe4f3cb2c Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Sun, 10 Mar 2024 17:34:24 -0700 Subject: [PATCH 02/84] Abstracts the CapabilitiesExchange and ConnectionFinalization sequences into a separate connection_activation module. This allows us to reuse them when we receive a Server Deactivate All. --- crates/ironrdp-client/src/rdp.rs | 1 + crates/ironrdp-connector/src/connection.rs | 283 +++------------ .../src/connection_activation.rs | 341 ++++++++++++++++++ .../src/connection_finalization.rs | 4 +- crates/ironrdp-connector/src/lib.rs | 1 + crates/ironrdp-session/src/active_stage.rs | 3 + crates/ironrdp-session/src/x224/mod.rs | 5 +- 7 files changed, 396 insertions(+), 242 deletions(-) create mode 100644 crates/ironrdp-connector/src/connection_activation.rs diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index a7e5f2774..1f55eebc9 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -280,6 +280,7 @@ async fn active_session( ActiveStageOutput::PointerBitmap(_) => { // Not applicable, because we use the software cursor rendering. } + ActiveStageOutput::DeactivateAll(_) => todo!(), ActiveStageOutput::Terminate(reason) => break 'outer reason, } } diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 90c542811..71624f2da 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -1,21 +1,18 @@ use std::mem; use std::net::SocketAddr; -use ironrdp_pdu::rdp::capability_sets::CapabilitySet; use ironrdp_pdu::rdp::client_info::{PerformanceFlags, TimezoneInfo}; use ironrdp_pdu::write_buf::WriteBuf; use ironrdp_pdu::{gcc, mcs, nego, rdp, PduHint}; use ironrdp_svc::{StaticChannelSet, StaticVirtualChannel, SvcClientProcessor}; use crate::channel_connection::{ChannelConnectionSequence, ChannelConnectionState}; -use crate::connection_finalization::ConnectionFinalizationSequence; +use crate::connection_activation::{ConnectionActivationSequence, ConnectionActivationState}; use crate::license_exchange::LicenseExchangeSequence; use crate::{ legacy, Config, ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize, Sequence, State, Written, }; -const DEFAULT_POINTER_CACHE_SIZE: u16 = 32; - #[derive(Debug)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct ConnectionResult { @@ -73,14 +70,10 @@ pub enum ClientConnectorState { user_channel_id: u16, }, CapabilitiesExchange { - io_channel_id: u16, - user_channel_id: u16, + connection_activation: ConnectionActivationSequence, }, ConnectionFinalization { - io_channel_id: u16, - user_channel_id: u16, - desktop_size: DesktopSize, - connection_finalization: ConnectionFinalizationSequence, + connection_activation: ConnectionActivationSequence, }, Connected { result: ConnectionResult, @@ -102,8 +95,12 @@ impl State for ClientConnectorState { Self::ConnectTimeAutoDetection { .. } => "ConnectTimeAutoDetection", Self::LicensingExchange { .. } => "LicensingExchange", Self::MultitransportBootstrapping { .. } => "MultitransportBootstrapping", - Self::CapabilitiesExchange { .. } => "CapabilitiesExchange", - Self::ConnectionFinalization { .. } => "ConnectionFinalization", + Self::CapabilitiesExchange { + connection_activation, .. + } => connection_activation.state().name(), + Self::ConnectionFinalization { + connection_activation, .. + } => connection_activation.state().name(), Self::Connected { .. } => "Connected", } } @@ -201,11 +198,12 @@ impl Sequence for ClientConnector { ClientConnectorState::ConnectTimeAutoDetection { .. } => None, ClientConnectorState::LicensingExchange { license_exchange, .. } => license_exchange.next_pdu_hint(), ClientConnectorState::MultitransportBootstrapping { .. } => None, - ClientConnectorState::CapabilitiesExchange { .. } => Some(&ironrdp_pdu::X224_HINT), + ClientConnectorState::CapabilitiesExchange { + connection_activation, .. + } => connection_activation.next_pdu_hint(), ClientConnectorState::ConnectionFinalization { - connection_finalization, - .. - } => connection_finalization.next_pdu_hint(), + connection_activation, .. + } => connection_activation.next_pdu_hint(), ClientConnectorState::Connected { .. } => None, } } @@ -511,120 +509,57 @@ impl Sequence for ClientConnector { } => ( Written::Nothing, ClientConnectorState::CapabilitiesExchange { - io_channel_id, - user_channel_id, + connection_activation: ConnectionActivationSequence::new( + self.config.clone(), + io_channel_id, + user_channel_id, + ), }, ), //== Capabilities Exchange ==/ // The server sends the set of capabilities it supports to the client. ClientConnectorState::CapabilitiesExchange { - io_channel_id, - user_channel_id, + mut connection_activation, } => { - debug!("Capabilities Exchange"); - - let send_data_indication_ctx = legacy::decode_send_data_indication(input)?; - let share_control_ctx = legacy::decode_share_control(send_data_indication_ctx)?; - - debug!(message = ?share_control_ctx.pdu, "Received"); - - if share_control_ctx.channel_id != io_channel_id { - warn!( - io_channel_id, - share_control_ctx.channel_id, "Unexpected channel ID for received Share Control Pdu" - ); - } - - let capability_sets = if let rdp::headers::ShareControlPdu::ServerDemandActive(server_demand_active) = - share_control_ctx.pdu - { - server_demand_active.pdu.capability_sets - } else { - return Err(general_err!( - "unexpected Share Control Pdu (expected ServerDemandActive)", - )); - }; - - for c in &capability_sets { - if let rdp::capability_sets::CapabilitySet::General(g) = c { - if g.protocol_version != rdp::capability_sets::PROTOCOL_VER { - warn!(version = g.protocol_version, "Unexpected protocol version"); - } - break; - } + let written = connection_activation.step(input, output)?; + match connection_activation.state { + ConnectionActivationState::ConnectionFinalization { .. } => ( + written, + ClientConnectorState::ConnectionFinalization { connection_activation }, + ), + _ => return Err(general_err!("invalid state (this is a bug)")), } - - let desktop_size = capability_sets - .iter() - .find_map(|c| match c { - rdp::capability_sets::CapabilitySet::Bitmap(b) => Some(DesktopSize { - width: b.desktop_width, - height: b.desktop_height, - }), - _ => None, - }) - .unwrap_or(DesktopSize { - width: self.config.desktop_size.width, - height: self.config.desktop_size.height, - }); - - let client_confirm_active = rdp::headers::ShareControlPdu::ClientConfirmActive( - create_client_confirm_active(&self.config, capability_sets), - ); - - debug!(message = ?client_confirm_active, "Send"); - - let written = legacy::encode_share_control( - user_channel_id, - io_channel_id, - share_control_ctx.share_id, - client_confirm_active, - output, - )?; - - ( - Written::from_size(written)?, - ClientConnectorState::ConnectionFinalization { - io_channel_id, - user_channel_id, - desktop_size, - connection_finalization: ConnectionFinalizationSequence::new(io_channel_id, user_channel_id), - }, - ) } //== Connection Finalization ==// // Client and server exchange a few PDUs in order to finalize the connection. // Client may send PDUs one after the other without waiting for a response in order to speed up the process. ClientConnectorState::ConnectionFinalization { - io_channel_id, - user_channel_id, - desktop_size, - mut connection_finalization, + mut connection_activation, } => { - debug!("Connection Finalization"); - - let written = connection_finalization.step(input, output)?; + let written = connection_activation.step(input, output)?; - let next_state = if connection_finalization.state.is_terminal() { - ClientConnectorState::Connected { - result: ConnectionResult { + let next_state = if !connection_activation.state.is_terminal() { + ClientConnectorState::ConnectionFinalization { connection_activation } + } else { + match connection_activation.state { + ConnectionActivationState::Finalized { io_channel_id, user_channel_id, - static_channels: mem::take(&mut self.static_channels), desktop_size, - graphics_config: self.config.graphics.clone(), - no_server_pointer: self.config.no_server_pointer, - pointer_software_rendering: self.config.pointer_software_rendering, + } => ClientConnectorState::Connected { + result: ConnectionResult { + io_channel_id, + user_channel_id, + static_channels: mem::take(&mut self.static_channels), + desktop_size, + graphics_config: self.config.graphics.clone(), + no_server_pointer: self.config.no_server_pointer, + pointer_software_rendering: self.config.pointer_software_rendering, + }, }, - } - } else { - ClientConnectorState::ConnectionFinalization { - io_channel_id, - user_channel_id, - desktop_size, - connection_finalization, + _ => return Err(general_err!("invalid state (this is a bug)")), } }; @@ -804,131 +739,3 @@ fn create_client_info_pdu(config: &Config, routing_addr: &SocketAddr) -> rdp::Cl client_info, } } - -fn create_client_confirm_active( - config: &Config, - mut server_capability_sets: Vec, -) -> rdp::capability_sets::ClientConfirmActive { - use ironrdp_pdu::rdp::capability_sets::*; - - server_capability_sets.retain(|capability_set| matches!(capability_set, CapabilitySet::MultiFragmentUpdate(_))); - - let lossy_bitmap_compression = config - .bitmap - .as_ref() - .map(|bitmap| bitmap.lossy_compression) - .unwrap_or(false); - - let drawing_flags = if lossy_bitmap_compression { - BitmapDrawingFlags::ALLOW_SKIP_ALPHA - | BitmapDrawingFlags::ALLOW_DYNAMIC_COLOR_FIDELITY - | BitmapDrawingFlags::ALLOW_COLOR_SUBSAMPLING - } else { - BitmapDrawingFlags::ALLOW_SKIP_ALPHA - }; - - server_capability_sets.extend_from_slice(&[ - CapabilitySet::General(General { - major_platform_type: config.platform, - extra_flags: GeneralExtraFlags::FASTPATH_OUTPUT_SUPPORTED | GeneralExtraFlags::NO_BITMAP_COMPRESSION_HDR, - ..Default::default() - }), - CapabilitySet::Bitmap(Bitmap { - pref_bits_per_pix: 32, - desktop_width: config.desktop_size.width, - desktop_height: config.desktop_size.height, - desktop_resize_flag: false, - drawing_flags, - }), - CapabilitySet::Order(Order::new( - OrderFlags::NEGOTIATE_ORDER_SUPPORT | OrderFlags::ZERO_BOUNDS_DELTAS_SUPPORT, - OrderSupportExFlags::empty(), - 0, - 0, - )), - CapabilitySet::BitmapCache(BitmapCache { - caches: [CacheEntry { - entries: 0, - max_cell_size: 0, - }; BITMAP_CACHE_ENTRIES_NUM], - }), - CapabilitySet::Input(Input { - input_flags: InputFlags::all(), - keyboard_layout: 0, - keyboard_type: Some(config.keyboard_type), - keyboard_subtype: config.keyboard_subtype, - keyboard_function_key: config.keyboard_functional_keys_count, - keyboard_ime_filename: config.ime_file_name.clone(), - }), - CapabilitySet::Pointer(Pointer { - // Pointer cache should be set to non-zero value to enable client-side pointer rendering. - color_pointer_cache_size: DEFAULT_POINTER_CACHE_SIZE, - pointer_cache_size: DEFAULT_POINTER_CACHE_SIZE, - }), - CapabilitySet::Brush(Brush { - support_level: SupportLevel::Default, - }), - CapabilitySet::GlyphCache(GlyphCache { - glyph_cache: [CacheDefinition { - entries: 0, - max_cell_size: 0, - }; GLYPH_CACHE_NUM], - frag_cache: CacheDefinition { - entries: 0, - max_cell_size: 0, - }, - glyph_support_level: GlyphSupportLevel::None, - }), - CapabilitySet::OffscreenBitmapCache(OffscreenBitmapCache { - is_supported: false, - cache_size: 0, - cache_entries: 0, - }), - CapabilitySet::VirtualChannel(VirtualChannel { - flags: VirtualChannelFlags::NO_COMPRESSION, - chunk_size: Some(0), // ignored - }), - CapabilitySet::Sound(Sound { - flags: SoundFlags::empty(), - }), - CapabilitySet::LargePointer(LargePointer { - // Setting `LargePointerSupportFlags::UP_TO_384X384_PIXELS` allows server to send - // `TS_FP_LARGEPOINTERATTRIBUTE` update messages, which are required for client-side - // rendering of pointers bigger than 96x96 pixels. - flags: LargePointerSupportFlags::UP_TO_384X384_PIXELS, - }), - CapabilitySet::SurfaceCommands(SurfaceCommands { - flags: CmdFlags::SET_SURFACE_BITS | CmdFlags::STREAM_SURFACE_BITS | CmdFlags::FRAME_MARKER, - }), - CapabilitySet::BitmapCodecs(BitmapCodecs(vec![Codec { - id: 0x03, // RemoteFX - property: CodecProperty::RemoteFx(RemoteFxContainer::ClientContainer(RfxClientCapsContainer { - capture_flags: CaptureFlags::empty(), - caps_data: RfxCaps(RfxCapset(vec![RfxICap { - flags: RfxICapFlags::empty(), - entropy_bits: EntropyBits::Rlgr3, - }])), - })), - }])), - CapabilitySet::FrameAcknowledge(FrameAcknowledge { - max_unacknowledged_frame_count: 2, - }), - ]); - - if !server_capability_sets - .iter() - .any(|c| matches!(&c, CapabilitySet::MultiFragmentUpdate(_))) - { - server_capability_sets.push(CapabilitySet::MultiFragmentUpdate(MultifragmentUpdate { - max_request_size: 1024, - })); - } - - ClientConfirmActive { - originator_id: SERVER_CHANNEL_ID, - pdu: DemandActive { - source_descriptor: "IRONRDP".to_owned(), - capability_sets: server_capability_sets, - }, - } -} diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs new file mode 100644 index 000000000..9332e3a68 --- /dev/null +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -0,0 +1,341 @@ +use std::mem; + +use ironrdp_pdu::rdp::{self, capability_sets::CapabilitySet}; + +use crate::{legacy, Config, ConnectionFinalizationSequence, ConnectorResult, DesktopSize, Sequence, State, Written}; + +/// Represents the Capability Exchange and Connection Finalization phases +/// of the connection sequence (section [1.3.1.1]). +/// +/// This is abstracted into its own struct to allow it to be used for the ordinary +/// RDP connection sequence [`ClientConnector`] that occurs for every RDP connection, +/// as well as the Deactivation-Reactivation Sequence ([1.3.1.3]) that occurs when +/// a [Server Deactivate All PDU] is received. +/// +/// [1.3.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/023f1e69-cfe8-4ee6-9ee0-7e759fb4e4ee +/// [1.3.1.3]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dfc234ce-481a-4674-9a5d-2a7bafb14432 +/// [`ClientConnector`]: crate::ClientConnector +/// [Server Deactivate All PDU]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/8a29971a-df3c-48da-add2-8ed9a05edc89 +#[derive(Debug, Clone)] +pub struct ConnectionActivationSequence { + pub state: ConnectionActivationState, + pub config: Config, +} + +impl ConnectionActivationSequence { + pub fn new(config: Config, io_channel_id: u16, user_channel_id: u16) -> Self { + Self { + state: ConnectionActivationState::CapabilitiesExchange { + io_channel_id, + user_channel_id, + }, + config, + } + } +} + +impl Sequence for ConnectionActivationSequence { + fn next_pdu_hint(&self) -> Option<&dyn ironrdp_pdu::PduHint> { + match &self.state { + ConnectionActivationState::Consumed => None, + ConnectionActivationState::Finalized { .. } => None, + ConnectionActivationState::CapabilitiesExchange { .. } => Some(&ironrdp_pdu::X224_HINT), + ConnectionActivationState::ConnectionFinalization { + connection_finalization, + .. + } => connection_finalization.next_pdu_hint(), + } + } + + fn state(&self) -> &dyn State { + &self.state + } + + fn step(&mut self, input: &[u8], output: &mut ironrdp_pdu::write_buf::WriteBuf) -> ConnectorResult { + let (written, next_state) = match mem::take(&mut self.state) { + // Invalid state + ConnectionActivationState::Consumed => { + return Err(general_err!("connector sequence state is consumed (this is a bug)")) + } + ConnectionActivationState::Finalized { .. } => { + return Err(general_err!("connector sequence state is finalized (this is a bug)")) + } + ConnectionActivationState::CapabilitiesExchange { + io_channel_id, + user_channel_id, + } => { + debug!("Capabilities Exchange"); + + let send_data_indication_ctx = legacy::decode_send_data_indication(input)?; + let share_control_ctx = legacy::decode_share_control(send_data_indication_ctx)?; + + debug!(message = ?share_control_ctx.pdu, "Received"); + + if share_control_ctx.channel_id != io_channel_id { + warn!( + io_channel_id, + share_control_ctx.channel_id, "Unexpected channel ID for received Share Control Pdu" + ); + } + + let capability_sets = if let rdp::headers::ShareControlPdu::ServerDemandActive(server_demand_active) = + share_control_ctx.pdu + { + server_demand_active.pdu.capability_sets + } else { + return Err(general_err!( + "unexpected Share Control Pdu (expected ServerDemandActive)", + )); + }; + + for c in &capability_sets { + if let rdp::capability_sets::CapabilitySet::General(g) = c { + if g.protocol_version != rdp::capability_sets::PROTOCOL_VER { + warn!(version = g.protocol_version, "Unexpected protocol version"); + } + break; + } + } + + let desktop_size = capability_sets + .iter() + .find_map(|c| match c { + rdp::capability_sets::CapabilitySet::Bitmap(b) => Some(DesktopSize { + width: b.desktop_width, + height: b.desktop_height, + }), + _ => None, + }) + .unwrap_or(DesktopSize { + width: self.config.desktop_size.width, + height: self.config.desktop_size.height, + }); + + let client_confirm_active = rdp::headers::ShareControlPdu::ClientConfirmActive( + create_client_confirm_active(&self.config, capability_sets), + ); + + debug!(message = ?client_confirm_active, "Send"); + + let written = legacy::encode_share_control( + user_channel_id, + io_channel_id, + share_control_ctx.share_id, + client_confirm_active, + output, + )?; + + ( + Written::from_size(written)?, + ConnectionActivationState::ConnectionFinalization { + io_channel_id, + user_channel_id, + desktop_size, + connection_finalization: ConnectionFinalizationSequence::new(io_channel_id, user_channel_id), + }, + ) + } + ConnectionActivationState::ConnectionFinalization { + io_channel_id, + user_channel_id, + desktop_size, + mut connection_finalization, + } => { + debug!("Connection Finalization"); + + let written = connection_finalization.step(input, output)?; + + let next_state = if !connection_finalization.state.is_terminal() { + ConnectionActivationState::ConnectionFinalization { + io_channel_id, + user_channel_id, + desktop_size, + connection_finalization, + } + } else { + ConnectionActivationState::Finalized { + io_channel_id, + user_channel_id, + desktop_size, + } + }; + + (written, next_state) + } + }; + + self.state = next_state; + + Ok(written) + } +} + +#[derive(Default, Debug, Clone)] +pub enum ConnectionActivationState { + #[default] + Consumed, + CapabilitiesExchange { + io_channel_id: u16, + user_channel_id: u16, + }, + ConnectionFinalization { + io_channel_id: u16, + user_channel_id: u16, + desktop_size: DesktopSize, + connection_finalization: ConnectionFinalizationSequence, + }, + Finalized { + io_channel_id: u16, + user_channel_id: u16, + desktop_size: DesktopSize, + }, +} + +impl State for ConnectionActivationState { + fn name(&self) -> &'static str { + match self { + ConnectionActivationState::Consumed => "Consumed", + ConnectionActivationState::CapabilitiesExchange { .. } => "CapabilitiesExchange", + ConnectionActivationState::ConnectionFinalization { .. } => "ConnectionFinalization", + ConnectionActivationState::Finalized { .. } => "Finalized", + } + } + + fn is_terminal(&self) -> bool { + matches!(self, ConnectionActivationState::Finalized { .. }) + } + + fn as_any(&self) -> &dyn core::any::Any { + self + } +} + +const DEFAULT_POINTER_CACHE_SIZE: u16 = 32; + +fn create_client_confirm_active( + config: &Config, + mut server_capability_sets: Vec, +) -> rdp::capability_sets::ClientConfirmActive { + use ironrdp_pdu::rdp::capability_sets::*; + + server_capability_sets.retain(|capability_set| matches!(capability_set, CapabilitySet::MultiFragmentUpdate(_))); + + let lossy_bitmap_compression = config + .bitmap + .as_ref() + .map(|bitmap| bitmap.lossy_compression) + .unwrap_or(false); + + let drawing_flags = if lossy_bitmap_compression { + BitmapDrawingFlags::ALLOW_SKIP_ALPHA + | BitmapDrawingFlags::ALLOW_DYNAMIC_COLOR_FIDELITY + | BitmapDrawingFlags::ALLOW_COLOR_SUBSAMPLING + } else { + BitmapDrawingFlags::ALLOW_SKIP_ALPHA + }; + + server_capability_sets.extend_from_slice(&[ + CapabilitySet::General(General { + major_platform_type: config.platform, + extra_flags: GeneralExtraFlags::FASTPATH_OUTPUT_SUPPORTED | GeneralExtraFlags::NO_BITMAP_COMPRESSION_HDR, + ..Default::default() + }), + CapabilitySet::Bitmap(Bitmap { + pref_bits_per_pix: 32, + desktop_width: config.desktop_size.width, + desktop_height: config.desktop_size.height, + desktop_resize_flag: false, + drawing_flags, + }), + CapabilitySet::Order(Order::new( + OrderFlags::NEGOTIATE_ORDER_SUPPORT | OrderFlags::ZERO_BOUNDS_DELTAS_SUPPORT, + OrderSupportExFlags::empty(), + 0, + 0, + )), + CapabilitySet::BitmapCache(BitmapCache { + caches: [CacheEntry { + entries: 0, + max_cell_size: 0, + }; BITMAP_CACHE_ENTRIES_NUM], + }), + CapabilitySet::Input(Input { + input_flags: InputFlags::all(), + keyboard_layout: 0, + keyboard_type: Some(config.keyboard_type), + keyboard_subtype: config.keyboard_subtype, + keyboard_function_key: config.keyboard_functional_keys_count, + keyboard_ime_filename: config.ime_file_name.clone(), + }), + CapabilitySet::Pointer(Pointer { + // Pointer cache should be set to non-zero value to enable client-side pointer rendering. + color_pointer_cache_size: DEFAULT_POINTER_CACHE_SIZE, + pointer_cache_size: DEFAULT_POINTER_CACHE_SIZE, + }), + CapabilitySet::Brush(Brush { + support_level: SupportLevel::Default, + }), + CapabilitySet::GlyphCache(GlyphCache { + glyph_cache: [CacheDefinition { + entries: 0, + max_cell_size: 0, + }; GLYPH_CACHE_NUM], + frag_cache: CacheDefinition { + entries: 0, + max_cell_size: 0, + }, + glyph_support_level: GlyphSupportLevel::None, + }), + CapabilitySet::OffscreenBitmapCache(OffscreenBitmapCache { + is_supported: false, + cache_size: 0, + cache_entries: 0, + }), + CapabilitySet::VirtualChannel(VirtualChannel { + flags: VirtualChannelFlags::NO_COMPRESSION, + chunk_size: Some(0), // ignored + }), + CapabilitySet::Sound(Sound { + flags: SoundFlags::empty(), + }), + CapabilitySet::LargePointer(LargePointer { + // Setting `LargePointerSupportFlags::UP_TO_384X384_PIXELS` allows server to send + // `TS_FP_LARGEPOINTERATTRIBUTE` update messages, which are required for client-side + // rendering of pointers bigger than 96x96 pixels. + flags: LargePointerSupportFlags::UP_TO_384X384_PIXELS, + }), + CapabilitySet::SurfaceCommands(SurfaceCommands { + flags: CmdFlags::SET_SURFACE_BITS | CmdFlags::STREAM_SURFACE_BITS | CmdFlags::FRAME_MARKER, + }), + CapabilitySet::BitmapCodecs(BitmapCodecs(vec![Codec { + id: 0x03, // RemoteFX + property: CodecProperty::RemoteFx(RemoteFxContainer::ClientContainer(RfxClientCapsContainer { + capture_flags: CaptureFlags::empty(), + caps_data: RfxCaps(RfxCapset(vec![RfxICap { + flags: RfxICapFlags::empty(), + entropy_bits: EntropyBits::Rlgr3, + }])), + })), + }])), + CapabilitySet::FrameAcknowledge(FrameAcknowledge { + max_unacknowledged_frame_count: 2, + }), + ]); + + if !server_capability_sets + .iter() + .any(|c| matches!(&c, CapabilitySet::MultiFragmentUpdate(_))) + { + server_capability_sets.push(CapabilitySet::MultiFragmentUpdate(MultifragmentUpdate { + max_request_size: 1024, + })); + } + + ClientConfirmActive { + originator_id: SERVER_CHANNEL_ID, + pdu: DemandActive { + source_descriptor: "IRONRDP".to_owned(), + capability_sets: server_capability_sets, + }, + } +} diff --git a/crates/ironrdp-connector/src/connection_finalization.rs b/crates/ironrdp-connector/src/connection_finalization.rs index 2136cb13b..2932bb8ba 100644 --- a/crates/ironrdp-connector/src/connection_finalization.rs +++ b/crates/ironrdp-connector/src/connection_finalization.rs @@ -8,7 +8,7 @@ use ironrdp_pdu::PduHint; use crate::{legacy, ConnectorResult, Sequence, State, Written}; -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] #[non_exhaustive] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum ConnectionFinalizationState { @@ -47,7 +47,7 @@ impl State for ConnectionFinalizationState { } } -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct ConnectionFinalizationSequence { pub state: ConnectionFinalizationState, diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index 379c3ab6b..2635db9b7 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -11,6 +11,7 @@ pub mod legacy; mod channel_connection; mod connection; +pub mod connection_activation; mod connection_finalization; pub mod credssp; mod license_exchange; diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index 3ed056aa8..8e0353999 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -1,5 +1,6 @@ use std::rc::Rc; +use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::ConnectionResult; use ironrdp_graphics::pointer::DecodedPointer; use ironrdp_pdu::geometry::InclusiveRectangle; @@ -201,6 +202,7 @@ pub enum ActiveStageOutput { PointerPosition { x: u16, y: u16 }, PointerBitmap(Rc), Terminate(GracefulDisconnectReason), + DeactivateAll(ConnectionActivationSequence), } impl TryFrom for ActiveStageOutput { @@ -218,6 +220,7 @@ impl TryFrom for ActiveStageOutput { Ok(Self::Terminate(reason)) } + x224::ProcessorOutput::DeactivateAll(cas) => Ok(Self::DeactivateAll(cas)), } } } diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index 752fb10e4..ac2709f53 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -4,8 +4,9 @@ mod gfx; use std::cmp; use std::collections::HashMap; +use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::legacy::SendDataIndicationCtx; -use ironrdp_connector::{ClientConnector, GraphicsConfig}; +use ironrdp_connector::GraphicsConfig; use ironrdp_pdu::dvc::FieldType; use ironrdp_pdu::mcs::{DisconnectProviderUltimatum, DisconnectReason, McsMessage}; use ironrdp_pdu::rdp::headers::ShareDataPdu; @@ -30,7 +31,7 @@ pub enum ProcessorOutput { /// A graceful disconnect notification. Client should close the connection upon receiving this. Disconnect(DisconnectReason), /// Received a [`ironrdp_pdu::rdp::headers::ServerDeactivateAll`] PDU. - DeactivateAll(ClientConnector), + DeactivateAll(ConnectionActivationSequence), } pub struct Processor { From 08dfb44f7ec54cc6c6dc46a43165316cb9f21a8e Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 11 Mar 2024 12:39:44 -0700 Subject: [PATCH 03/84] Breaks single_connect_step into two separate methods: `single_connect_step_read` and `single_connect_step_write`. Also adds a `ConnectionActivationSequence::reset_clone` method to aid in reusing the sequence for Deactivate All PDU handling. Passes `ConnectionActivationSequence` to `x224::Processor` to hand back upon receiving a Deactivate All PDU. --- crates/ironrdp-async/src/connector.rs | 36 ++++++++++++---- crates/ironrdp-connector/src/connection.rs | 2 + .../src/connection_activation.rs | 41 ++++++++++++++++--- crates/ironrdp-session/src/active_stage.rs | 1 + crates/ironrdp-session/src/x224/mod.rs | 7 +++- 5 files changed, 73 insertions(+), 14 deletions(-) diff --git a/crates/ironrdp-async/src/connector.rs b/crates/ironrdp-async/src/connector.rs index bf5fc48eb..d8d0e8d2f 100644 --- a/crates/ironrdp-async/src/connector.rs +++ b/crates/ironrdp-async/src/connector.rs @@ -2,8 +2,8 @@ use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, Kerbe use ironrdp_connector::sspi::credssp::ClientState; use ironrdp_connector::sspi::generator::GeneratorState; use ironrdp_connector::{ - custom_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, - Sequence as _, ServerName, State as _, + custom_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, Sequence, + ServerName, State as _, Written, }; use ironrdp_pdu::write_buf::WriteBuf; @@ -187,10 +187,23 @@ where S: FramedWrite + FramedRead, { buf.clear(); + let written = single_connect_step_read(framed, connector, buf).await?; + single_connect_step_write(framed, buf, written).await +} + +pub async fn single_connect_step_read( + framed: &mut Framed, + connector: &mut dyn Sequence, + buf: &mut WriteBuf, +) -> ConnectorResult +where + S: FramedRead, +{ + buf.clear(); - let written = if let Some(next_pdu_hint) = connector.next_pdu_hint() { + if let Some(next_pdu_hint) = connector.next_pdu_hint() { debug!( - connector.state = connector.state.name(), + connector.state = connector.state().name(), hint = ?next_pdu_hint, "Wait for PDU" ); @@ -202,11 +215,20 @@ where trace!(length = pdu.len(), "PDU received"); - connector.step(&pdu, buf)? + connector.step(&pdu, buf) } else { - connector.step_no_input(buf)? - }; + connector.step_no_input(buf) + } +} +async fn single_connect_step_write( + framed: &mut Framed, + buf: &mut WriteBuf, + written: Written, +) -> ConnectorResult<()> +where + S: FramedWrite, +{ if let Some(response_len) = written.size() { debug_assert_eq!(buf.filled_len(), response_len); let response = buf.filled(); diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 71624f2da..2e989a9db 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -23,6 +23,7 @@ pub struct ConnectionResult { pub graphics_config: Option, pub no_server_pointer: bool, pub pointer_software_rendering: bool, + pub connection_activation: ConnectionActivationSequence, } #[derive(Default, Debug)] @@ -557,6 +558,7 @@ impl Sequence for ClientConnector { graphics_config: self.config.graphics.clone(), no_server_pointer: self.config.no_server_pointer, pointer_software_rendering: self.config.pointer_software_rendering, + connection_activation, }, }, _ => return Err(general_err!("invalid state (this is a bug)")), diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs index 9332e3a68..656a0ec9d 100644 --- a/crates/ironrdp-connector/src/connection_activation.rs +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -32,6 +32,37 @@ impl ConnectionActivationSequence { config, } } + + pub fn reset_clone(&self) -> Self { + self.clone().reset() + } + + fn reset(mut self) -> Self { + match &self.state { + ConnectionActivationState::CapabilitiesExchange { + io_channel_id, + user_channel_id, + } + | ConnectionActivationState::ConnectionFinalization { + io_channel_id, + user_channel_id, + .. + } + | ConnectionActivationState::Finalized { + io_channel_id, + user_channel_id, + .. + } => { + self.state = ConnectionActivationState::CapabilitiesExchange { + io_channel_id: *io_channel_id, + user_channel_id: *user_channel_id, + }; + + self + } + ConnectionActivationState::Consumed => self, + } + } } impl Sequence for ConnectionActivationSequence { @@ -53,12 +84,10 @@ impl Sequence for ConnectionActivationSequence { fn step(&mut self, input: &[u8], output: &mut ironrdp_pdu::write_buf::WriteBuf) -> ConnectorResult { let (written, next_state) = match mem::take(&mut self.state) { - // Invalid state - ConnectionActivationState::Consumed => { - return Err(general_err!("connector sequence state is consumed (this is a bug)")) - } - ConnectionActivationState::Finalized { .. } => { - return Err(general_err!("connector sequence state is finalized (this is a bug)")) + ConnectionActivationState::Consumed | ConnectionActivationState::Finalized { .. } => { + return Err(general_err!( + "connector sequence state is finalized or consumed (this is a bug)" + )); } ConnectionActivationState::CapabilitiesExchange { io_channel_id, diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index 8e0353999..8bf8886f8 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -29,6 +29,7 @@ impl ActiveStage { connection_result.io_channel_id, connection_result.graphics_config, graphics_handler, + connection_result.connection_activation, ); let fast_path_processor = fast_path::ProcessorBuilder { diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index ac2709f53..acd16d512 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -45,6 +45,7 @@ pub struct Processor { drdynvc_initialized: bool, graphics_config: Option, graphics_handler: Option>, + connection_activation: ConnectionActivationSequence, } impl Processor { @@ -54,6 +55,7 @@ impl Processor { io_channel_id: u16, graphics_config: Option, graphics_handler: Option>, + connection_activation: ConnectionActivationSequence, ) -> Self { let drdynvc_channel_id = static_channels.iter().find_map(|(type_id, channel)| { if channel.is_drdynvc() { @@ -73,6 +75,7 @@ impl Processor { drdynvc_initialized: false, graphics_config, graphics_handler, + connection_activation, } } @@ -182,7 +185,9 @@ impl Processor { )), } } - ironrdp_connector::legacy::IoChannelPdu::DeactivateAll(_) => todo!(), + ironrdp_connector::legacy::IoChannelPdu::DeactivateAll(_) => Ok(vec![ProcessorOutput::DeactivateAll( + self.connection_activation.reset_clone(), + )]), } } From 55159db98a4757e656cfceebbfff1bd821561991 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 11 Mar 2024 18:30:40 -0700 Subject: [PATCH 04/84] Sets desktop_resize_flag to true which is required for the Microsoft::Windows::RDS::DisplayControl DVC to work. --- crates/ironrdp-connector/src/connection_activation.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs index 656a0ec9d..bce969062 100644 --- a/crates/ironrdp-connector/src/connection_activation.rs +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -273,7 +273,8 @@ fn create_client_confirm_active( pref_bits_per_pix: 32, desktop_width: config.desktop_size.width, desktop_height: config.desktop_size.height, - desktop_resize_flag: false, + // This is required to be true in order for the Microsoft::Windows::RDS::DisplayControl DVC to work. + desktop_resize_flag: true, drawing_flags, }), CapabilitySet::Order(Order::new( From 89bef14c5b25819223c71b2245f2c89597667bb7 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 11 Mar 2024 21:45:00 -0700 Subject: [PATCH 05/84] Removes the explicit state machine from DecodingContext. State comes to us from the server via the BlockType in the BlockHeader, so it needn't be rigidly tracked explicitly in DecodingContext. This makes the RFX pipeline more flexible, which in turn allows us to seamlessly use it when we get another sync sequence mid-stream due to having fielded a Server Deactivate PDU. --- crates/ironrdp-pdu/src/codecs/rfx.rs | 16 ++++++- .../src/codecs/rfx/data_messages.rs | 16 ++++--- .../src/codecs/rfx/header_messages.rs | 16 ++++--- crates/ironrdp-session/src/rfx.rs | 44 +++++++++---------- crates/ironrdp-session/src/x224/mod.rs | 5 ++- 5 files changed, 62 insertions(+), 35 deletions(-) diff --git a/crates/ironrdp-pdu/src/codecs/rfx.rs b/crates/ironrdp-pdu/src/codecs/rfx.rs index 7a1b17b75..df4a3910a 100644 --- a/crates/ironrdp-pdu/src/codecs/rfx.rs +++ b/crates/ironrdp-pdu/src/codecs/rfx.rs @@ -73,6 +73,11 @@ pub struct BlockHeader { } impl BlockHeader { + pub fn from_buffer_consume(buffer: &mut &[u8]) -> Result { + let ty = BlockType::from_buffer(buffer)?; + Self::from_buffer_consume_with_type(buffer, ty) + } + fn from_buffer_consume_with_type(buffer: &mut &[u8], ty: BlockType) -> Result { let block_length = buffer.read_u32::()? as usize; @@ -95,8 +100,7 @@ impl BlockHeader { } fn from_buffer_consume_with_expected_type(buffer: &mut &[u8], expected_type: BlockType) -> Result { - let ty = buffer.read_u16::()?; - let ty = BlockType::from_u16(ty).ok_or(RfxError::InvalidBlockType(ty))?; + let ty = BlockType::from_buffer(buffer)?; if ty != expected_type { return Err(RfxError::UnexpectedBlockType { expected: expected_type, @@ -204,6 +208,14 @@ pub enum BlockType { Extension = 0xCCC7, } +impl BlockType { + fn from_buffer(buffer: &mut &[u8]) -> Result { + let ty = buffer.read_u16::()?; + let ty = BlockType::from_u16(ty).ok_or(RfxError::InvalidBlockType(ty))?; + Ok(ty) + } +} + #[derive(Debug, Error)] pub enum RfxError { #[error("IO error")] diff --git a/crates/ironrdp-pdu/src/codecs/rfx/data_messages.rs b/crates/ironrdp-pdu/src/codecs/rfx/data_messages.rs index 02d0f9900..5f378f7eb 100644 --- a/crates/ironrdp-pdu/src/codecs/rfx/data_messages.rs +++ b/crates/ironrdp-pdu/src/codecs/rfx/data_messages.rs @@ -120,11 +120,8 @@ pub struct FrameBeginPdu { pub number_of_regions: i16, } -impl PduBufferParsing<'_> for FrameBeginPdu { - type Error = RfxError; - - fn from_buffer_consume(buffer: &mut &[u8]) -> Result { - let header = BlockHeader::from_buffer_consume_with_expected_type(buffer, BlockType::FrameBegin)?; +impl FrameBeginPdu { + pub fn from_buffer_consume_with_header(buffer: &mut &[u8], header: BlockHeader) -> Result { CodecChannelHeader::from_buffer_consume_with_type(buffer, BlockType::FrameBegin)?; let mut buffer = buffer.split_to(header.data_length); @@ -136,6 +133,15 @@ impl PduBufferParsing<'_> for FrameBeginPdu { number_of_regions, }) } +} + +impl PduBufferParsing<'_> for FrameBeginPdu { + type Error = RfxError; + + fn from_buffer_consume(buffer: &mut &[u8]) -> Result { + let header = BlockHeader::from_buffer_consume_with_expected_type(buffer, BlockType::FrameBegin)?; + Self::from_buffer_consume_with_header(buffer, header) + } fn to_buffer_consume(&self, buffer: &mut &mut [u8]) -> Result<(), Self::Error> { let header = BlockHeader { diff --git a/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs b/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs index 07754c7da..67dc717af 100644 --- a/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs +++ b/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs @@ -16,11 +16,8 @@ const CHANNEL_SIZE: usize = 5; #[derive(Debug, Clone, PartialEq, Eq)] pub struct SyncPdu; -impl PduBufferParsing<'_> for SyncPdu { - type Error = RfxError; - - fn from_buffer_consume(buffer: &mut &[u8]) -> Result { - let header = BlockHeader::from_buffer_consume_with_expected_type(buffer, BlockType::Sync)?; +impl SyncPdu { + pub fn from_buffer_consume_with_header(buffer: &mut &[u8], header: BlockHeader) -> Result { let mut buffer = buffer.split_to(header.data_length); let magic = buffer.read_u32::()?; @@ -34,6 +31,15 @@ impl PduBufferParsing<'_> for SyncPdu { Ok(Self) } } +} + +impl PduBufferParsing<'_> for SyncPdu { + type Error = RfxError; + + fn from_buffer_consume(buffer: &mut &[u8]) -> Result { + let header = BlockHeader::from_buffer_consume_with_expected_type(buffer, BlockType::Sync)?; + Self::from_buffer_consume_with_header(buffer, header) + } fn to_buffer_consume(&self, buffer: &mut &mut [u8]) -> Result<(), Self::Error> { let header = BlockHeader { diff --git a/crates/ironrdp-session/src/rfx.rs b/crates/ironrdp-session/src/rfx.rs index 7ba8efc6a..8d2576bf6 100644 --- a/crates/ironrdp-session/src/rfx.rs +++ b/crates/ironrdp-session/src/rfx.rs @@ -15,7 +15,6 @@ const TILE_SIZE: u16 = 64; pub type FrameId = u32; pub struct DecodingContext { - state: SequenceState, context: rfx::ContextPdu, channels: rfx::ChannelsPdu, decoding_tiles: DecodingTileContext, @@ -24,7 +23,6 @@ pub struct DecodingContext { impl Default for DecodingContext { fn default() -> Self { Self { - state: SequenceState::HeaderMessages, context: rfx::ContextPdu { flags: rfx::OperatingMode::empty(), entropy_algorithm: rfx::EntropyAlgorithm::Rlgr1, @@ -47,20 +45,31 @@ impl DecodingContext { input: &mut &[u8], ) -> SessionResult<(FrameId, InclusiveRectangle)> { loop { - match self.state { - SequenceState::HeaderMessages => { - self.process_headers(input)?; + let block_header = rfx::BlockHeader::from_buffer_consume(input)?; + match block_header.ty { + rfx::BlockType::Sync => { + self.process_sync(input, block_header)?; } - SequenceState::DataMessages => { - return self.process_data_messages(image, destination, input); + rfx::BlockType::FrameBegin => { + return self.process_frame(input, block_header, image, destination); + } + _ => { + return Err(reason_err!( + "rfx::DecodingContext", + "unexpected RFX block type: {:?}", + block_header.ty + )); } } } } - fn process_headers(&mut self, input: &mut &[u8]) -> SessionResult<()> { - let _sync = rfx::SyncPdu::from_buffer_consume(input)?; + fn process_sync(&mut self, input: &mut &[u8], header: rfx::BlockHeader) -> SessionResult<()> { + let _sync = rfx::SyncPdu::from_buffer_consume_with_header(input, header)?; + self.process_headers(input) + } + fn process_headers(&mut self, input: &mut &[u8]) -> SessionResult<()> { let mut context = None; let mut channels = None; @@ -81,24 +90,24 @@ impl DecodingContext { self.context = context; self.channels = channels; - self.state = SequenceState::DataMessages; Ok(()) } #[instrument(skip_all)] - fn process_data_messages( + fn process_frame( &mut self, + input: &mut &[u8], + header: rfx::BlockHeader, image: &mut DecodedImage, destination: &InclusiveRectangle, - input: &mut &[u8], ) -> SessionResult<(FrameId, InclusiveRectangle)> { let channel = self.channels.0.first().unwrap(); let width = channel.width.as_u16(); let height = channel.height.as_u16(); let entropy_algorithm = self.context.entropy_algorithm; - let frame_begin = rfx::FrameBeginPdu::from_buffer_consume(input)?; + let frame_begin = rfx::FrameBeginPdu::from_buffer_consume_with_header(input, header)?; let mut region = rfx::RegionPdu::from_buffer_consume(input)?; let tile_set = rfx::TileSetPdu::from_buffer_consume(input)?; let _frame_end = rfx::FrameEndPdu::from_buffer_consume(input)?; @@ -145,10 +154,6 @@ impl DecodingContext { final_update_rectangle = final_update_rectangle.union(¤t_update_rectangle); } - if self.context.flags.contains(rfx::OperatingMode::IMAGE_MODE) { - self.state = SequenceState::HeaderMessages; - } - Ok((frame_begin.index, final_update_rectangle)) } } @@ -258,8 +263,3 @@ struct TileData<'a> { quants: [Quant; 3], data: [&'a [u8]; 3], } - -enum SequenceState { - HeaderMessages, - DataMessages, -} diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index acd16d512..f95bd0548 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -30,7 +30,10 @@ pub enum ProcessorOutput { ResponseFrame(Vec), /// A graceful disconnect notification. Client should close the connection upon receiving this. Disconnect(DisconnectReason), - /// Received a [`ironrdp_pdu::rdp::headers::ServerDeactivateAll`] PDU. + /// 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(ConnectionActivationSequence), } From 717d02068b2cbcae804c3a5043d4f440a8ae62d8 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 19 Mar 2024 21:01:13 -0700 Subject: [PATCH 06/84] Adds todo in ironrdp-web --- crates/ironrdp-web/src/session.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 5d76b14d0..6407d38ee 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -603,6 +603,7 @@ impl Session { hotspot_y, })?; } + ActiveStageOutput::DeactivateAll(_) => todo!(), ActiveStageOutput::Terminate(reason) => break 'outer reason, } } From d07d522104af6b78d1818b5cf86c2682693fd8ae Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 13 Mar 2024 16:21:03 -0700 Subject: [PATCH 07/84] Proof of concept for the transition from legacy x224::Processor::process_dyvc to DrdynvcClient::process --- crates/ironrdp-client/src/rdp.rs | 2 +- crates/ironrdp-cliprdr/src/pdu/mod.rs | 2 +- crates/ironrdp-dvc/src/client.rs | 69 ++++++++++++++++++++------ crates/ironrdp-pdu/src/lib.rs | 4 ++ crates/ironrdp-pdu/src/rdp/vc/dvc.rs | 5 +- crates/ironrdp-session/src/legacy.rs | 9 ++-- crates/ironrdp-session/src/x224/mod.rs | 3 -- 7 files changed, 67 insertions(+), 27 deletions(-) diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 1f55eebc9..f207cf8c1 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -104,7 +104,7 @@ async fn connect( let mut connector = connector::ClientConnector::new(config.connector.clone()) .with_server_addr(server_addr) - // .with_static_channel(ironrdp::dvc::DrdynvcClient::new()) // FIXME(#61): drdynvc is not working + .with_static_channel(ironrdp::dvc::DrdynvcClient::new()) // FIXME(#61): drdynvc is not working .with_static_channel(rdpsnd::Rdpsnd::new()) .with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0)); diff --git a/crates/ironrdp-cliprdr/src/pdu/mod.rs b/crates/ironrdp-cliprdr/src/pdu/mod.rs index 59d5ac3cb..0041a9e3a 100644 --- a/crates/ironrdp-cliprdr/src/pdu/mod.rs +++ b/crates/ironrdp-cliprdr/src/pdu/mod.rs @@ -219,7 +219,7 @@ impl<'de> PduDecode<'de> for ClipboardPdu<'de> { fn decode(src: &mut ReadCursor<'de>) -> PduResult { ensure_fixed_part_size!(in: src); - let read_empty_pdu = |src: &mut ReadCursor<'de>| { + let read_empty_pdu = |src: &mut ReadCursor<'de>| -> PduResult<()> { let _header = PartialHeader::decode(src)?; Ok(()) }; diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index e0b0bc704..31b92e2be 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -2,6 +2,7 @@ use alloc::borrow::ToOwned; use alloc::boxed::Box; use alloc::collections::BTreeMap; use alloc::string::String; +use alloc::vec; use alloc::vec::Vec; use core::any::Any; use core::fmt; @@ -24,6 +25,8 @@ pub trait DvcClientProcessor: DvcProcessor {} /// It adds support for dynamic virtual channels (DVC). pub struct DrdynvcClient { dynamic_channels: BTreeMap>, + /// Indicates whether the capability request/response handshake has been completed. + cap_handshake_done: bool, } impl fmt::Debug for DrdynvcClient { @@ -47,6 +50,7 @@ impl DrdynvcClient { pub fn new() -> Self { Self { dynamic_channels: BTreeMap::new(), + cap_handshake_done: false, } } @@ -61,6 +65,18 @@ impl DrdynvcClient { self.dynamic_channels.insert(channel_name, Box::new(channel)); self } + + fn create_capabilities_response(&mut self) -> SvcMessage { + let caps_response = dvc::ClientPdu::CapabilitiesResponse(dvc::CapabilitiesResponsePdu { + version: dvc::CapsVersion::V1, + }); + debug!("Send DVC Capabilities Response PDU: {caps_response:?}"); + self.cap_handshake_done = true; + SvcMessage::from(DvcMessage { + dvc_pdu: caps_response, + dvc_data: &[], + }) + } } impl_as_any!(DrdynvcClient); @@ -82,20 +98,24 @@ impl SvcProcessor for DrdynvcClient { fn process(&mut self, payload: &[u8]) -> PduResult> { let dvc_ctx = decode_dvc_message(payload)?; + let mut responses = Vec::new(); match dvc_ctx.dvc_pdu { dvc::ServerPdu::CapabilitiesRequest(caps_request) => { debug!("Got DVC Capabilities Request PDU: {caps_request:?}"); - let caps_response = dvc::ClientPdu::CapabilitiesResponse(dvc::CapabilitiesResponsePdu { - version: dvc::CapsVersion::V1, - }); - - debug!("Send DVC Capabilities Response PDU: {caps_response:?}"); - // crate::legacy::encode_dvc_message(initiator_id, channel_id, caps_response, &[], output)?; + responses.push(self.create_capabilities_response()); } dvc::ServerPdu::CreateRequest(create_request) => { debug!("Got DVC Create Request PDU: {create_request:?}"); + if !self.cap_handshake_done { + debug!( + "Got DVC Create Request PDU before a Capabilities Request PDU. \ + Sending Capabilities Response PDU before the Create Response PDU." + ); + responses.push(self.create_capabilities_response()); + } + // let creation_status = if let Some(dynamic_channel) = create_dvc( // create_request.channel_name.as_str(), // create_request.channel_id, @@ -211,10 +231,14 @@ impl SvcProcessor for DrdynvcClient { } } - Err(ironrdp_pdu::other_err!( - "DRDYNVC", - "ironrdp-dvc::DrdynvcClient implementation is not yet ready" - )) + if !responses.is_empty() { + Ok(responses) + } else { + Err(ironrdp_pdu::other_err!( + "DRDYNVC", + "ironrdp-dvc::DrdynvcClient implementation is not yet ready" + )) + } } fn is_drdynvc(&self) -> bool { @@ -235,10 +259,6 @@ fn decode_dvc_message(user_data: &[u8]) -> PduResult> { let mut user_data = user_data; let user_data_len = user_data.len(); - // [ vc::ChannelPduHeader | ā€¦ - let channel_header = vc::ChannelPduHeader::from_buffer(&mut user_data).map_err(|e| custom_err!("DVC header", e))?; - debug_assert_eq!(user_data_len, channel_header.length as usize); - // ā€¦ | dvc::ServerPdu | ā€¦ let dvc_pdu = vc::dvc::ServerPdu::from_buffer(&mut user_data, user_data_len).map_err(|e| custom_err!("DVC server PDU", e))?; @@ -248,3 +268,24 @@ fn decode_dvc_message(user_data: &[u8]) -> PduResult> { Ok(DynamicChannelCtx { dvc_pdu, dvc_data }) } + +struct DvcMessage<'a> { + dvc_pdu: vc::dvc::ClientPdu, + dvc_data: &'a [u8], +} + +impl PduEncode for DvcMessage<'_> { + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + self.dvc_pdu.to_buffer(dst)?; + dst.write_slice(self.dvc_data); + Ok(()) + } + + fn name(&self) -> &'static str { + self.dvc_pdu.as_short_name() + } + + fn size(&self) -> usize { + self.dvc_pdu.buffer_length() + self.dvc_data.len() + } +} diff --git a/crates/ironrdp-pdu/src/lib.rs b/crates/ironrdp-pdu/src/lib.rs index fb977ed4d..64f9012bc 100644 --- a/crates/ironrdp-pdu/src/lib.rs +++ b/crates/ironrdp-pdu/src/lib.rs @@ -88,6 +88,10 @@ impl fmt::Display for PduErrorKind { } } +impl ironrdp_error::legacy::CatchAllKind for PduErrorKind { + const CATCH_ALL_VALUE: Self = Self::Custom; +} + pub trait PduErrorExt { fn not_enough_bytes(context: &'static str, received: usize, expected: usize) -> Self; fn invalid_message(context: &'static str, field: &'static str, reason: &'static str) -> Self; diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs index a387490fb..370b190cd 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs @@ -6,6 +6,7 @@ use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use super::ChannelError; +use crate::cursor::WriteCursor; use crate::PduParsing; #[cfg(test)] @@ -171,7 +172,7 @@ impl ClientPdu { } } - pub fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), ChannelError> { + pub fn to_buffer(&self, mut stream: &mut WriteCursor<'_>) -> Result<(), ChannelError> { match self { ClientPdu::CapabilitiesResponse(caps_request) => caps_request.to_buffer(&mut stream)?, ClientPdu::CreateResponse(create_request) => create_request.to_buffer(&mut stream)?, @@ -193,7 +194,7 @@ impl ClientPdu { } } - pub fn as_short_name(&self) -> &str { + pub fn as_short_name(&self) -> &'static str { match self { ClientPdu::CapabilitiesResponse(_) => "Capabilities Response PDU", ClientPdu::CreateResponse(_) => "Create Response PDU", diff --git a/crates/ironrdp-session/src/legacy.rs b/crates/ironrdp-session/src/legacy.rs index 9363a0c01..fba1d0971 100644 --- a/crates/ironrdp-session/src/legacy.rs +++ b/crates/ironrdp-session/src/legacy.rs @@ -48,14 +48,11 @@ impl PduParsing for DvcMessage<'_> { where Self: Sized, { - Err(std::io::Error::other("legacy::DvcMessage::from_buffer called ā€“ this is a bug").into()) + Err(std::io::Error::other("legacy::DvcMessage::from_buffer called - this is a bug").into()) } - fn to_buffer(&self, mut stream: impl Write) -> Result<(), Self::Error> { - self.channel_header.to_buffer(&mut stream)?; - self.dvc_pdu.to_buffer(&mut stream)?; - stream.write_all(self.dvc_data)?; - Ok(()) + fn to_buffer(&self, mut _stream: impl Write) -> Result<(), Self::Error> { + Err(std::io::Error::other("legacy::DvcMessage::to_buffer called - this is a bug").into()) } fn buffer_length(&self) -> usize { diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index f95bd0548..6347c14a0 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -117,9 +117,6 @@ impl Processor { if channel_id == self.io_channel_id { self.process_io_channel(data_ctx) - } else if self.drdynvc_channel_id == Some(channel_id) { - self.process_dyvc(data_ctx) - .map(|data| vec![ProcessorOutput::ResponseFrame(data)]) } 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(crate::SessionError::pdu)?; process_svc_messages(response_pdus, channel_id, data_ctx.initiator_id) From 5c22b53e1a6cf9bd6059f64f1eed3e5bb6de7ccd Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 13 Mar 2024 19:02:01 -0700 Subject: [PATCH 08/84] Adds a DynamicChannelSet (similar to the StaticChannelSet) to the DVC client codebase. This uses `encode_dvc_data` to encode the DVC data responses. Currently no dynamic channels are added so the code is untested (WIP). --- crates/ironrdp-dvc/src/client.rs | 255 ++++++++++++++++++------------- crates/ironrdp-dvc/src/server.rs | 5 +- 2 files changed, 151 insertions(+), 109 deletions(-) diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 31b92e2be..8234fb979 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -5,7 +5,7 @@ use alloc::string::String; use alloc::vec; use alloc::vec::Vec; use core::any::Any; -use core::fmt; +use core::{cmp, fmt}; use ironrdp_pdu as pdu; @@ -13,10 +13,11 @@ use ironrdp_svc::{impl_as_any, CompressionCondition, SvcClientProcessor, SvcMess use pdu::cursor::WriteCursor; use pdu::gcc::ChannelName; use pdu::rdp::vc; -use pdu::PduEncode; use pdu::{dvc, PduResult}; +use pdu::{other_err, PduEncode}; -use crate::DvcProcessor; +use crate::complete_data::CompleteData; +use crate::{encode_dvc_data, DvcMessages, DvcProcessor}; pub trait DvcClientProcessor: DvcProcessor {} @@ -24,7 +25,7 @@ pub trait DvcClientProcessor: DvcProcessor {} /// /// It adds support for dynamic virtual channels (DVC). pub struct DrdynvcClient { - dynamic_channels: BTreeMap>, + dynamic_channels: DynamicChannelSet, /// Indicates whether the capability request/response handshake has been completed. cap_handshake_done: bool, } @@ -49,7 +50,7 @@ impl DrdynvcClient { pub fn new() -> Self { Self { - dynamic_channels: BTreeMap::new(), + dynamic_channels: DynamicChannelSet::new(), cap_handshake_done: false, } } @@ -61,8 +62,7 @@ impl DrdynvcClient { where T: DvcClientProcessor + 'static, { - let channel_name = channel.channel_name().to_owned(); - self.dynamic_channels.insert(channel_name, Box::new(channel)); + self.dynamic_channels.insert(channel); self } @@ -107,6 +107,8 @@ impl SvcProcessor for DrdynvcClient { } dvc::ServerPdu::CreateRequest(create_request) => { debug!("Got DVC Create Request PDU: {create_request:?}"); + let channel_name = create_request.channel_name.clone(); + let channel_id = create_request.channel_id; if !self.cap_handshake_done { debug!( @@ -116,43 +118,31 @@ impl SvcProcessor for DrdynvcClient { responses.push(self.create_capabilities_response()); } - // let creation_status = if let Some(dynamic_channel) = create_dvc( - // create_request.channel_name.as_str(), - // create_request.channel_id, - // create_request.channel_id_type, - // &mut self.graphics_handler, - // ) { - // self.dynamic_channels.insert(create_request.channel_id, dynamic_channel); - // self.channel_map - // .insert(create_request.channel_name.clone(), create_request.channel_id); - - // dvc::DVC_CREATION_STATUS_OK - // } else { - // dvc::DVC_CREATION_STATUS_NO_LISTENER - // }; - - // let create_response = dvc::ClientPdu::CreateResponse(dvc::CreateResponsePdu { - // channel_id_type: create_request.channel_id_type, - // channel_id: create_request.channel_id, - // creation_status, - // }); - - // debug!("Send DVC Create Response PDU: {create_response:?}"); - // crate::legacy::encode_dvc_message( - // data_ctx.initiator_id, - // data_ctx.channel_id, - // create_response, - // &[], - // &mut buf, - // )?; - - // negotiate_dvc( - // &create_request, - // data_ctx.initiator_id, - // data_ctx.channel_id, - // &mut buf, - // &self.graphics_config, - // )?; + let channel_exists = self.dynamic_channels.get_by_channel_name(&channel_name).is_some(); + let (creation_status, start_messages) = if channel_exists { + // If we have a handler for this channel, attach the channel ID + // and get any start messages. + self.dynamic_channels + .attach_channel_id(channel_name.clone(), channel_id); + let dynamic_channel = self.dynamic_channels.get_by_channel_name_mut(&channel_name).unwrap(); + (dvc::DVC_CREATION_STATUS_OK, dynamic_channel.start(channel_id)?) + } else { + (dvc::DVC_CREATION_STATUS_NO_LISTENER, vec![]) + }; + + // Send the Create Response PDU. + let create_response = dvc::ClientPdu::CreateResponse(dvc::CreateResponsePdu { + channel_id_type: create_request.channel_id_type, + channel_id, + creation_status, + }); + debug!("Send DVC Create Response PDU: {create_response:?}"); + responses.push(SvcMessage::from(DvcMessage::new(create_response, &[]))); + + // If this DVC has start messages, send them. + if !start_messages.is_empty() { + responses.extend(encode_dvc_data(channel_id, start_messages)?); + } } dvc::ServerPdu::CloseRequest(close_request) => { debug!("Got DVC Close Request PDU: {close_request:?}"); @@ -162,83 +152,38 @@ impl SvcProcessor for DrdynvcClient { channel_id: close_request.channel_id, }); - // debug!("Send DVC Close Response PDU: {close_response:?}"); - // crate::legacy::encode_dvc_message( - // data_ctx.initiator_id, - // data_ctx.channel_id, - // close_response, - // &[], - // &mut buf, - // )?; - - // self.dynamic_channels.remove(&close_request.channel_id); + debug!("Send DVC Close Response PDU: {close_response:?}"); + responses.push(SvcMessage::from(DvcMessage::new(close_response, &[]))); + self.dynamic_channels.remove_by_channel_id(&close_request.channel_id); } dvc::ServerPdu::DataFirst(data) => { - let channel_id_type = data.channel_id_type; let channel_id = data.channel_id; - let dvc_data = dvc_ctx.dvc_data; - // // FIXME(perf): copy with data_buf.to_vec() - // if let Some(dvc_data) = self - // .dynamic_channels - // .get_mut(&data.channel_id) - // .ok_or_else(|| reason_err!("DVC", "access to non existing channel: {}", data.channel_id))? - // .process_data_first_pdu(data.total_data_size as usize, dvc_data.to_vec())? - // { - // let client_data = dvc::ClientPdu::Data(dvc::DataPdu { - // channel_id_type, - // channel_id, - // data_size: dvc_data.len(), - // }); - - // crate::legacy::encode_dvc_message( - // data_ctx.initiator_id, - // data_ctx.channel_id, - // client_data, - // &dvc_data, - // &mut buf, - // )?; - // } + let messages = self + .dynamic_channels + .get_by_channel_id_mut(&channel_id) + .ok_or_else(|| other_err!("DVC", "access to non existing channel"))? + .process(channel_id, dvc_data)?; + + responses.extend(encode_dvc_data(channel_id, messages)?); } dvc::ServerPdu::Data(data) => { - let channel_id_type = data.channel_id_type; + // TODO: identical to DataFirst, create a helper function let channel_id = data.channel_id; - let dvc_data = dvc_ctx.dvc_data; - // // FIXME(perf): copy with data_buf.to_vec() - // if let Some(dvc_data) = self - // .dynamic_channels - // .get_mut(&data.channel_id) - // .ok_or_else(|| reason_err!("DVC", "access to non existing channel: {}", data.channel_id))? - // .process_data_pdu(dvc_data.to_vec())? - // { - // let client_data = dvc::ClientPdu::Data(dvc::DataPdu { - // channel_id_type, - // channel_id, - // data_size: dvc_data.len(), - // }); - - // crate::legacy::encode_dvc_message( - // data_ctx.initiator_id, - // data_ctx.channel_id, - // client_data, - // &dvc_data, - // &mut buf, - // )?; - // } + let messages = self + .dynamic_channels + .get_by_channel_id_mut(&channel_id) + .ok_or_else(|| other_err!("DVC", "access to non existing channel"))? + .process(channel_id, dvc_data)?; + + responses.extend(encode_dvc_data(channel_id, messages)?); } } - if !responses.is_empty() { - Ok(responses) - } else { - Err(ironrdp_pdu::other_err!( - "DRDYNVC", - "ironrdp-dvc::DrdynvcClient implementation is not yet ready" - )) - } + Ok(responses) } fn is_drdynvc(&self) -> bool { @@ -269,11 +214,18 @@ fn decode_dvc_message(user_data: &[u8]) -> PduResult> { Ok(DynamicChannelCtx { dvc_pdu, dvc_data }) } +/// TODO: this is the same as server.rs's `DynamicChannelCtx`, can we unify them? struct DvcMessage<'a> { dvc_pdu: vc::dvc::ClientPdu, dvc_data: &'a [u8], } +impl<'a> DvcMessage<'a> { + fn new(dvc_pdu: vc::dvc::ClientPdu, dvc_data: &'a [u8]) -> Self { + Self { dvc_pdu, dvc_data } + } +} + impl PduEncode for DvcMessage<'_> { fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { self.dvc_pdu.to_buffer(dst)?; @@ -289,3 +241,90 @@ impl PduEncode for DvcMessage<'_> { self.dvc_pdu.buffer_length() + self.dvc_data.len() } } + +pub struct DynamicVirtualChannel { + id: DynamicChannelId, + handler: Box, +} + +impl DynamicVirtualChannel { + fn new(handler: T) -> Self { + Self { + id: 0, //TODO: is this correct? + handler: Box::new(handler), + } + } + + fn start(&mut self, channel_id: DynamicChannelId) -> PduResult { + self.handler.start(channel_id) + } + + fn process(&mut self, channel_id: DynamicChannelId, data: &[u8]) -> PduResult { + self.handler.process(channel_id, data) + } + + fn channel_name(&self) -> &str { + self.handler.channel_name() + } +} + +struct DynamicChannelSet { + channels: BTreeMap, + name_to_id: BTreeMap, + id_to_name: BTreeMap, +} + +impl DynamicChannelSet { + #[inline] + fn new() -> Self { + Self { + channels: BTreeMap::new(), + name_to_id: BTreeMap::new(), + id_to_name: BTreeMap::new(), + } + } + + pub fn insert(&mut self, channel: T) -> Option { + let name = channel.channel_name().to_owned(); + self.channels.insert(name, DynamicVirtualChannel::new(channel)) + } + + pub fn get_by_channel_name(&self, name: &DynamicChannelName) -> Option<&DynamicVirtualChannel> { + self.channels.get(name) + } + + pub fn get_by_channel_name_mut(&mut self, name: &DynamicChannelName) -> Option<&mut DynamicVirtualChannel> { + self.channels.get_mut(name) + } + + pub fn get_by_channel_id(&self, id: &DynamicChannelId) -> Option<&DynamicVirtualChannel> { + self.id_to_name.get(id).and_then(|name| self.channels.get(name)) + } + + pub fn get_by_channel_id_mut(&mut self, id: &DynamicChannelId) -> Option<&mut DynamicVirtualChannel> { + self.id_to_name.get(id).and_then(|name| self.channels.get_mut(name)) + } + + pub fn attach_channel_id(&mut self, name: DynamicChannelName, id: DynamicChannelId) -> Option { + let channel = self.get_by_channel_name_mut(&name)?; + channel.id = id; + self.id_to_name.insert(id, name.clone()); + self.name_to_id.insert(name, id) + } + + pub fn remove_by_channel_id(&mut self, id: &DynamicChannelId) -> Option { + if let Some(name) = self.id_to_name.remove(id) { + self.name_to_id.remove(&name); + return self.channels.remove(&name); + } + None + } + + #[inline] + pub fn values(&self) -> impl Iterator { + self.channels.values() + } +} + +type DynamicChannelName = String; +type DynamicChannelId = u32; diff --git a/crates/ironrdp-dvc/src/server.rs b/crates/ironrdp-dvc/src/server.rs index 258d1ee90..174188ef3 100644 --- a/crates/ironrdp-dvc/src/server.rs +++ b/crates/ironrdp-dvc/src/server.rs @@ -223,7 +223,8 @@ fn encode_dvc_message(pdu: vc::dvc::ServerPdu) -> PduResult { Ok(SvcMessage::from(buf).with_flags(ChannelFlags::SHOW_PROTOCOL)) } -fn encode_dvc_data(channel_id: u32, messages: DvcMessages) -> PduResult> { +// TODO: This is used by both client and server, so it should be moved to a common place +pub fn encode_dvc_data(channel_id: u32, messages: DvcMessages) -> PduResult> { let mut res = Vec::new(); for msg in messages { let total_size = msg.size(); @@ -237,6 +238,8 @@ fn encode_dvc_data(channel_id: u32, messages: DvcMessages) -> PduResult= DATA_MAX_SIZE { let total_size = cast_length!("encode_dvc_data", "totalDataSize", total_size)?; + // TODO: DataFirst and Data pdus are common for both client and server, + // so they should be unified. In fact all DVC pdu types should be unified. vc::dvc::ServerPdu::DataFirst(DataFirstPdu::new(channel_id, total_size, DATA_MAX_SIZE)) } else { vc::dvc::ServerPdu::Data(DataPdu::new(channel_id, size)) From 8a5c5f111d3d2adab08a532303142df2f34c93e4 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 13 Mar 2024 23:00:04 -0700 Subject: [PATCH 09/84] Adds a CommonPdu to consolidate common client/server pdu's for dvcs --- crates/ironrdp-dvc/src/client.rs | 13 +- crates/ironrdp-dvc/src/complete_data.rs | 17 +- crates/ironrdp-dvc/src/lib.rs | 3 +- crates/ironrdp-dvc/src/server.rs | 35 +- crates/ironrdp-pdu/src/rdp/vc/dvc.rs | 136 ++++--- crates/ironrdp-server/src/server.rs | 6 +- crates/ironrdp-session/src/active_stage.rs | 10 +- crates/ironrdp-session/src/x224/display.rs | 3 +- crates/ironrdp-session/src/x224/gfx.rs | 3 +- crates/ironrdp-session/src/x224/mod.rs | 390 ++------------------- crates/ironrdp-web/src/session.rs | 2 +- 11 files changed, 176 insertions(+), 442 deletions(-) diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 8234fb979..a0d9cc6b0 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -156,7 +156,7 @@ impl SvcProcessor for DrdynvcClient { responses.push(SvcMessage::from(DvcMessage::new(close_response, &[]))); self.dynamic_channels.remove_by_channel_id(&close_request.channel_id); } - dvc::ServerPdu::DataFirst(data) => { + dvc::ServerPdu::Common(dvc::CommonPdu::DataFirst(data)) => { let channel_id = data.channel_id; let dvc_data = dvc_ctx.dvc_data; @@ -168,7 +168,7 @@ impl SvcProcessor for DrdynvcClient { responses.extend(encode_dvc_data(channel_id, messages)?); } - dvc::ServerPdu::Data(data) => { + dvc::ServerPdu::Common(dvc::CommonPdu::Data(data)) => { // TODO: identical to DataFirst, create a helper function let channel_id = data.channel_id; let dvc_data = dvc_ctx.dvc_data; @@ -243,14 +243,12 @@ impl PduEncode for DvcMessage<'_> { } pub struct DynamicVirtualChannel { - id: DynamicChannelId, handler: Box, } impl DynamicVirtualChannel { fn new(handler: T) -> Self { Self { - id: 0, //TODO: is this correct? handler: Box::new(handler), } } @@ -307,15 +305,14 @@ impl DynamicChannelSet { pub fn attach_channel_id(&mut self, name: DynamicChannelName, id: DynamicChannelId) -> Option { let channel = self.get_by_channel_name_mut(&name)?; - channel.id = id; self.id_to_name.insert(id, name.clone()); self.name_to_id.insert(name, id) } - pub fn remove_by_channel_id(&mut self, id: &DynamicChannelId) -> Option { + pub fn remove_by_channel_id(&mut self, id: &DynamicChannelId) -> Option { if let Some(name) = self.id_to_name.remove(id) { - self.name_to_id.remove(&name); - return self.channels.remove(&name); + return self.name_to_id.remove(&name); + // Channels are retained in the `self.channels` map to allow potential re-addition by the server. } None } diff --git a/crates/ironrdp-dvc/src/complete_data.rs b/crates/ironrdp-dvc/src/complete_data.rs index ba20e20dc..d0f49a756 100644 --- a/crates/ironrdp-dvc/src/complete_data.rs +++ b/crates/ironrdp-dvc/src/complete_data.rs @@ -1,5 +1,6 @@ use alloc::vec::Vec; use core::cmp; +use ironrdp_pdu::dvc; #[derive(Debug, PartialEq)] pub(crate) struct CompleteData { @@ -15,7 +16,14 @@ impl CompleteData { } } - pub(crate) fn process_data_first_pdu(&mut self, total_data_size: usize, data: Vec) -> Option> { + pub(crate) fn process_data(&mut self, pdu: dvc::CommonPdu, data: Vec) -> Option> { + match pdu { + dvc::CommonPdu::DataFirst(df) => self.process_data_first_pdu(df.total_data_size as usize, data), + dvc::CommonPdu::Data(_) => self.process_data_pdu(data), + } + } + + fn process_data_first_pdu(&mut self, total_data_size: usize, data: Vec) -> Option> { if self.total_size != 0 || !self.data.is_empty() { error!("Incomplete DVC message, it will be skipped"); @@ -32,7 +40,7 @@ impl CompleteData { } } - pub(crate) fn process_data_pdu(&mut self, mut data: Vec) -> Option> { + fn process_data_pdu(&mut self, mut data: Vec) -> Option> { if self.total_size == 0 && self.data.is_empty() { // message is not fragmented Some(data) @@ -69,3 +77,8 @@ impl CompleteData { } } } + +pub(crate) enum Chunk { + DataFirstPdu(dvc::DataFirstPdu), + DataPdu(dvc::DataPdu), +} diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index ced6247c8..358ba1337 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -14,6 +14,7 @@ use alloc::vec::Vec; // Re-export ironrdp_pdu crate for convenience #[rustfmt::skip] // do not re-order this pub use pub use ironrdp_pdu as pdu; +use ironrdp_svc::AsAny; use pdu::write_buf::WriteBuf; use pdu::{assert_obj_safe, PduEncode, PduResult}; @@ -34,7 +35,7 @@ pub type DvcMessages = Vec>; /// The Dynamic Virtual Channel APIs exist to address limitations of Static Virtual Channels: /// - Limited number of channels /// - Packet reconstruction -pub trait DvcProcessor: Send + Sync { +pub trait DvcProcessor: AsAny + Send + Sync { fn channel_name(&self) -> &str; fn start(&mut self, _channel_id: u32) -> PduResult; diff --git a/crates/ironrdp-dvc/src/server.rs b/crates/ironrdp-dvc/src/server.rs index 174188ef3..bff49b462 100644 --- a/crates/ironrdp-dvc/src/server.rs +++ b/crates/ironrdp-dvc/src/server.rs @@ -35,7 +35,7 @@ enum ChannelState { struct DynamicChannel { state: ChannelState, processor: Box, - data: CompleteData, + complete_data: CompleteData, } impl DynamicChannel { @@ -46,7 +46,7 @@ impl DynamicChannel { Self { state: ChannelState::Closed, processor: Box::new(processor), - data: CompleteData::new(), + complete_data: CompleteData::new(), } } } @@ -166,27 +166,26 @@ impl SvcProcessor for DrdynvcServer { } c.state = ChannelState::Closed; } - dvc::ClientPdu::DataFirst(data) => { - let c = self.channel_by_id(data.channel_id)?; + dvc::ClientPdu::Common(dvc::CommonPdu::DataFirst(data)) => { + let channel_id = data.channel_id; + let c = self.channel_by_id(channel_id)?; if c.state != ChannelState::Opened { return Err(invalid_message_err!("DRDYNVC", "", "invalid channel state")); } - if let Some(complete) = c - .data - .process_data_first_pdu(data.total_data_size as usize, dvc_ctx.dvc_data.into()) - { - let msg = c.processor.process(data.channel_id, &complete)?; - resp.extend(encode_dvc_data(data.channel_id, msg)?); + if let Some(complete) = c.complete_data.process_data(data.into(), dvc_ctx.dvc_data.into()) { + let msg = c.processor.process(channel_id, &complete)?; + resp.extend(encode_dvc_data(channel_id, msg)?); } } - dvc::ClientPdu::Data(data) => { - let c = self.channel_by_id(data.channel_id)?; + dvc::ClientPdu::Common(dvc::CommonPdu::Data(data)) => { + let channel_id = data.channel_id; + let c = self.channel_by_id(channel_id)?; if c.state != ChannelState::Opened { return Err(invalid_message_err!("DRDYNVC", "", "invalid channel state")); } - if let Some(complete) = c.data.process_data_pdu(dvc_ctx.dvc_data.into()) { - let msg = c.processor.process(data.channel_id, &complete)?; - resp.extend(encode_dvc_data(data.channel_id, msg)?); + if let Some(complete) = c.complete_data.process_data(data.into(), dvc_ctx.dvc_data.into()) { + let msg = c.processor.process(channel_id, &complete)?; + resp.extend(encode_dvc_data(channel_id, msg)?); } } } @@ -238,11 +237,9 @@ pub fn encode_dvc_data(channel_id: u32, messages: DvcMessages) -> PduResult= DATA_MAX_SIZE { let total_size = cast_length!("encode_dvc_data", "totalDataSize", total_size)?; - // TODO: DataFirst and Data pdus are common for both client and server, - // so they should be unified. In fact all DVC pdu types should be unified. - vc::dvc::ServerPdu::DataFirst(DataFirstPdu::new(channel_id, total_size, DATA_MAX_SIZE)) + dvc::CommonPdu::DataFirst(DataFirstPdu::new(channel_id, total_size, DATA_MAX_SIZE)) } else { - vc::dvc::ServerPdu::Data(DataPdu::new(channel_id, size)) + dvc::CommonPdu::Data(DataPdu::new(channel_id, size)) }; let end = off diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs index 370b190cd..cbcee89a0 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs @@ -42,13 +42,82 @@ pub enum PduType { Capabilities = 0x05, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CommonPdu { + DataFirst(DataFirstPdu), + Data(DataPdu), +} + +impl CommonPdu { + pub fn from_buffer( + pdu_type: PduType, + mut stream: impl io::Read, + dvc_data_size: usize, + channel_id_type: FieldType, + ) -> Result { + match pdu_type { + PduType::DataFirst => { + let data_length_type = + FieldType::from_u8(channel_id_type as u8).ok_or(ChannelError::InvalidDvcDataLength)?; + + Ok(CommonPdu::DataFirst(DataFirstPdu::from_buffer( + &mut stream, + channel_id_type, + data_length_type, + dvc_data_size, + )?)) + } + PduType::Data => Ok(CommonPdu::Data(DataPdu::from_buffer( + &mut stream, + channel_id_type, + dvc_data_size, + )?)), + _ => Err(ChannelError::InvalidDvcPduType), + } + } + + pub fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), ChannelError> { + match self { + CommonPdu::DataFirst(data_first) => data_first.to_buffer(&mut stream)?, + CommonPdu::Data(data) => data.to_buffer(&mut stream)?, + }; + + Ok(()) + } + + pub fn buffer_length(&self) -> usize { + match self { + CommonPdu::DataFirst(data_first) => data_first.buffer_length(), + CommonPdu::Data(data) => data.buffer_length(), + } + } + + pub fn as_short_name(&self) -> &'static str { + match self { + CommonPdu::DataFirst(_) => "Data First PDU", + CommonPdu::Data(_) => "Data PDU", + } + } +} + +impl From for CommonPdu { + fn from(data_first: DataFirstPdu) -> Self { + CommonPdu::DataFirst(data_first) + } +} + +impl From for CommonPdu { + fn from(data: DataPdu) -> Self { + CommonPdu::Data(data) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum ServerPdu { CapabilitiesRequest(CapabilitiesRequestPdu), CreateRequest(CreateRequestPdu), - DataFirst(DataFirstPdu), - Data(DataPdu), CloseRequest(ClosePdu), + Common(CommonPdu), } impl ServerPdu { @@ -68,21 +137,17 @@ impl ServerPdu { channel_id_type, dvc_data_size, )?)), - PduType::DataFirst => { - let data_length_type = - FieldType::from_u8(dvc_header.pdu_dependent).ok_or(ChannelError::InvalidDvcDataLength)?; - - Ok(ServerPdu::DataFirst(DataFirstPdu::from_buffer( - &mut stream, - channel_id_type, - data_length_type, - dvc_data_size, - )?)) - } - PduType::Data => Ok(ServerPdu::Data(DataPdu::from_buffer( + PduType::DataFirst => Ok(ServerPdu::Common(CommonPdu::from_buffer( + PduType::DataFirst, &mut stream, + dvc_data_size, channel_id_type, + )?)), + PduType::Data => Ok(ServerPdu::Common(CommonPdu::from_buffer( + PduType::DataFirst, + &mut stream, dvc_data_size, + channel_id_type, )?)), PduType::Close => Ok(ServerPdu::CloseRequest(ClosePdu::from_buffer( &mut stream, @@ -95,8 +160,7 @@ impl ServerPdu { match self { ServerPdu::CapabilitiesRequest(caps_request) => caps_request.to_buffer(&mut stream)?, ServerPdu::CreateRequest(create_request) => create_request.to_buffer(&mut stream)?, - ServerPdu::DataFirst(data_first) => data_first.to_buffer(&mut stream)?, - ServerPdu::Data(data) => data.to_buffer(&mut stream)?, + ServerPdu::Common(common) => common.to_buffer(&mut stream)?, ServerPdu::CloseRequest(close_request) => close_request.to_buffer(&mut stream)?, }; @@ -107,18 +171,16 @@ impl ServerPdu { match self { ServerPdu::CapabilitiesRequest(caps_request) => caps_request.buffer_length(), ServerPdu::CreateRequest(create_request) => create_request.buffer_length(), - ServerPdu::DataFirst(data_first) => data_first.buffer_length(), - ServerPdu::Data(data) => data.buffer_length(), + ServerPdu::Common(common) => common.buffer_length(), ServerPdu::CloseRequest(close_request) => close_request.buffer_length(), } } - pub fn as_short_name(&self) -> &str { + pub fn as_short_name(&self) -> &'static str { match self { ServerPdu::CapabilitiesRequest(_) => "Capabilities Request PDU", ServerPdu::CreateRequest(_) => "Create Request PDU", - ServerPdu::DataFirst(_) => "Data First PDU", - ServerPdu::Data(_) => "Data PDU", + ServerPdu::Common(common) => common.as_short_name(), ServerPdu::CloseRequest(_) => "Close Request PDU", } } @@ -128,9 +190,8 @@ impl ServerPdu { pub enum ClientPdu { CapabilitiesResponse(CapabilitiesResponsePdu), CreateResponse(CreateResponsePdu), - DataFirst(DataFirstPdu), - Data(DataPdu), CloseResponse(ClosePdu), + Common(CommonPdu), } impl ClientPdu { @@ -149,21 +210,17 @@ impl ClientPdu { &mut stream, channel_id_type, )?)), - PduType::DataFirst => { - let data_length_type = - FieldType::from_u8(dvc_header.pdu_dependent).ok_or(ChannelError::InvalidDvcDataLength)?; - - Ok(ClientPdu::DataFirst(DataFirstPdu::from_buffer( - &mut stream, - channel_id_type, - data_length_type, - dvc_data_size, - )?)) - } - PduType::Data => Ok(ClientPdu::Data(DataPdu::from_buffer( + PduType::DataFirst => Ok(ClientPdu::Common(CommonPdu::from_buffer( + PduType::DataFirst, &mut stream, + dvc_data_size, channel_id_type, + )?)), + PduType::Data => Ok(ClientPdu::Common(CommonPdu::from_buffer( + PduType::DataFirst, + &mut stream, dvc_data_size, + channel_id_type, )?)), PduType::Close => Ok(ClientPdu::CloseResponse(ClosePdu::from_buffer( &mut stream, @@ -176,8 +233,7 @@ impl ClientPdu { match self { ClientPdu::CapabilitiesResponse(caps_request) => caps_request.to_buffer(&mut stream)?, ClientPdu::CreateResponse(create_request) => create_request.to_buffer(&mut stream)?, - ClientPdu::DataFirst(data_first) => data_first.to_buffer(&mut stream)?, - ClientPdu::Data(data) => data.to_buffer(&mut stream)?, + ClientPdu::Common(common) => common.to_buffer(&mut stream)?, ClientPdu::CloseResponse(close_response) => close_response.to_buffer(&mut stream)?, }; @@ -188,8 +244,7 @@ impl ClientPdu { match self { ClientPdu::CapabilitiesResponse(caps_request) => caps_request.buffer_length(), ClientPdu::CreateResponse(create_request) => create_request.buffer_length(), - ClientPdu::DataFirst(data_first) => data_first.buffer_length(), - ClientPdu::Data(data) => data.buffer_length(), + ClientPdu::Common(common) => common.buffer_length(), ClientPdu::CloseResponse(close_response) => close_response.buffer_length(), } } @@ -198,8 +253,7 @@ impl ClientPdu { match self { ClientPdu::CapabilitiesResponse(_) => "Capabilities Response PDU", ClientPdu::CreateResponse(_) => "Create Response PDU", - ClientPdu::DataFirst(_) => "Data First PDU", - ClientPdu::Data(_) => "Data PDU", + ClientPdu::Common(common) => common.as_short_name(), ClientPdu::CloseResponse(_) => "Close Response PDU", } } diff --git a/crates/ironrdp-server/src/server.rs b/crates/ironrdp-server/src/server.rs index e56f8fe99..4b87676d5 100644 --- a/crates/ironrdp-server/src/server.rs +++ b/crates/ironrdp-server/src/server.rs @@ -12,7 +12,7 @@ use ironrdp_pdu::input::InputEventPdu; use ironrdp_pdu::mcs::SendDataRequest; use ironrdp_pdu::rdp::capability_sets::{CapabilitySet, CmdFlags, GeneralExtraFlags}; use ironrdp_pdu::{self, custom_err, decode, mcs, nego, rdp, Action, PduParsing, PduResult}; -use ironrdp_svc::{server_encode_svc_messages, StaticChannelSet}; +use ironrdp_svc::{impl_as_any, server_encode_svc_messages, StaticChannelSet}; use ironrdp_tokio::{Framed, FramedRead, FramedWrite, TokioFramed}; use tokio::net::{TcpListener, TcpStream}; use tokio_rustls::TlsAcceptor; @@ -45,6 +45,8 @@ impl RdpServerSecurity { struct DisplayControlHandler {} +impl_as_any!(DisplayControlHandler); + impl dvc::DvcProcessor for DisplayControlHandler { fn channel_name(&self) -> &str { ironrdp_pdu::dvc::display::CHANNEL_NAME @@ -82,6 +84,8 @@ struct AInputHandler { handler: Arc>>, } +impl_as_any!(AInputHandler); + impl dvc::DvcProcessor for AInputHandler { fn channel_name(&self) -> &str { ironrdp_ainput::CHANNEL_NAME diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index 8bf8886f8..2a9dd43b8 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -27,8 +27,6 @@ impl ActiveStage { connection_result.static_channels, connection_result.user_channel_id, connection_result.io_channel_id, - connection_result.graphics_config, - graphics_handler, connection_result.connection_activation, ); @@ -166,10 +164,10 @@ impl ActiveStage { Ok(vec![ActiveStageOutput::ResponseFrame(frame.into_inner())]) } - /// Sends a PDU on the dynamic channel. - pub fn encode_dynamic(&self, output: &mut WriteBuf, channel_name: &str, dvc_data: &[u8]) -> SessionResult<()> { - self.x224_processor.encode_dynamic(output, channel_name, dvc_data) - } + // /// Sends a PDU on the dynamic channel. + // pub fn encode_dynamic(&self, output: &mut WriteBuf, channel_name: &str, dvc_data: &[u8]) -> SessionResult<()> { + // self.x224_processor.encode_dynamic(output, channel_name, dvc_data) + // } /// 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 { diff --git a/crates/ironrdp-session/src/x224/display.rs b/crates/ironrdp-session/src/x224/display.rs index d60e165c9..a65243b4e 100644 --- a/crates/ironrdp-session/src/x224/display.rs +++ b/crates/ironrdp-session/src/x224/display.rs @@ -1,12 +1,11 @@ use ironrdp_pdu::dvc::display::ServerPdu; use ironrdp_pdu::PduParsing; -use super::DynamicChannelDataHandler; use crate::SessionResult; pub(crate) struct Handler; -impl DynamicChannelDataHandler for Handler { +impl Handler { fn process_complete_data(&mut self, complete_data: Vec) -> SessionResult>> { let gfx_pdu = ServerPdu::from_buffer(&mut complete_data.as_slice())?; debug!("Got Display PDU: {:?}", gfx_pdu); diff --git a/crates/ironrdp-session/src/x224/gfx.rs b/crates/ironrdp-session/src/x224/gfx.rs index 75609e924..c55f00b8d 100644 --- a/crates/ironrdp-session/src/x224/gfx.rs +++ b/crates/ironrdp-session/src/x224/gfx.rs @@ -8,7 +8,6 @@ use ironrdp_pdu::dvc::gfx::{ }; use ironrdp_pdu::PduParsing; -use crate::x224::DynamicChannelDataHandler; use crate::SessionResult; pub trait GfxHandler { @@ -33,7 +32,7 @@ impl Handler { } } -impl DynamicChannelDataHandler for Handler { +impl Handler { fn process_complete_data(&mut self, complete_data: Vec) -> SessionResult>> { let mut client_pdu_buffer: Vec = Vec::new(); self.decompressed_buffer.clear(); diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index 6347c14a0..a58b02d22 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -1,13 +1,10 @@ mod display; mod gfx; -use std::cmp; use std::collections::HashMap; use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::legacy::SendDataIndicationCtx; -use ironrdp_connector::GraphicsConfig; -use ironrdp_pdu::dvc::FieldType; use ironrdp_pdu::mcs::{DisconnectProviderUltimatum, DisconnectReason, McsMessage}; use ironrdp_pdu::rdp::headers::ShareDataPdu; use ironrdp_pdu::rdp::server_error_info::{ErrorInfo, ProtocolIndependentCode, ServerSetErrorInfoPdu}; @@ -40,14 +37,9 @@ pub enum ProcessorOutput { pub struct Processor { channel_map: HashMap, static_channels: StaticChannelSet, - dynamic_channels: HashMap, user_channel_id: u16, io_channel_id: u16, drdynvc_channel_id: Option, - /// Indicates whether the DVC capabilities response has been sent. - drdynvc_initialized: bool, - graphics_config: Option, - graphics_handler: Option>, connection_activation: ConnectionActivationSequence, } @@ -56,8 +48,6 @@ impl Processor { static_channels: StaticChannelSet, user_channel_id: u16, io_channel_id: u16, - graphics_config: Option, - graphics_handler: Option>, connection_activation: ConnectionActivationSequence, ) -> Self { let drdynvc_channel_id = static_channels.iter().find_map(|(type_id, channel)| { @@ -70,14 +60,10 @@ impl Processor { Self { static_channels, - dynamic_channels: HashMap::new(), channel_map: HashMap::new(), user_channel_id, io_channel_id, drdynvc_channel_id, - drdynvc_initialized: false, - graphics_config, - graphics_handler, connection_activation, } } @@ -191,202 +177,38 @@ impl Processor { } } - fn send_drdynvc_capabilities_response( - &mut self, - data_ctx: SendDataIndicationCtx<'_>, - buf: &mut WriteBuf, - ) -> SessionResult<()> { - let caps_response = dvc::ClientPdu::CapabilitiesResponse(dvc::CapabilitiesResponsePdu { - version: dvc::CapsVersion::V1, - }); - debug!("Send DVC Capabilities Response PDU: {caps_response:?}"); - crate::legacy::encode_dvc_message(data_ctx.initiator_id, data_ctx.channel_id, caps_response, &[], buf)?; - self.drdynvc_initialized = true; - Ok(()) - } - - fn process_dyvc(&mut self, data_ctx: SendDataIndicationCtx<'_>) -> SessionResult> { - debug_assert_eq!(Some(data_ctx.channel_id), self.drdynvc_channel_id); - - let dvc_ctx = crate::legacy::decode_dvc_message(data_ctx)?; - - let mut buf = WriteBuf::new(); - - match dvc_ctx.dvc_pdu { - dvc::ServerPdu::CapabilitiesRequest(caps_request) => { - debug!("Got DVC Capabilities Request PDU: {caps_request:?}"); - self.send_drdynvc_capabilities_response(data_ctx, &mut buf)?; - } - dvc::ServerPdu::CreateRequest(create_request) => { - debug!("Got DVC Create Request PDU: {create_request:?}"); - - /* - * For some reason the server does not always send the - * capabilities pdu as it should. When this happens, - * send a capabilities response. - * https://github.com/FreeRDP/FreeRDP/blob/ba8cf8cf2158018fb7abbedb51ab245f369be813/channels/drdynvc/client/drdynvc_main.c#L1165-L1169 - */ - if !self.drdynvc_initialized { - debug!( - "Got DVC Create Request PDU before a Capabilities Request PDU. \ - Sending Capabilities Response PDU before the Create Response PDU." - ); - - self.send_drdynvc_capabilities_response(data_ctx, &mut buf)?; - } - - let creation_status = if let Some(dynamic_channel) = create_dvc( - create_request.channel_name.as_str(), - create_request.channel_id, - create_request.channel_id_type, - &mut self.graphics_handler, - ) { - self.dynamic_channels.insert(create_request.channel_id, dynamic_channel); - self.channel_map - .insert(create_request.channel_name.clone(), create_request.channel_id); - - dvc::DVC_CREATION_STATUS_OK - } else { - dvc::DVC_CREATION_STATUS_NO_LISTENER - }; - - let create_response = dvc::ClientPdu::CreateResponse(dvc::CreateResponsePdu { - channel_id_type: create_request.channel_id_type, - channel_id: create_request.channel_id, - creation_status, - }); - - debug!("Send DVC Create Response PDU: {create_response:?}"); - crate::legacy::encode_dvc_message( - data_ctx.initiator_id, - data_ctx.channel_id, - create_response, - &[], - &mut buf, - )?; - - negotiate_dvc( - &create_request, - data_ctx.initiator_id, - data_ctx.channel_id, - &mut buf, - &self.graphics_config, - )?; - } - dvc::ServerPdu::CloseRequest(close_request) => { - debug!("Got DVC Close Request PDU: {close_request:?}"); - - let close_response = dvc::ClientPdu::CloseResponse(dvc::ClosePdu { - channel_id_type: close_request.channel_id_type, - channel_id: close_request.channel_id, - }); - - debug!("Send DVC Close Response PDU: {close_response:?}"); - crate::legacy::encode_dvc_message( - data_ctx.initiator_id, - data_ctx.channel_id, - close_response, - &[], - &mut buf, - )?; - - self.dynamic_channels.remove(&close_request.channel_id); - } - dvc::ServerPdu::DataFirst(data) => { - debug!("Got DVC Data First PDU: {data:?}"); - let channel_id_type = data.channel_id_type; - let channel_id = data.channel_id; - - let dvc_data = dvc_ctx.dvc_data; - - // FIXME(perf): copy with data_buf.to_vec() - if let Some(dvc_data) = self - .dynamic_channels - .get_mut(&data.channel_id) - .ok_or_else(|| reason_err!("DVC", "access to non existing channel: {}", data.channel_id))? - .process_data_first_pdu(data.total_data_size as usize, dvc_data.to_vec())? - { - let client_data = dvc::ClientPdu::Data(dvc::DataPdu { - channel_id_type, - channel_id, - data_size: dvc_data.len(), - }); - - crate::legacy::encode_dvc_message( - data_ctx.initiator_id, - data_ctx.channel_id, - client_data, - &dvc_data, - &mut buf, - )?; - } - } - dvc::ServerPdu::Data(data) => { - debug!("Got DVC Data PDU: {data:?}"); - let channel_id_type = data.channel_id_type; - let channel_id = data.channel_id; - - let dvc_data = dvc_ctx.dvc_data; - - // FIXME(perf): copy with data_buf.to_vec() - if let Some(dvc_data) = self - .dynamic_channels - .get_mut(&data.channel_id) - .ok_or_else(|| reason_err!("DVC", "access to non existing channel: {}", data.channel_id))? - .process_data_pdu(dvc_data.to_vec())? - { - let client_data = dvc::ClientPdu::Data(dvc::DataPdu { - channel_id_type, - channel_id, - data_size: dvc_data.len(), - }); - - crate::legacy::encode_dvc_message( - data_ctx.initiator_id, - data_ctx.channel_id, - client_data, - &dvc_data, - &mut buf, - )?; - } - } - } - - Ok(buf.into_inner()) - } - /// Sends a PDU on the dynamic channel. - pub fn encode_dynamic(&self, output: &mut WriteBuf, channel_name: &str, dvc_data: &[u8]) -> SessionResult<()> { - let drdynvc_channel_id = self - .drdynvc_channel_id - .ok_or_else(|| general_err!("dynamic virtual channel not connected"))?; - - let dvc_channel_id = self - .channel_map - .get(channel_name) - .ok_or_else(|| reason_err!("DVC", "access to non existing channel name: {}", channel_name))?; - - let dvc_channel = self - .dynamic_channels - .get(dvc_channel_id) - .ok_or_else(|| reason_err!("DVC", "access to non existing channel: {}", dvc_channel_id))?; - - let dvc_client_data = dvc::ClientPdu::Data(dvc::DataPdu { - channel_id_type: dvc_channel.channel_id_type, - channel_id: dvc_channel.channel_id, - data_size: dvc_data.len(), - }); - - crate::legacy::encode_dvc_message( - self.user_channel_id, - drdynvc_channel_id, - dvc_client_data, - dvc_data, - output, - )?; - - Ok(()) - } + // pub fn encode_dynamic(&self, output: &mut WriteBuf, channel_name: &str, dvc_data: &[u8]) -> SessionResult<()> { + // let drdynvc_channel_id = self + // .drdynvc_channel_id + // .ok_or_else(|| general_err!("dynamic virtual channel not connected"))?; + + // let dvc_channel_id = self + // .channel_map + // .get(channel_name) + // .ok_or_else(|| reason_err!("DVC", "access to non existing channel name: {}", channel_name))?; + + // let dvc_channel = self + // .dynamic_channels + // .get(dvc_channel_id) + // .ok_or_else(|| reason_err!("DVC", "access to non existing channel: {}", dvc_channel_id))?; + + // let dvc_client_data = dvc::ClientPdu::Common(dvc::CommonPdu::Data(dvc::DataPdu { + // channel_id_type: dvc_channel.channel_id_type, + // channel_id: dvc_channel.channel_id, + // data_size: dvc_data.len(), + // })); + + // crate::legacy::encode_dvc_message( + // self.user_channel_id, + // drdynvc_channel_id, + // dvc_client_data, + // dvc_data, + // output, + // )?; + + // Ok(()) + // } /// 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 { @@ -407,156 +229,6 @@ fn process_svc_messages(messages: Vec, channel_id: u16, initiator_id client_encode_svc_messages(messages, channel_id, initiator_id).map_err(crate::SessionError::pdu) } -fn create_dvc( - channel_name: &str, - channel_id: u32, - channel_id_type: FieldType, - graphics_handler: &mut Option>, -) -> Option { - match channel_name { - RDP8_GRAPHICS_PIPELINE_NAME => { - let handler = graphics_handler.take(); - Some(DynamicChannel::new( - Box::new(gfx::Handler::new(handler)), - channel_id, - channel_id_type, - )) - } - RDP8_DISPLAY_PIPELINE_NAME => Some(DynamicChannel::new( - Box::new(display::Handler), - channel_id, - channel_id_type, - )), - _ => { - warn!(channel_name, "Unsupported dynamic virtual channel"); - None - } - } -} - -fn negotiate_dvc( - create_request: &dvc::CreateRequestPdu, - initiator_id: u16, - channel_id: u16, - buf: &mut WriteBuf, - graphics_config: &Option, -) -> SessionResult<()> { - if create_request.channel_name == RDP8_GRAPHICS_PIPELINE_NAME { - let dvc_data = gfx::create_capabilities_advertise(graphics_config)?; - let dvc_pdu = dvc::ClientPdu::Data(dvc::DataPdu { - channel_id_type: create_request.channel_id_type, - channel_id: create_request.channel_id, - data_size: dvc_data.len(), - }); - - debug!("Send GFX Capabilities Advertise PDU"); - crate::legacy::encode_dvc_message(initiator_id, channel_id, dvc_pdu, &dvc_data, buf)?; - } - - Ok(()) -} - -trait DynamicChannelDataHandler { - fn process_complete_data(&mut self, complete_data: Vec) -> SessionResult>>; -} - -pub struct DynamicChannel { - data: CompleteData, - channel_id_type: FieldType, - channel_id: u32, - handler: Box, -} - -impl DynamicChannel { - fn new(handler: Box, channel_id: u32, channel_id_type: FieldType) -> Self { - Self { - data: CompleteData::new(), - handler, - channel_id_type, - channel_id, - } - } - - fn process_data_first_pdu(&mut self, total_data_size: usize, data: Vec) -> SessionResult>> { - if let Some(complete_data) = self.data.process_data_first_pdu(total_data_size, data) { - self.handler.process_complete_data(complete_data) - } else { - Ok(None) - } - } - - fn process_data_pdu(&mut self, data: Vec) -> SessionResult>> { - if let Some(complete_data) = self.data.process_data_pdu(data) { - self.handler.process_complete_data(complete_data) - } else { - Ok(None) - } - } -} - -#[derive(Debug, PartialEq)] -struct CompleteData { - total_size: usize, - data: Vec, -} - -impl CompleteData { - fn new() -> Self { - Self { - total_size: 0, - data: Vec::new(), - } - } - - fn process_data_first_pdu(&mut self, total_data_size: usize, data: Vec) -> Option> { - if self.total_size != 0 || !self.data.is_empty() { - error!("Incomplete DVC message, it will be skipped"); - - self.data.clear(); - } - - if total_data_size == data.len() { - Some(data) - } else { - self.total_size = total_data_size; - self.data = data; - - None - } - } - - fn process_data_pdu(&mut self, mut data: Vec) -> Option> { - if self.total_size == 0 && self.data.is_empty() { - // message is not fragmented - Some(data) - } else { - // message is fragmented so need to reassemble it - let actual_data_length = self.data.len() + data.len(); - - match actual_data_length.cmp(&(self.total_size)) { - cmp::Ordering::Less => { - // this is one of the fragmented messages, just append it - self.data.append(&mut data); - None - } - cmp::Ordering::Equal => { - // this is the last fragmented message, need to return the whole reassembled message - self.total_size = 0; - self.data.append(&mut data); - Some(self.data.drain(..).collect()) - } - cmp::Ordering::Greater => { - error!("Actual DVC message size is grater than expected total DVC message size"); - self.total_size = 0; - self.data.clear(); - - None - } - } - } - } -} - /// Converts an [`ErrorInfo`] into a [`DisconnectReason`]. /// /// Returns `None` if the error code is not a graceful disconnect code. diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 6407d38ee..614fe2d59 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -603,7 +603,7 @@ impl Session { hotspot_y, })?; } - ActiveStageOutput::DeactivateAll(_) => todo!(), + ActiveStageOutput::DeactivateAll(_) => todo!("DeactivateAll"), ActiveStageOutput::Terminate(reason) => break 'outer reason, } } From ffb446557eb625d0f4380844a7d400074c64f1da Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Fri, 15 Mar 2024 11:07:19 -0700 Subject: [PATCH 10/84] Gets a `DvcClientProcessor` in full working condition in the form of `DisplayControlClient` --- Cargo.lock | 1 + crates/ironrdp-client/src/rdp.rs | 2 +- crates/ironrdp-dvc/src/client.rs | 107 ++++++++++------ crates/ironrdp-dvc/src/complete_data.rs | 5 - crates/ironrdp-dvc/src/lib.rs | 115 +++++++++++++++++- crates/ironrdp-dvc/src/server.rs | 56 +++------ crates/ironrdp-pdu/src/rdp/vc/dvc.rs | 11 +- crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs | 2 +- .../src/rdp/vc/dvc/data_first/tests.rs | 22 ++-- crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs | 20 ++- crates/ironrdp-session/Cargo.toml | 2 +- crates/ironrdp-session/src/active_stage.rs | 3 +- crates/ironrdp-session/src/x224/mod.rs | 8 +- crates/ironrdp-web/src/session.rs | 2 +- crates/ironrdp/examples/screenshot.rs | 2 +- 15 files changed, 250 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f01939893..671207be3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1915,6 +1915,7 @@ version = "0.1.0" dependencies = [ "bitflags 2.4.2", "ironrdp-connector", + "ironrdp-dvc", "ironrdp-error", "ironrdp-graphics", "ironrdp-pdu", diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index f207cf8c1..3eb0b2ff8 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -160,7 +160,7 @@ async fn active_session( connection_result.desktop_size.height, ); - let mut active_stage = ActiveStage::new(connection_result, None); + let mut active_stage = ActiveStage::new(connection_result); let disconnect_reason = 'outer: loop { let outputs = tokio::select! { diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index a0d9cc6b0..e07224c15 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -4,7 +4,7 @@ use alloc::collections::BTreeMap; use alloc::string::String; use alloc::vec; use alloc::vec::Vec; -use core::any::Any; +use core::any::{Any, TypeId}; use core::{cmp, fmt}; use ironrdp_pdu as pdu; @@ -17,7 +17,7 @@ use pdu::{dvc, PduResult}; use pdu::{other_err, PduEncode}; use crate::complete_data::CompleteData; -use crate::{encode_dvc_data, DvcMessages, DvcProcessor}; +use crate::{encode_dvc_messages, DvcMessages, DvcProcessor}; pub trait DvcClientProcessor: DvcProcessor {} @@ -60,12 +60,21 @@ impl DrdynvcClient { #[must_use] pub fn with_dynamic_channel(mut self, channel: T) -> Self where - T: DvcClientProcessor + 'static, + T: DvcProcessor + 'static, { self.dynamic_channels.insert(channel); self } + pub fn get_dynamic_channel_by_type_id(&self) -> Option<&T> + where + T: DvcProcessor, + { + self.dynamic_channels + .get_by_type_id(TypeId::of::()) + .and_then(|channel| channel.channel_processor_downcast_ref().map(|channel| channel as &T)) + } + fn create_capabilities_response(&mut self) -> SvcMessage { let caps_response = dvc::ClientPdu::CapabilitiesResponse(dvc::CapabilitiesResponsePdu { version: dvc::CapsVersion::V1, @@ -141,7 +150,7 @@ impl SvcProcessor for DrdynvcClient { // If this DVC has start messages, send them. if !start_messages.is_empty() { - responses.extend(encode_dvc_data(channel_id, start_messages)?); + responses.extend(encode_dvc_messages(channel_id, start_messages, None)?); } } dvc::ServerPdu::CloseRequest(close_request) => { @@ -156,30 +165,17 @@ impl SvcProcessor for DrdynvcClient { responses.push(SvcMessage::from(DvcMessage::new(close_response, &[]))); self.dynamic_channels.remove_by_channel_id(&close_request.channel_id); } - dvc::ServerPdu::Common(dvc::CommonPdu::DataFirst(data)) => { - let channel_id = data.channel_id; + dvc::ServerPdu::Common(common) => { + let channel_id = common.channel_id(); let dvc_data = dvc_ctx.dvc_data; let messages = self .dynamic_channels .get_by_channel_id_mut(&channel_id) .ok_or_else(|| other_err!("DVC", "access to non existing channel"))? - .process(channel_id, dvc_data)?; + .process(common, dvc_data)?; - responses.extend(encode_dvc_data(channel_id, messages)?); - } - dvc::ServerPdu::Common(dvc::CommonPdu::Data(data)) => { - // TODO: identical to DataFirst, create a helper function - let channel_id = data.channel_id; - let dvc_data = dvc_ctx.dvc_data; - - let messages = self - .dynamic_channels - .get_by_channel_id_mut(&channel_id) - .ok_or_else(|| other_err!("DVC", "access to non existing channel"))? - .process(channel_id, dvc_data)?; - - responses.extend(encode_dvc_data(channel_id, messages)?); + responses.extend(encode_dvc_messages(channel_id, messages, None)?); } } @@ -243,33 +239,54 @@ impl PduEncode for DvcMessage<'_> { } pub struct DynamicVirtualChannel { - handler: Box, + channel_processor: Box, + complete_data: CompleteData, } impl DynamicVirtualChannel { fn new(handler: T) -> Self { Self { - handler: Box::new(handler), + channel_processor: Box::new(handler), + complete_data: CompleteData::new(), } } fn start(&mut self, channel_id: DynamicChannelId) -> PduResult { - self.handler.start(channel_id) + self.channel_processor.start(channel_id) } - fn process(&mut self, channel_id: DynamicChannelId, data: &[u8]) -> PduResult { - self.handler.process(channel_id, data) + fn process(&mut self, pdu: dvc::CommonPdu, data: &[u8]) -> PduResult { + let channel_id = pdu.channel_id(); + let complete_data = self.complete_data.process_data(pdu, data.into()); + if let Some(complete_data) = complete_data { + self.channel_processor.process(channel_id, &complete_data) + } else { + Ok(vec![]) + } + } + + fn set_id(&mut self, id: DynamicChannelId) { + self.channel_processor.set_id(id); } fn channel_name(&self) -> &str { - self.handler.channel_name() + self.channel_processor.channel_name() + } + + fn channel_processor_downcast_ref(&self) -> Option<&T> { + self.channel_processor.as_any().downcast_ref() + } + + fn channel_processor_downcast_mut(&mut self) -> Option<&mut T> { + self.channel_processor.as_any_mut().downcast_mut() } } struct DynamicChannelSet { channels: BTreeMap, - name_to_id: BTreeMap, - id_to_name: BTreeMap, + name_to_channel_id: BTreeMap, + channel_id_to_name: BTreeMap, + type_id_to_name: BTreeMap, } impl DynamicChannelSet { @@ -277,16 +294,24 @@ impl DynamicChannelSet { fn new() -> Self { Self { channels: BTreeMap::new(), - name_to_id: BTreeMap::new(), - id_to_name: BTreeMap::new(), + name_to_channel_id: BTreeMap::new(), + channel_id_to_name: BTreeMap::new(), + type_id_to_name: BTreeMap::new(), } } - pub fn insert(&mut self, channel: T) -> Option { + fn insert(&mut self, channel: T) -> Option { let name = channel.channel_name().to_owned(); + self.type_id_to_name.insert(TypeId::of::(), name.clone()); self.channels.insert(name, DynamicVirtualChannel::new(channel)) } + pub fn get_by_type_id(&self, type_id: TypeId) -> Option<&DynamicVirtualChannel> { + self.type_id_to_name + .get(&type_id) + .and_then(|name| self.channels.get(name)) + } + pub fn get_by_channel_name(&self, name: &DynamicChannelName) -> Option<&DynamicVirtualChannel> { self.channels.get(name) } @@ -296,23 +321,27 @@ impl DynamicChannelSet { } pub fn get_by_channel_id(&self, id: &DynamicChannelId) -> Option<&DynamicVirtualChannel> { - self.id_to_name.get(id).and_then(|name| self.channels.get(name)) + self.channel_id_to_name.get(id).and_then(|name| self.channels.get(name)) } pub fn get_by_channel_id_mut(&mut self, id: &DynamicChannelId) -> Option<&mut DynamicVirtualChannel> { - self.id_to_name.get(id).and_then(|name| self.channels.get_mut(name)) + self.channel_id_to_name + .get(id) + .and_then(|name| self.channels.get_mut(name)) } pub fn attach_channel_id(&mut self, name: DynamicChannelName, id: DynamicChannelId) -> Option { let channel = self.get_by_channel_name_mut(&name)?; - self.id_to_name.insert(id, name.clone()); - self.name_to_id.insert(name, id) + channel.set_id(id); + self.channel_id_to_name.insert(id, name.clone()); + self.name_to_channel_id.insert(name, id) } pub fn remove_by_channel_id(&mut self, id: &DynamicChannelId) -> Option { - if let Some(name) = self.id_to_name.remove(id) { - return self.name_to_id.remove(&name); - // Channels are retained in the `self.channels` map to allow potential re-addition by the server. + if let Some(name) = self.channel_id_to_name.remove(id) { + return self.name_to_channel_id.remove(&name); + // Channels are retained in the `self.channels` and `self.type_id_to_name` map to allow potential + // dynamic re-addition by the server. } None } diff --git a/crates/ironrdp-dvc/src/complete_data.rs b/crates/ironrdp-dvc/src/complete_data.rs index d0f49a756..26facb391 100644 --- a/crates/ironrdp-dvc/src/complete_data.rs +++ b/crates/ironrdp-dvc/src/complete_data.rs @@ -77,8 +77,3 @@ impl CompleteData { } } } - -pub(crate) enum Chunk { - DataFirstPdu(dvc::DataFirstPdu), - DataPdu(dvc::DataPdu), -} diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 358ba1337..066dcbf1e 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -9,14 +9,17 @@ extern crate tracing; extern crate alloc; use alloc::boxed::Box; +use alloc::vec; use alloc::vec::Vec; // Re-export ironrdp_pdu crate for convenience #[rustfmt::skip] // do not re-order this pub use pub use ironrdp_pdu as pdu; -use ironrdp_svc::AsAny; +use ironrdp_svc::{self, impl_as_any, AsAny, SvcMessage}; +use pdu::dvc::gfx::ServerPdu; +use pdu::dvc::{self, DataFirstPdu, DataPdu}; use pdu::write_buf::WriteBuf; -use pdu::{assert_obj_safe, PduEncode, PduResult}; +use pdu::{assert_obj_safe, cast_length, custom_err, encode_vec, other_err, PduEncode, PduParsing as _, PduResult}; mod complete_data; use complete_data::CompleteData; @@ -36,8 +39,18 @@ pub type DvcMessages = Vec>; /// - Limited number of channels /// - Packet reconstruction pub trait DvcProcessor: AsAny + Send + Sync { + /// The name of the channel, e.g. "Microsoft::Windows::RDS::DisplayControl" fn channel_name(&self) -> &str; + /// The ID of the channel. Optional because + /// ID's are assigned dynamically by the server. + fn id(&self) -> Option; + + /// Sets the ID of the channel. + fn set_id(&mut self, id: u32); + + /// Returns any messages that should be sent immediately + /// upon the channel being created. fn start(&mut self, _channel_id: u32) -> PduResult; fn process(&mut self, channel_id: u32, payload: &[u8]) -> PduResult; @@ -46,3 +59,101 @@ pub trait DvcProcessor: AsAny + Send + Sync { } assert_obj_safe!(DvcProcessor); + +const DATA_MAX_SIZE: usize = 1590; + +pub(crate) fn encode_dvc_messages( + channel_id: u32, + messages: DvcMessages, + flags: Option, +) -> PduResult> { + let mut res = Vec::new(); + for msg in messages { + let total_size = msg.size(); + + let msg = encode_vec(msg.as_ref())?; + let mut off = 0; + + while off < total_size { + let rem = total_size.checked_sub(off).unwrap(); + let size = core::cmp::min(rem, DATA_MAX_SIZE); + + let pdu = if off == 0 && total_size >= DATA_MAX_SIZE { + let total_size = cast_length!("encode_dvc_data", "totalDataSize", total_size)?; + dvc::CommonPdu::DataFirst(dvc::DataFirstPdu::new(channel_id, total_size, DATA_MAX_SIZE)) + } else { + dvc::CommonPdu::Data(dvc::DataPdu::new(channel_id, size)) + }; + + let end = off + .checked_add(size) + .ok_or_else(|| other_err!("encode_dvc_data", "overflow occurred"))?; + let mut data = Vec::new(); + pdu.to_buffer(&mut data) + .map_err(|e| custom_err!("encode_dvc_messages", e))?; + data.extend_from_slice(&msg[off..end]); + let mut svc = SvcMessage::from(data); + if let Some(flags) = flags { + svc = svc.with_flags(flags); + } + res.push(svc); + off = end; + } + } + + Ok(res) +} + +pub struct DisplayControlClient { + id: Option, +} + +impl_as_any!(DisplayControlClient); + +impl DvcProcessor for DisplayControlClient { + fn channel_name(&self) -> &str { + dvc::display::CHANNEL_NAME + } + + fn id(&self) -> Option { + self.id + } + + fn set_id(&mut self, id: u32) { + self.id = Some(id); + } + + fn start(&mut self, _channel_id: u32) -> PduResult { + Ok(Vec::new()) + } + + fn process(&mut self, channel_id: u32, payload: &[u8]) -> PduResult { + // TODO: We can parse the payload here for completeness sake, + // in practice we don't need to do anything with the payload. + debug!("Got Display PDU of length: {}", payload.len()); + Ok(Vec::new()) + } +} + +impl DvcClientProcessor for DisplayControlClient {} + +impl DisplayControlClient { + pub fn new() -> Self { + Self { id: None } + } + + pub fn encode_monitors(&self, monitors: Vec) -> PduResult> { + if self.id.is_none() { + return Err(other_err!("encode_monitors", "channel id is not set")); + } + let mut buf = WriteBuf::new(); + let pdu = dvc::display::ClientPdu::DisplayControlMonitorLayout(dvc::display::MonitorLayoutPdu { monitors }); + encode_dvc_messages(self.id.unwrap(), vec![Box::new(pdu)], None) + } +} + +impl Default for DisplayControlClient { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/ironrdp-dvc/src/server.rs b/crates/ironrdp-dvc/src/server.rs index bff49b462..52317123f 100644 --- a/crates/ironrdp-dvc/src/server.rs +++ b/crates/ironrdp-dvc/src/server.rs @@ -18,9 +18,7 @@ use pdu::write_buf::WriteBuf; use pdu::{cast_length, custom_err, encode_vec, invalid_message_err, other_err, PduEncode, PduParsing}; use pdu::{dvc, PduResult}; -use crate::{CompleteData, DvcMessages, DvcProcessor}; - -const DATA_MAX_SIZE: usize = 1590; +use crate::{encode_dvc_messages, CompleteData, DvcMessages, DvcProcessor}; pub trait DvcServerProcessor: DvcProcessor {} @@ -156,7 +154,11 @@ impl SvcProcessor for DrdynvcServer { } c.state = ChannelState::Opened; let msg = c.processor.start(create_resp.channel_id)?; - resp.extend(encode_dvc_data(id, msg)?); + resp.extend(encode_dvc_messages( + id, + msg, + Some(ironrdp_svc::ChannelFlags::SHOW_PROTOCOL), + )?); } dvc::ClientPdu::CloseResponse(close_resp) => { debug!("Got DVC Close Response PDU: {close_resp:?}"); @@ -174,7 +176,11 @@ impl SvcProcessor for DrdynvcServer { } if let Some(complete) = c.complete_data.process_data(data.into(), dvc_ctx.dvc_data.into()) { let msg = c.processor.process(channel_id, &complete)?; - resp.extend(encode_dvc_data(channel_id, msg)?); + resp.extend(encode_dvc_messages( + channel_id, + msg, + Some(ironrdp_svc::ChannelFlags::SHOW_PROTOCOL), + )?); } } dvc::ClientPdu::Common(dvc::CommonPdu::Data(data)) => { @@ -185,7 +191,11 @@ impl SvcProcessor for DrdynvcServer { } if let Some(complete) = c.complete_data.process_data(data.into(), dvc_ctx.dvc_data.into()) { let msg = c.processor.process(channel_id, &complete)?; - resp.extend(encode_dvc_data(channel_id, msg)?); + resp.extend(encode_dvc_messages( + channel_id, + msg, + Some(ironrdp_svc::ChannelFlags::SHOW_PROTOCOL), + )?); } } } @@ -221,37 +231,3 @@ fn encode_dvc_message(pdu: vc::dvc::ServerPdu) -> PduResult { pdu.to_buffer(&mut buf).map_err(|e| custom_err!("DVC server pdu", e))?; Ok(SvcMessage::from(buf).with_flags(ChannelFlags::SHOW_PROTOCOL)) } - -// TODO: This is used by both client and server, so it should be moved to a common place -pub fn encode_dvc_data(channel_id: u32, messages: DvcMessages) -> PduResult> { - let mut res = Vec::new(); - for msg in messages { - let total_size = msg.size(); - - let msg = encode_vec(msg.as_ref())?; - let mut off = 0; - - while off < total_size { - let rem = total_size.checked_sub(off).unwrap(); - let size = core::cmp::min(rem, DATA_MAX_SIZE); - - let pdu = if off == 0 && total_size >= DATA_MAX_SIZE { - let total_size = cast_length!("encode_dvc_data", "totalDataSize", total_size)?; - dvc::CommonPdu::DataFirst(DataFirstPdu::new(channel_id, total_size, DATA_MAX_SIZE)) - } else { - dvc::CommonPdu::Data(DataPdu::new(channel_id, size)) - }; - - let end = off - .checked_add(size) - .ok_or_else(|| other_err!("encode_dvc_data", "overflow occurred"))?; - let mut data = Vec::new(); - pdu.to_buffer(&mut data).map_err(|e| custom_err!("DVC server pdu", e))?; - data.extend_from_slice(&msg[off..end]); - res.push(SvcMessage::from(data).with_flags(ChannelFlags::SHOW_PROTOCOL)); - off = end; - } - } - - Ok(res) -} diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs index cbcee89a0..de25e620f 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs @@ -98,6 +98,13 @@ impl CommonPdu { CommonPdu::Data(_) => "Data PDU", } } + + pub fn channel_id(&self) -> u32 { + match self { + CommonPdu::DataFirst(data_first) => data_first.channel_id, + CommonPdu::Data(data) => data.channel_id, + } + } } impl From for CommonPdu { @@ -144,7 +151,7 @@ impl ServerPdu { channel_id_type, )?)), PduType::Data => Ok(ServerPdu::Common(CommonPdu::from_buffer( - PduType::DataFirst, + PduType::Data, &mut stream, dvc_data_size, channel_id_type, @@ -217,7 +224,7 @@ impl ClientPdu { channel_id_type, )?)), PduType::Data => Ok(ClientPdu::Common(CommonPdu::from_buffer( - PduType::DataFirst, + PduType::Data, &mut stream, dvc_data_size, channel_id_type, diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs index 7712249fe..4539a58d9 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs @@ -17,7 +17,7 @@ pub struct DataPdu { impl DataPdu { pub fn new(channel_id: u32, data_size: usize) -> Self { Self { - channel_id_type: FieldType::U32, + channel_id_type: FieldType::U8, channel_id, data_size, } diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first/tests.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first/tests.rs index 0251e2919..ce32b699b 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first/tests.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first/tests.rs @@ -1,7 +1,7 @@ use lazy_static::lazy_static; use super::*; -use crate::rdp::vc::dvc::ClientPdu; +use crate::{cursor::WriteCursor, dvc::CommonPdu, rdp::vc::dvc::ClientPdu}; const DVC_TEST_CHANNEL_ID_U8: u32 = 0x03; const DVC_TEST_DATA_LENGTH: u32 = 0x0000_0C7B; @@ -127,13 +127,14 @@ lazy_static! { total_data_size: DVC_TEST_DATA_LENGTH, data_size: DVC_DATA_FIRST_BUFFER.len() }; - static ref DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH: ClientPdu = ClientPdu::DataFirst(DataFirstPdu { - channel_id_type: FieldType::U8, - channel_id: 0x7, - total_data_size_type: FieldType::U16, - total_data_size: 0x639, - data_size: DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_BUFFER.len(), - }); + static ref DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH: ClientPdu = + ClientPdu::Common(CommonPdu::DataFirst(DataFirstPdu { + channel_id_type: FieldType::U8, + channel_id: 0x7, + total_data_size_type: FieldType::U16, + total_data_size: 0x639, + data_size: DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_BUFFER.len(), + })); } #[test] @@ -212,12 +213,13 @@ fn from_buffer_correct_parses_dvc_server_pdu_with_data_first_where_total_length_ fn to_buffer_correct_serializes_dvc_server_pdu_with_data_first_where_total_length_equals_to_buffer_length() { let data_first = &*DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH; - let mut buffer = Vec::new(); + let mut b = vec![0x00; data_first.buffer_length()]; + let mut buffer = WriteCursor::new(&mut b); data_first.to_buffer(&mut buffer).unwrap(); assert_eq!( DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_PREFIX.as_ref(), - buffer.as_slice() + buffer.inner() ); } diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs index 0e9ed9a1d..4b39a283d 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs @@ -1,13 +1,12 @@ use std::io; +use crate::{cursor::WriteCursor, PduEncode, PduParsing, PduResult}; use bitflags::bitflags; use byteorder::{LittleEndian, ReadBytesExt as _, WriteBytesExt as _}; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive as _, ToPrimitive as _}; use thiserror::Error; -use crate::PduParsing; - pub const CHANNEL_NAME: &str = "Microsoft::Windows::RDS::DisplayControl"; const RDP_DISPLAY_HEADER_SIZE: usize = 8; @@ -283,6 +282,23 @@ impl PduParsing for ClientPdu { } } +impl PduEncode for ClientPdu { + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + self.to_buffer(dst).map_err(DisplayPipelineError::from)?; + Ok(()) + } + + fn name(&self) -> &'static str { + match self { + ClientPdu::DisplayControlMonitorLayout(_) => "DISPLAYCONTROL_MONITOR_LAYOUT_PDU", + } + } + + fn size(&self) -> usize { + self.buffer_length() + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] pub enum ClientPduType { DisplayControlMonitorLayout = 0x02, diff --git a/crates/ironrdp-session/Cargo.toml b/crates/ironrdp-session/Cargo.toml index f2d4ef174..0f77336bf 100644 --- a/crates/ironrdp-session/Cargo.toml +++ b/crates/ironrdp-session/Cargo.toml @@ -19,7 +19,7 @@ test = false bitflags.workspace = true # TODO: investigate usage in this crate ironrdp-connector.workspace = true # TODO: at some point, this dependency could be removed (good for compilation speed) ironrdp-svc.workspace = true -# ironrdp-dvc.workspace = true +ironrdp-dvc.workspace = true ironrdp-error.workspace = true ironrdp-graphics.workspace = true ironrdp-pdu = { workspace = true, features = ["std"] } diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index 2a9dd43b8..9b687edf8 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -12,7 +12,6 @@ use ironrdp_svc::{SvcProcessor, SvcProcessorMessages}; use crate::fast_path::UpdateKind; use crate::image::DecodedImage; -use crate::x224::GfxHandler; use crate::{fast_path, x224, SessionError, SessionResult}; pub struct ActiveStage { @@ -22,7 +21,7 @@ pub struct ActiveStage { } impl ActiveStage { - pub fn new(connection_result: ConnectionResult, graphics_handler: Option>) -> Self { + pub fn new(connection_result: ConnectionResult) -> Self { let x224_processor = x224::Processor::new( connection_result.static_channels, connection_result.user_channel_id, diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index a58b02d22..2a48ddbd1 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::legacy::SendDataIndicationCtx; +use ironrdp_dvc::{DrdynvcClient, DvcProcessor}; use ironrdp_pdu::mcs::{DisconnectProviderUltimatum, DisconnectReason, McsMessage}; use ironrdp_pdu::rdp::headers::ShareDataPdu; use ironrdp_pdu::rdp::server_error_info::{ErrorInfo, ProtocolIndependentCode, ServerSetErrorInfoPdu}; @@ -68,7 +69,7 @@ impl Processor { } } - pub fn get_svc_processor(&mut self) -> Option<&T> { + pub fn get_svc_processor(&self) -> Option<&T> { self.static_channels .get_by_type::() .and_then(|svc| svc.channel_processor_downcast_ref()) @@ -94,6 +95,11 @@ impl Processor { process_svc_messages(messages.into(), channel_id, self.user_channel_id) } + pub fn get_dvc_processor(&self) -> Option<&T> { + self.get_svc_processor::()? + .get_dynamic_channel_by_type_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> { diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 614fe2d59..8b633847d 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -426,7 +426,7 @@ impl Session { connection_result.desktop_size.height, ); - let mut active_stage = ActiveStage::new(connection_result, None); + let mut active_stage = ActiveStage::new(connection_result); let disconnect_reason = 'outer: loop { let outputs = select! { diff --git a/crates/ironrdp/examples/screenshot.rs b/crates/ironrdp/examples/screenshot.rs index 0f204ead7..ac4254cc9 100644 --- a/crates/ironrdp/examples/screenshot.rs +++ b/crates/ironrdp/examples/screenshot.rs @@ -275,7 +275,7 @@ fn active_stage( mut framed: UpgradedFramed, image: &mut DecodedImage, ) -> anyhow::Result<()> { - let mut active_stage = ActiveStage::new(connection_result, None); + let mut active_stage = ActiveStage::new(connection_result); 'outer: loop { let (action, payload) = match framed.read_pdu() { From 783b1d4f2cdfd4e560ee8247f20d3254ac7ed526 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Fri, 15 Mar 2024 16:11:40 -0700 Subject: [PATCH 11/84] Starts a display module in ironrdp-dvc I attempted to transfer everything in ironrdp_pdu::dvc::display into it, but it required more refactoring that I'd expected to get it to play nicely with the no_std possibility. This also removes the requirement of keeping track of the channel_id in each `DynamicVirtualChannel`, and instead lets the `DynamicChannelSet` take care of returning that as needed. --- Cargo.toml | 2 + crates/ironrdp-ainput/Cargo.toml | 4 +- crates/ironrdp-dvc/src/client.rs | 39 +++++++------ crates/ironrdp-dvc/src/display.rs | 63 ++++++++++++++++++++ crates/ironrdp-dvc/src/lib.rs | 67 ++-------------------- crates/ironrdp-graphics/Cargo.toml | 4 +- crates/ironrdp-pdu/Cargo.toml | 4 +- crates/ironrdp-session/src/active_stage.rs | 5 -- crates/ironrdp-session/src/x224/mod.rs | 55 +----------------- 9 files changed, 97 insertions(+), 146 deletions(-) create mode 100644 crates/ironrdp-dvc/src/display.rs diff --git a/Cargo.toml b/Cargo.toml index a79ce9f7c..de56c28b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,8 @@ thiserror = "1.0" png = "0.17" bitflags = "2.4" byteorder = "1.5" +num-derive = "0.4" +num-traits = "0.2" [profile.dev] opt-level = 1 diff --git a/crates/ironrdp-ainput/Cargo.toml b/crates/ironrdp-ainput/Cargo.toml index 542a29842..989b9fade 100644 --- a/crates/ironrdp-ainput/Cargo.toml +++ b/crates/ironrdp-ainput/Cargo.toml @@ -19,5 +19,5 @@ test = false ironrdp-pdu.workspace = true bitflags.workspace = true -num-derive = "0.4" -num-traits = "0.2" +num-derive.workspace = true +num-traits.workspace = true diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index e07224c15..fdbdb8823 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -66,13 +66,17 @@ impl DrdynvcClient { self } - pub fn get_dynamic_channel_by_type_id(&self) -> Option<&T> + pub fn get_dynamic_channel_by_type_id(&self) -> Option<(&T, Option)> where T: DvcProcessor, { self.dynamic_channels .get_by_type_id(TypeId::of::()) - .and_then(|channel| channel.channel_processor_downcast_ref().map(|channel| channel as &T)) + .and_then(|(channel, channel_id)| { + channel + .channel_processor_downcast_ref() + .map(|channel| (channel as &T, channel_id)) + }) } fn create_capabilities_response(&mut self) -> SvcMessage { @@ -265,10 +269,6 @@ impl DynamicVirtualChannel { } } - fn set_id(&mut self, id: DynamicChannelId) { - self.channel_processor.set_id(id); - } - fn channel_name(&self) -> &str { self.channel_processor.channel_name() } @@ -306,10 +306,18 @@ impl DynamicChannelSet { self.channels.insert(name, DynamicVirtualChannel::new(channel)) } - pub fn get_by_type_id(&self, type_id: TypeId) -> Option<&DynamicVirtualChannel> { - self.type_id_to_name - .get(&type_id) - .and_then(|name| self.channels.get(name)) + pub fn attach_channel_id(&mut self, name: DynamicChannelName, id: DynamicChannelId) -> Option { + let channel = self.get_by_channel_name_mut(&name)?; + self.channel_id_to_name.insert(id, name.clone()); + self.name_to_channel_id.insert(name, id) + } + + pub fn get_by_type_id(&self, type_id: TypeId) -> Option<(&DynamicVirtualChannel, Option)> { + self.type_id_to_name.get(&type_id).and_then(|name| { + self.channels + .get(name) + .map(|channel| (channel, self.name_to_channel_id.get(name).copied())) + }) } pub fn get_by_channel_name(&self, name: &DynamicChannelName) -> Option<&DynamicVirtualChannel> { @@ -330,13 +338,6 @@ impl DynamicChannelSet { .and_then(|name| self.channels.get_mut(name)) } - pub fn attach_channel_id(&mut self, name: DynamicChannelName, id: DynamicChannelId) -> Option { - let channel = self.get_by_channel_name_mut(&name)?; - channel.set_id(id); - self.channel_id_to_name.insert(id, name.clone()); - self.name_to_channel_id.insert(name, id) - } - pub fn remove_by_channel_id(&mut self, id: &DynamicChannelId) -> Option { if let Some(name) = self.channel_id_to_name.remove(id) { return self.name_to_channel_id.remove(&name); @@ -352,5 +353,5 @@ impl DynamicChannelSet { } } -type DynamicChannelName = String; -type DynamicChannelId = u32; +pub type DynamicChannelName = String; +pub type DynamicChannelId = u32; diff --git a/crates/ironrdp-dvc/src/display.rs b/crates/ironrdp-dvc/src/display.rs new file mode 100644 index 000000000..01b62fa24 --- /dev/null +++ b/crates/ironrdp-dvc/src/display.rs @@ -0,0 +1,63 @@ +use crate::encode_dvc_messages; +use crate::vec; +use crate::Box; +use crate::DvcClientProcessor; +use crate::DvcMessages; +use crate::DvcProcessor; +use crate::PduResult; +use crate::SvcMessage; +use crate::Vec; +use ironrdp_pdu::cursor::WriteCursor; +use ironrdp_pdu::dvc; +use ironrdp_pdu::other_err; +use ironrdp_pdu::write_buf::WriteBuf; +use ironrdp_pdu::PduEncode; +use ironrdp_pdu::PduParsing; +use ironrdp_svc::impl_as_any; + +pub const CHANNEL_NAME: &str = "Microsoft::Windows::RDS::DisplayControl"; + +const RDP_DISPLAY_HEADER_SIZE: usize = 8; + +pub struct DisplayControlClient {} + +impl_as_any!(DisplayControlClient); + +impl DvcProcessor for DisplayControlClient { + fn channel_name(&self) -> &str { + CHANNEL_NAME + } + + fn start(&mut self, _channel_id: u32) -> PduResult { + Ok(Vec::new()) + } + + fn process(&mut self, channel_id: u32, payload: &[u8]) -> PduResult { + // TODO: We can parse the payload here for completeness sake, + // in practice we don't need to do anything with the payload. + debug!("Got Display PDU of length: {}", payload.len()); + Ok(Vec::new()) + } +} + +impl DvcClientProcessor for DisplayControlClient {} + +impl DisplayControlClient { + pub fn new() -> Self { + Self {} + } + + pub fn encode_monitors(&self, channel_id: u32, monitors: Vec) -> PduResult> { + let mut buf = WriteBuf::new(); + let pdu = dvc::display::ClientPdu::DisplayControlMonitorLayout(dvc::display::MonitorLayoutPdu { monitors }); + encode_dvc_messages(channel_id, vec![Box::new(pdu)], None) + } +} + +impl Default for DisplayControlClient { + fn default() -> Self { + Self::new() + } +} + +// TODO: dvc::display should ultimately be moved into here diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 066dcbf1e..ab1a9d10f 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -30,6 +30,8 @@ pub use client::*; mod server; pub use server::*; +pub mod display; + pub type DvcMessages = Vec>; /// A type that is a Dynamic Virtual Channel (DVC) @@ -42,13 +44,6 @@ pub trait DvcProcessor: AsAny + Send + Sync { /// The name of the channel, e.g. "Microsoft::Windows::RDS::DisplayControl" fn channel_name(&self) -> &str; - /// The ID of the channel. Optional because - /// ID's are assigned dynamically by the server. - fn id(&self) -> Option; - - /// Sets the ID of the channel. - fn set_id(&mut self, id: u32); - /// Returns any messages that should be sent immediately /// upon the channel being created. fn start(&mut self, _channel_id: u32) -> PduResult; @@ -79,7 +74,7 @@ pub(crate) fn encode_dvc_messages( let size = core::cmp::min(rem, DATA_MAX_SIZE); let pdu = if off == 0 && total_size >= DATA_MAX_SIZE { - let total_size = cast_length!("encode_dvc_data", "totalDataSize", total_size)?; + let total_size = cast_length!("encode_dvc_messages", "totalDataSize", total_size)?; dvc::CommonPdu::DataFirst(dvc::DataFirstPdu::new(channel_id, total_size, DATA_MAX_SIZE)) } else { dvc::CommonPdu::Data(dvc::DataPdu::new(channel_id, size)) @@ -87,7 +82,7 @@ pub(crate) fn encode_dvc_messages( let end = off .checked_add(size) - .ok_or_else(|| other_err!("encode_dvc_data", "overflow occurred"))?; + .ok_or_else(|| other_err!("encode_dvc_messages", "overflow occurred"))?; let mut data = Vec::new(); pdu.to_buffer(&mut data) .map_err(|e| custom_err!("encode_dvc_messages", e))?; @@ -103,57 +98,3 @@ pub(crate) fn encode_dvc_messages( Ok(res) } - -pub struct DisplayControlClient { - id: Option, -} - -impl_as_any!(DisplayControlClient); - -impl DvcProcessor for DisplayControlClient { - fn channel_name(&self) -> &str { - dvc::display::CHANNEL_NAME - } - - fn id(&self) -> Option { - self.id - } - - fn set_id(&mut self, id: u32) { - self.id = Some(id); - } - - fn start(&mut self, _channel_id: u32) -> PduResult { - Ok(Vec::new()) - } - - fn process(&mut self, channel_id: u32, payload: &[u8]) -> PduResult { - // TODO: We can parse the payload here for completeness sake, - // in practice we don't need to do anything with the payload. - debug!("Got Display PDU of length: {}", payload.len()); - Ok(Vec::new()) - } -} - -impl DvcClientProcessor for DisplayControlClient {} - -impl DisplayControlClient { - pub fn new() -> Self { - Self { id: None } - } - - pub fn encode_monitors(&self, monitors: Vec) -> PduResult> { - if self.id.is_none() { - return Err(other_err!("encode_monitors", "channel id is not set")); - } - let mut buf = WriteBuf::new(); - let pdu = dvc::display::ClientPdu::DisplayControlMonitorLayout(dvc::display::MonitorLayoutPdu { monitors }); - encode_dvc_messages(self.id.unwrap(), vec![Box::new(pdu)], None) - } -} - -impl Default for DisplayControlClient { - fn default() -> Self { - Self::new() - } -} diff --git a/crates/ironrdp-graphics/Cargo.toml b/crates/ironrdp-graphics/Cargo.toml index 21850254b..7d21ee166 100644 --- a/crates/ironrdp-graphics/Cargo.toml +++ b/crates/ironrdp-graphics/Cargo.toml @@ -23,8 +23,8 @@ byteorder.workspace = true ironrdp-error.workspace = true ironrdp-pdu = { workspace = true, features = ["std"] } lazy_static = "1.4" -num-derive = "0.4" -num-traits = "0.2" +num-derive.workspace = true +num-traits.workspace = true thiserror.workspace = true [dev-dependencies] diff --git a/crates/ironrdp-pdu/Cargo.toml b/crates/ironrdp-pdu/Cargo.toml index ab38f33b5..a1b901d08 100644 --- a/crates/ironrdp-pdu/Cargo.toml +++ b/crates/ironrdp-pdu/Cargo.toml @@ -32,9 +32,9 @@ der-parser = "8.2" thiserror.workspace = true md5 = { package = "md-5", version = "0.10" } num-bigint = "0.4" -num-derive = "0.4" +num-derive.workspace = true num-integer = "0.1" -num-traits = "0.2" +num-traits.workspace = true sha1 = "0.10" x509-cert = { version = "0.2", default-features = false, features = ["std"] } pkcs1 = "0.7" diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index 9b687edf8..1970369bc 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -163,11 +163,6 @@ impl ActiveStage { Ok(vec![ActiveStageOutput::ResponseFrame(frame.into_inner())]) } - // /// Sends a PDU on the dynamic channel. - // pub fn encode_dynamic(&self, output: &mut WriteBuf, channel_name: &str, dvc_data: &[u8]) -> SessionResult<()> { - // self.x224_processor.encode_dynamic(output, channel_name, dvc_data) - // } - /// 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 { self.x224_processor.encode_static(output, pdu) diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index 2a48ddbd1..b674ca0c9 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -1,15 +1,12 @@ -mod display; mod gfx; -use std::collections::HashMap; - use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::legacy::SendDataIndicationCtx; +use ironrdp_dvc::DynamicChannelId; use ironrdp_dvc::{DrdynvcClient, DvcProcessor}; use ironrdp_pdu::mcs::{DisconnectProviderUltimatum, DisconnectReason, McsMessage}; use ironrdp_pdu::rdp::headers::ShareDataPdu; use ironrdp_pdu::rdp::server_error_info::{ErrorInfo, ProtocolIndependentCode, ServerSetErrorInfoPdu}; -use ironrdp_pdu::rdp::vc::dvc; use ironrdp_pdu::write_buf::WriteBuf; use ironrdp_svc::{client_encode_svc_messages, StaticChannelSet, SvcMessage, SvcProcessor, SvcProcessorMessages}; @@ -18,9 +15,6 @@ use crate::{SessionError, SessionErrorExt as _, SessionResult}; #[rustfmt::skip] pub use self::gfx::GfxHandler; -pub const RDP8_GRAPHICS_PIPELINE_NAME: &str = "Microsoft::Windows::RDS::Graphics"; -pub const RDP8_DISPLAY_PIPELINE_NAME: &str = "Microsoft::Windows::RDS::DisplayControl"; - /// X224 Processor output #[derive(Debug, Clone)] pub enum ProcessorOutput { @@ -36,11 +30,9 @@ pub enum ProcessorOutput { } pub struct Processor { - channel_map: HashMap, static_channels: StaticChannelSet, user_channel_id: u16, io_channel_id: u16, - drdynvc_channel_id: Option, connection_activation: ConnectionActivationSequence, } @@ -51,20 +43,10 @@ impl Processor { io_channel_id: u16, connection_activation: ConnectionActivationSequence, ) -> Self { - let drdynvc_channel_id = static_channels.iter().find_map(|(type_id, channel)| { - if channel.is_drdynvc() { - static_channels.get_channel_id_by_type_id(type_id) - } else { - None - } - }); - Self { static_channels, - channel_map: HashMap::new(), user_channel_id, io_channel_id, - drdynvc_channel_id, connection_activation, } } @@ -95,7 +77,7 @@ impl Processor { process_svc_messages(messages.into(), channel_id, self.user_channel_id) } - pub fn get_dvc_processor(&self) -> Option<&T> { + pub fn get_dvc_processor(&self) -> Option<(&T, Option)> { self.get_svc_processor::()? .get_dynamic_channel_by_type_id::() } @@ -183,39 +165,6 @@ impl Processor { } } - /// Sends a PDU on the dynamic channel. - // pub fn encode_dynamic(&self, output: &mut WriteBuf, channel_name: &str, dvc_data: &[u8]) -> SessionResult<()> { - // let drdynvc_channel_id = self - // .drdynvc_channel_id - // .ok_or_else(|| general_err!("dynamic virtual channel not connected"))?; - - // let dvc_channel_id = self - // .channel_map - // .get(channel_name) - // .ok_or_else(|| reason_err!("DVC", "access to non existing channel name: {}", channel_name))?; - - // let dvc_channel = self - // .dynamic_channels - // .get(dvc_channel_id) - // .ok_or_else(|| reason_err!("DVC", "access to non existing channel: {}", dvc_channel_id))?; - - // let dvc_client_data = dvc::ClientPdu::Common(dvc::CommonPdu::Data(dvc::DataPdu { - // channel_id_type: dvc_channel.channel_id_type, - // channel_id: dvc_channel.channel_id, - // data_size: dvc_data.len(), - // })); - - // crate::legacy::encode_dvc_message( - // self.user_channel_id, - // drdynvc_channel_id, - // dvc_client_data, - // dvc_data, - // output, - // )?; - - // Ok(()) - // } - /// 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 = From e1ec8eb31b88570a43126e839c09a580d395a7ff Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Fri, 15 Mar 2024 17:54:15 -0700 Subject: [PATCH 12/84] Adds `DvcPduEncode` trait and `MonitorLayoutPdu` `DvcPduEncode` trait simply inherits `PduEncode`. It is a marker trait to indicate that the implementor, when encoded, is ready to be wrapped in DataFirst and/or Data PDU(s). `MonitorLayoutPdu` is a copy of the pdu by the same name in the `ironrdp-pdu` crate. It is moved (well, copied for now) to the `ironrdp-dvc` crate and implements `DvcPduEncode`. --- Cargo.lock | 2 + crates/ironrdp-ainput/Cargo.toml | 1 + crates/ironrdp-ainput/src/lib.rs | 3 + crates/ironrdp-dvc/Cargo.toml | 1 + crates/ironrdp-dvc/src/display.rs | 170 +++++++++++++++- crates/ironrdp-dvc/src/lib.rs | 9 +- crates/ironrdp-pdu/src/macros.rs | 2 +- crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs | 19 +- crates/ironrdp-session/src/x224/gfx.rs | 201 ------------------- crates/ironrdp-session/src/x224/mod.rs | 5 - 10 files changed, 179 insertions(+), 234 deletions(-) delete mode 100644 crates/ironrdp-session/src/x224/gfx.rs diff --git a/Cargo.lock b/Cargo.lock index 671207be3..8d565eed3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1667,6 +1667,7 @@ name = "ironrdp-ainput" version = "0.1.0" dependencies = [ "bitflags 2.4.2", + "ironrdp-dvc", "ironrdp-pdu", "num-derive", "num-traits", @@ -1776,6 +1777,7 @@ dependencies = [ name = "ironrdp-dvc" version = "0.1.0" dependencies = [ + "bitflags 2.4.2", "ironrdp-pdu", "ironrdp-svc", "slab", diff --git a/crates/ironrdp-ainput/Cargo.toml b/crates/ironrdp-ainput/Cargo.toml index 989b9fade..b0d424c64 100644 --- a/crates/ironrdp-ainput/Cargo.toml +++ b/crates/ironrdp-ainput/Cargo.toml @@ -16,6 +16,7 @@ doctest = false test = false [dependencies] +ironrdp-dvc.workspace = true ironrdp-pdu.workspace = true bitflags.workspace = true diff --git a/crates/ironrdp-ainput/src/lib.rs b/crates/ironrdp-ainput/src/lib.rs index 0a368c072..7a014f948 100644 --- a/crates/ironrdp-ainput/src/lib.rs +++ b/crates/ironrdp-ainput/src/lib.rs @@ -1,4 +1,5 @@ use bitflags::bitflags; +use ironrdp_dvc::DvcPduEncode; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive as _, ToPrimitive as _}; @@ -139,6 +140,8 @@ impl PduEncode for ServerPdu { } } +impl DvcPduEncode for ServerPdu {} + impl<'de> PduDecode<'de> for ServerPdu { fn decode(src: &mut ReadCursor<'de>) -> PduResult { ensure_fixed_part_size!(in: src); diff --git a/crates/ironrdp-dvc/Cargo.toml b/crates/ironrdp-dvc/Cargo.toml index 8f8908e9a..d3ddb95f3 100644 --- a/crates/ironrdp-dvc/Cargo.toml +++ b/crates/ironrdp-dvc/Cargo.toml @@ -20,6 +20,7 @@ default = [] std = [] [dependencies] +bitflags.workspace = true ironrdp-svc.workspace = true ironrdp-pdu = { workspace = true, features = ["alloc"] } tracing.workspace = true diff --git a/crates/ironrdp-dvc/src/display.rs b/crates/ironrdp-dvc/src/display.rs index 01b62fa24..d6cbd078d 100644 --- a/crates/ironrdp-dvc/src/display.rs +++ b/crates/ironrdp-dvc/src/display.rs @@ -1,31 +1,37 @@ +//! Display Control Virtual Channel +//! [[MS-RDPEDISP]] +//! +//! [[MS-RDPEDISP]]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/d2954508-f487-48bc-8731-39743e0854a9 use crate::encode_dvc_messages; use crate::vec; use crate::Box; use crate::DvcClientProcessor; use crate::DvcMessages; +use crate::DvcPduEncode; use crate::DvcProcessor; use crate::PduResult; use crate::SvcMessage; use crate::Vec; + +use bitflags::bitflags; +use ironrdp_pdu::cast_length; use ironrdp_pdu::cursor::WriteCursor; use ironrdp_pdu::dvc; +use ironrdp_pdu::ensure_size; use ironrdp_pdu::other_err; use ironrdp_pdu::write_buf::WriteBuf; use ironrdp_pdu::PduEncode; use ironrdp_pdu::PduParsing; use ironrdp_svc::impl_as_any; -pub const CHANNEL_NAME: &str = "Microsoft::Windows::RDS::DisplayControl"; - -const RDP_DISPLAY_HEADER_SIZE: usize = 8; - +/// A client for the Display Control Virtual Channel. pub struct DisplayControlClient {} impl_as_any!(DisplayControlClient); impl DvcProcessor for DisplayControlClient { fn channel_name(&self) -> &str { - CHANNEL_NAME + "Microsoft::Windows::RDS::DisplayControl" } fn start(&mut self, _channel_id: u32) -> PduResult { @@ -47,9 +53,10 @@ impl DisplayControlClient { Self {} } - pub fn encode_monitors(&self, channel_id: u32, monitors: Vec) -> PduResult> { + /// Fully encodes a [`MonitorLayoutPdu`] with the given monitors. + pub fn encode_monitors(&self, channel_id: u32, monitors: Vec) -> PduResult> { let mut buf = WriteBuf::new(); - let pdu = dvc::display::ClientPdu::DisplayControlMonitorLayout(dvc::display::MonitorLayoutPdu { monitors }); + let pdu = MonitorLayoutPdu::new(monitors); encode_dvc_messages(channel_id, vec![Box::new(pdu)], None) } } @@ -60,4 +67,151 @@ impl Default for DisplayControlClient { } } -// TODO: dvc::display should ultimately be moved into here +/// [2.2.1.1] DISPLAYCONTROL_HEADER +/// +/// [2.2.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/3dceb555-2faf-4596-9e74-62be820df8ba +pub struct Header { + pdu_type: DisplayControlType, + length: usize, +} + +impl Header { + pub fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: Self::size()); + dst.write_u32(cast_length!("Type", self.pdu_type)?); + dst.write_u32(cast_length!("Length", self.length)?); + Ok(()) + } + + pub fn size() -> usize { + 4 /* pdu_type */ + 4 /* length */ + } +} + +/// [2.2.2.2] DISPLAYCONTROL_MONITOR_LAYOUT_PDU +/// +/// [2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/22741217-12a0-4fb8-b5a0-df43905aaf06 +pub struct MonitorLayoutPdu { + header: Header, + pub monitors: Vec, +} + +impl MonitorLayoutPdu { + pub fn new(monitors: Vec) -> Self { + Self { + header: Header { + pdu_type: DisplayControlType::MonitorLayout, + length: (Header::size() + 4 /* MonitorLayoutSize */ + 4 /* NumMonitors */ + (monitors.len() * Monitor::size())), + }, + monitors, + } + } +} + +impl PduEncode for MonitorLayoutPdu { + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: self.size()); + self.header.encode(dst)?; + dst.write_u32(cast_length!("MonitorLayoutSize", Monitor::size())?); + dst.write_u32(cast_length!("NumMonitors", self.monitors.len())?); + for monitor in &self.monitors { + monitor.encode(dst)?; + } + Ok(()) + } + + fn name(&self) -> &'static str { + "DISPLAYCONTROL_MONITOR_LAYOUT_PDU" + } + + fn size(&self) -> usize { + self.header.length + } +} + +impl DvcPduEncode for MonitorLayoutPdu {} + +/// [2.2.2.2.1] DISPLAYCONTROL_MONITOR_LAYOUT_PDU +/// +/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c +pub struct Monitor { + pub flags: MonitorFlags, + pub left: u32, + pub top: u32, + pub width: u32, + pub height: u32, + pub physical_width: u32, + pub physical_height: u32, + pub orientation: Orientation, + pub desktop_scale_factor: u32, + pub device_scale_factor: u32, +} + +impl Monitor { + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: Self::size()); + dst.write_u32(self.flags.bits()); + dst.write_u32(self.left); + dst.write_u32(self.top); + dst.write_u32(self.width); + dst.write_u32(self.height); + dst.write_u32(self.physical_width); + dst.write_u32(self.physical_height); + dst.write_u32(cast_length!("Orientation", self.orientation)?); + dst.write_u32(self.desktop_scale_factor); + dst.write_u32(self.device_scale_factor); + Ok(()) + } + fn size() -> usize { + 40 + } +} + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct MonitorFlags: u32 { + const PRIMARY = 1; + } +} + +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Orientation { + Landscape = 0, + Portrait = 90, + LandscapeFlipped = 180, + PortraitFlipped = 270, +} + +impl TryFrom for u32 { + type Error = core::convert::Infallible; + + fn try_from(value: Orientation) -> Result { + Ok(match value { + Orientation::Landscape => 0, + Orientation::Portrait => 90, + Orientation::LandscapeFlipped => 180, + Orientation::PortraitFlipped => 270, + }) + } +} + +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DisplayControlType { + /// DISPLAYCONTROL_PDU_TYPE_CAPS + Caps = 0x00000005, + /// DISPLAYCONTROL_PDU_TYPE_MONITOR_LAYOUT + MonitorLayout = 0x00000002, +} + +impl TryFrom for u32 { + type Error = core::convert::Infallible; + + fn try_from(value: DisplayControlType) -> Result { + Ok(match value { + DisplayControlType::Caps => 0x05, + DisplayControlType::MonitorLayout => 0x02, + }) + } +} diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index ab1a9d10f..deb0b4d08 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -32,7 +32,14 @@ pub use server::*; pub mod display; -pub type DvcMessages = Vec>; +/// Represents a message that, when encoded, forms a complete PDU for a given dynamic virtual channel. +/// This means a message that is ready to be wrapped in [`dvc::CommonPdu::DataFirst`] and [`dvc::CommonPdu::Data`] PDUs +/// (being split into multiple of such PDUs if necessary). +pub trait DvcPduEncode: PduEncode {} +pub type DvcMessages = Vec>; + +/// For legacy reasons, we implement [`DvcPduEncode`] for [`Vec`]. +impl DvcPduEncode for Vec {} /// A type that is a Dynamic Virtual Channel (DVC) /// diff --git a/crates/ironrdp-pdu/src/macros.rs b/crates/ironrdp-pdu/src/macros.rs index 29c3cd5a5..7d51b94c3 100644 --- a/crates/ironrdp-pdu/src/macros.rs +++ b/crates/ironrdp-pdu/src/macros.rs @@ -9,7 +9,7 @@ macro_rules! function { () => {{ fn f() {} fn type_name_of(_: T) -> &'static str { - std::any::type_name::() + core::any::type_name::() } let name = type_name_of(f); name.strip_suffix("::f").unwrap() diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs index 4b39a283d..2ddd04070 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs @@ -1,6 +1,6 @@ use std::io; -use crate::{cursor::WriteCursor, PduEncode, PduParsing, PduResult}; +use crate::PduParsing; use bitflags::bitflags; use byteorder::{LittleEndian, ReadBytesExt as _, WriteBytesExt as _}; use num_derive::{FromPrimitive, ToPrimitive}; @@ -282,23 +282,6 @@ impl PduParsing for ClientPdu { } } -impl PduEncode for ClientPdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - self.to_buffer(dst).map_err(DisplayPipelineError::from)?; - Ok(()) - } - - fn name(&self) -> &'static str { - match self { - ClientPdu::DisplayControlMonitorLayout(_) => "DISPLAYCONTROL_MONITOR_LAYOUT_PDU", - } - } - - fn size(&self) -> usize { - self.buffer_length() - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] pub enum ClientPduType { DisplayControlMonitorLayout = 0x02, diff --git a/crates/ironrdp-session/src/x224/gfx.rs b/crates/ironrdp-session/src/x224/gfx.rs deleted file mode 100644 index c55f00b8d..000000000 --- a/crates/ironrdp-session/src/x224/gfx.rs +++ /dev/null @@ -1,201 +0,0 @@ -use bitflags::bitflags; -use ironrdp_connector::GraphicsConfig; -use ironrdp_graphics::zgfx; -use ironrdp_pdu::dvc::gfx::{ - CapabilitiesAdvertisePdu, CapabilitiesV103Flags, CapabilitiesV104Flags, CapabilitiesV107Flags, - CapabilitiesV10Flags, CapabilitiesV81Flags, CapabilitiesV8Flags, CapabilitySet, ClientPdu, FrameAcknowledgePdu, - QueueDepth, ServerPdu, -}; -use ironrdp_pdu::PduParsing; - -use crate::SessionResult; - -pub trait GfxHandler { - fn on_message(&self, message: ServerPdu) -> SessionResult>; -} - -pub(crate) struct Handler { - decompressor: zgfx::Decompressor, - decompressed_buffer: Vec, - frames_decoded: u32, - gfx_handler: Option>, -} - -impl Handler { - pub(crate) fn new(gfx_handler: Option>) -> Self { - Self { - decompressor: zgfx::Decompressor::new(), - decompressed_buffer: Vec::with_capacity(1024 * 16), - frames_decoded: 0, - gfx_handler, - } - } -} - -impl Handler { - fn process_complete_data(&mut self, complete_data: Vec) -> SessionResult>> { - let mut client_pdu_buffer: Vec = Vec::new(); - self.decompressed_buffer.clear(); - self.decompressor - .decompress(complete_data.as_slice(), &mut self.decompressed_buffer)?; - let mut slice = &mut self.decompressed_buffer.as_slice(); - while !slice.is_empty() { - let gfx_pdu = ServerPdu::from_buffer(&mut slice)?; - debug!("Got GFX PDU: {:?}", gfx_pdu); - - if let ServerPdu::EndFrame(end_frame_pdu) = &gfx_pdu { - self.frames_decoded += 1; - // Enqueue an acknowledge for every end frame - let client_pdu = ClientPdu::FrameAcknowledge(FrameAcknowledgePdu { - queue_depth: QueueDepth::Suspend, - frame_id: end_frame_pdu.frame_id, - total_frames_decoded: self.frames_decoded, - }); - debug!("Sending GFX PDU: {:?}", client_pdu); - client_pdu_buffer.reserve(client_pdu_buffer.len() + client_pdu.buffer_length()); - client_pdu.to_buffer(&mut client_pdu_buffer)?; - } else { - // Handle the normal PDU - } - - // If there is a listener send all the data to the listener - if let Some(handler) = self.gfx_handler.as_mut() { - // Handle the normal PDU - let client_pdu = handler.on_message(gfx_pdu)?; - - if let Some(client_pdu) = client_pdu { - client_pdu_buffer.reserve(client_pdu_buffer.len() + client_pdu.buffer_length()); - client_pdu.to_buffer(&mut client_pdu_buffer)?; - } - } - } - - if !client_pdu_buffer.is_empty() { - return Ok(Some(client_pdu_buffer)); - } - - Ok(None) - } -} - -bitflags! { - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - struct CapabilityVersion: u32 { - const V8 = 1 << 0; - const V8_1 = 1 << 1; - const V10 = 1 << 2; - const V10_1 = 1 << 3; - const V10_2 = 1 << 4; - const V10_3 = 1 << 5; - const V10_4 = 1 << 6; - const V10_5 = 1 << 7; - const V10_6 = 1 << 8; - const V10_6ERR = 1 << 9; - const V10_7 = 1 << 10; - } -} - -pub(crate) fn create_capabilities_advertise(graphics_config: &Option) -> SessionResult> { - let mut capabilities = Vec::new(); - - if let Some(config) = graphics_config { - let capability_version = CapabilityVersion::from_bits(config.capabilities) - .ok_or_else(|| reason_err!("GFX", "invalid capabilities mask: {:x}", config.capabilities))?; - - if capability_version.contains(CapabilityVersion::V8) { - let flags = if config.thin_client { - CapabilitiesV8Flags::THIN_CLIENT - } else if config.small_cache { - CapabilitiesV8Flags::SMALL_CACHE - } else { - CapabilitiesV8Flags::empty() - }; - - capabilities.push(CapabilitySet::V8 { flags }); - } - - if capability_version.contains(CapabilityVersion::V8_1) { - let mut flags = CapabilitiesV81Flags::empty(); - if config.thin_client { - flags |= CapabilitiesV81Flags::THIN_CLIENT; - } - - if config.small_cache { - flags |= CapabilitiesV81Flags::SMALL_CACHE; - } - - if config.h264 { - flags |= CapabilitiesV81Flags::AVC420_ENABLED; - } - - capabilities.push(CapabilitySet::V8_1 { flags }); - } - - if config.avc444 { - let flags = if config.small_cache { - CapabilitiesV10Flags::SMALL_CACHE - } else { - CapabilitiesV10Flags::empty() - }; - - if capability_version.contains(CapabilityVersion::V10) { - capabilities.push(CapabilitySet::V10 { flags }); - } - - if capability_version.contains(CapabilityVersion::V10_1) { - capabilities.push(CapabilitySet::V10_1 {}); - } - - if capability_version.contains(CapabilityVersion::V10_2) { - capabilities.push(CapabilitySet::V10_2 { flags }); - } - - if capability_version.contains(CapabilityVersion::V10_3) { - let flags = if config.thin_client { - CapabilitiesV103Flags::AVC_THIN_CLIENT - } else { - CapabilitiesV103Flags::empty() - }; - capabilities.push(CapabilitySet::V10_3 { flags }); - } - - let mut flags = if config.small_cache { - CapabilitiesV104Flags::SMALL_CACHE - } else { - CapabilitiesV104Flags::empty() - }; - - if config.thin_client { - flags |= CapabilitiesV104Flags::AVC_THIN_CLIENT; - } - - if capability_version.contains(CapabilityVersion::V10_4) { - capabilities.push(CapabilitySet::V10_4 { flags }); - } - - if capability_version.contains(CapabilityVersion::V10_5) { - capabilities.push(CapabilitySet::V10_5 { flags }); - } - - if capability_version.contains(CapabilityVersion::V10_6) { - capabilities.push(CapabilitySet::V10_6 { flags }); - } - - if capability_version.contains(CapabilityVersion::V10_6ERR) { - capabilities.push(CapabilitySet::V10_6Err { flags }); - } - - if capability_version.contains(CapabilityVersion::V10_7) { - capabilities.push(CapabilitySet::V10_7 { - flags: CapabilitiesV107Flags::from_bits(flags.bits()).unwrap(), - }); - } - } - } - info!(?capabilities); - let capabilities_advertise = ClientPdu::CapabilitiesAdvertise(CapabilitiesAdvertisePdu(capabilities)); - let mut capabilities_advertise_buffer = Vec::with_capacity(capabilities_advertise.buffer_length()); - capabilities_advertise.to_buffer(&mut capabilities_advertise_buffer)?; - - Ok(capabilities_advertise_buffer) -} diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index b674ca0c9..1c4aaf6a4 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -1,5 +1,3 @@ -mod gfx; - use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::legacy::SendDataIndicationCtx; use ironrdp_dvc::DynamicChannelId; @@ -12,9 +10,6 @@ use ironrdp_svc::{client_encode_svc_messages, StaticChannelSet, SvcMessage, SvcP use crate::{SessionError, SessionErrorExt as _, SessionResult}; -#[rustfmt::skip] -pub use self::gfx::GfxHandler; - /// X224 Processor output #[derive(Debug, Clone)] pub enum ProcessorOutput { From d72c77c3dc9236c55a8fc516e434d5057515a409 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 18 Mar 2024 12:59:51 -0700 Subject: [PATCH 13/84] Adds a `SvcPduEncode` in line with the `DvcPduEncode` added previously. Also creates a `DrdynvcPdu` enum that implements `SvcPduEncode`, and begins migrating `DataFirstPdu` and `DataPdu` to the new `PduEncode` paradigm. In the process, also: - Moved `DynamicVirtualChannel` and `DynamicChannelSet` from `client.rs` to `lib.rs` since they can ultimately be used by both client and server. --- crates/ironrdp-cliprdr/src/pdu/mod.rs | 3 + crates/ironrdp-dvc/src/client.rs | 119 +-------------- crates/ironrdp-dvc/src/lib.rs | 171 ++++++++++++++++++---- crates/ironrdp-dvc/src/pdu.rs | 202 ++++++++++++++++++++++++++ crates/ironrdp-rdpdr/src/pdu/mod.rs | 3 + crates/ironrdp-svc/src/lib.rs | 12 +- 6 files changed, 368 insertions(+), 142 deletions(-) create mode 100644 crates/ironrdp-dvc/src/pdu.rs diff --git a/crates/ironrdp-cliprdr/src/pdu/mod.rs b/crates/ironrdp-cliprdr/src/pdu/mod.rs index 0041a9e3a..b2832988f 100644 --- a/crates/ironrdp-cliprdr/src/pdu/mod.rs +++ b/crates/ironrdp-cliprdr/src/pdu/mod.rs @@ -19,6 +19,7 @@ pub use self::lock::*; use bitflags::bitflags; use ironrdp_pdu::cursor::{ReadCursor, WriteCursor}; use ironrdp_pdu::{ensure_fixed_part_size, invalid_message_err, PduDecode, PduEncode, PduResult}; +use ironrdp_svc::SvcPduEncode; const MSG_TYPE_MONITOR_READY: u16 = 0x0001; const MSG_TYPE_FORMAT_LIST: u16 = 0x0002; @@ -215,6 +216,8 @@ impl PduEncode for ClipboardPdu<'_> { } } +impl SvcPduEncode for ClipboardPdu<'_> {} + impl<'de> PduDecode<'de> for ClipboardPdu<'de> { fn decode(src: &mut ReadCursor<'de>) -> PduResult { ensure_fixed_part_size!(in: src); diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index fdbdb8823..7504c73e1 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -9,7 +9,7 @@ use core::{cmp, fmt}; use ironrdp_pdu as pdu; -use ironrdp_svc::{impl_as_any, CompressionCondition, SvcClientProcessor, SvcMessage, SvcProcessor}; +use ironrdp_svc::{impl_as_any, CompressionCondition, SvcClientProcessor, SvcMessage, SvcPduEncode, SvcProcessor}; use pdu::cursor::WriteCursor; use pdu::gcc::ChannelName; use pdu::rdp::vc; @@ -17,7 +17,7 @@ use pdu::{dvc, PduResult}; use pdu::{other_err, PduEncode}; use crate::complete_data::CompleteData; -use crate::{encode_dvc_messages, DvcMessages, DvcProcessor}; +use crate::{encode_dvc_messages, DvcMessages, DvcProcessor, DynamicChannelId, DynamicChannelSet}; pub trait DvcClientProcessor: DvcProcessor {} @@ -242,116 +242,5 @@ impl PduEncode for DvcMessage<'_> { } } -pub struct DynamicVirtualChannel { - channel_processor: Box, - complete_data: CompleteData, -} - -impl DynamicVirtualChannel { - fn new(handler: T) -> Self { - Self { - channel_processor: Box::new(handler), - complete_data: CompleteData::new(), - } - } - - fn start(&mut self, channel_id: DynamicChannelId) -> PduResult { - self.channel_processor.start(channel_id) - } - - fn process(&mut self, pdu: dvc::CommonPdu, data: &[u8]) -> PduResult { - let channel_id = pdu.channel_id(); - let complete_data = self.complete_data.process_data(pdu, data.into()); - if let Some(complete_data) = complete_data { - self.channel_processor.process(channel_id, &complete_data) - } else { - Ok(vec![]) - } - } - - fn channel_name(&self) -> &str { - self.channel_processor.channel_name() - } - - fn channel_processor_downcast_ref(&self) -> Option<&T> { - self.channel_processor.as_any().downcast_ref() - } - - fn channel_processor_downcast_mut(&mut self) -> Option<&mut T> { - self.channel_processor.as_any_mut().downcast_mut() - } -} - -struct DynamicChannelSet { - channels: BTreeMap, - name_to_channel_id: BTreeMap, - channel_id_to_name: BTreeMap, - type_id_to_name: BTreeMap, -} - -impl DynamicChannelSet { - #[inline] - fn new() -> Self { - Self { - channels: BTreeMap::new(), - name_to_channel_id: BTreeMap::new(), - channel_id_to_name: BTreeMap::new(), - type_id_to_name: BTreeMap::new(), - } - } - - fn insert(&mut self, channel: T) -> Option { - let name = channel.channel_name().to_owned(); - self.type_id_to_name.insert(TypeId::of::(), name.clone()); - self.channels.insert(name, DynamicVirtualChannel::new(channel)) - } - - pub fn attach_channel_id(&mut self, name: DynamicChannelName, id: DynamicChannelId) -> Option { - let channel = self.get_by_channel_name_mut(&name)?; - self.channel_id_to_name.insert(id, name.clone()); - self.name_to_channel_id.insert(name, id) - } - - pub fn get_by_type_id(&self, type_id: TypeId) -> Option<(&DynamicVirtualChannel, Option)> { - self.type_id_to_name.get(&type_id).and_then(|name| { - self.channels - .get(name) - .map(|channel| (channel, self.name_to_channel_id.get(name).copied())) - }) - } - - pub fn get_by_channel_name(&self, name: &DynamicChannelName) -> Option<&DynamicVirtualChannel> { - self.channels.get(name) - } - - pub fn get_by_channel_name_mut(&mut self, name: &DynamicChannelName) -> Option<&mut DynamicVirtualChannel> { - self.channels.get_mut(name) - } - - pub fn get_by_channel_id(&self, id: &DynamicChannelId) -> Option<&DynamicVirtualChannel> { - self.channel_id_to_name.get(id).and_then(|name| self.channels.get(name)) - } - - pub fn get_by_channel_id_mut(&mut self, id: &DynamicChannelId) -> Option<&mut DynamicVirtualChannel> { - self.channel_id_to_name - .get(id) - .and_then(|name| self.channels.get_mut(name)) - } - - pub fn remove_by_channel_id(&mut self, id: &DynamicChannelId) -> Option { - if let Some(name) = self.channel_id_to_name.remove(id) { - return self.name_to_channel_id.remove(&name); - // Channels are retained in the `self.channels` and `self.type_id_to_name` map to allow potential - // dynamic re-addition by the server. - } - None - } - - #[inline] - pub fn values(&self) -> impl Iterator { - self.channels.values() - } -} - -pub type DynamicChannelName = String; -pub type DynamicChannelId = u32; +/// Fully encoded Dynamic Virtual Channel PDUs are sent over a static virtual channel, so they must be `SvcPduEncode`. +impl SvcPduEncode for DvcMessage<'_> {} diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index deb0b4d08..28c2f5f24 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -3,23 +3,30 @@ // TODO: this crate is WIP +use crate::alloc::borrow::ToOwned; #[macro_use] extern crate tracing; extern crate alloc; +use alloc::string::String; +use core::any::TypeId; + use alloc::boxed::Box; +use alloc::collections::BTreeMap; use alloc::vec; use alloc::vec::Vec; // Re-export ironrdp_pdu crate for convenience #[rustfmt::skip] // do not re-order this pub use -pub use ironrdp_pdu as pdu; +pub use ironrdp_pdu; +use ironrdp_pdu::dvc::gfx::ServerPdu; +use ironrdp_pdu::dvc::{self, DataFirstPdu, DataPdu}; +use ironrdp_pdu::write_buf::WriteBuf; +use ironrdp_pdu::{ + assert_obj_safe, cast_length, custom_err, encode_vec, ensure_size, other_err, PduEncode, PduParsing as _, PduResult, +}; use ironrdp_svc::{self, impl_as_any, AsAny, SvcMessage}; -use pdu::dvc::gfx::ServerPdu; -use pdu::dvc::{self, DataFirstPdu, DataPdu}; -use pdu::write_buf::WriteBuf; -use pdu::{assert_obj_safe, cast_length, custom_err, encode_vec, other_err, PduEncode, PduParsing as _, PduResult}; mod complete_data; use complete_data::CompleteData; @@ -31,15 +38,14 @@ mod server; pub use server::*; pub mod display; +mod pdu; /// Represents a message that, when encoded, forms a complete PDU for a given dynamic virtual channel. /// This means a message that is ready to be wrapped in [`dvc::CommonPdu::DataFirst`] and [`dvc::CommonPdu::Data`] PDUs /// (being split into multiple of such PDUs if necessary). -pub trait DvcPduEncode: PduEncode {} -pub type DvcMessages = Vec>; - -/// For legacy reasons, we implement [`DvcPduEncode`] for [`Vec`]. -impl DvcPduEncode for Vec {} +pub trait DvcPduEncode: PduEncode + Send {} +pub type DvcMessage = Box; +pub type DvcMessages = Vec; /// A type that is a Dynamic Virtual Channel (DVC) /// @@ -71,30 +77,31 @@ pub(crate) fn encode_dvc_messages( ) -> PduResult> { let mut res = Vec::new(); for msg in messages { - let total_size = msg.size(); + let total_length = msg.size(); + let needs_splitting = total_length >= DATA_MAX_SIZE; let msg = encode_vec(msg.as_ref())?; let mut off = 0; - while off < total_size { - let rem = total_size.checked_sub(off).unwrap(); + while off < total_length { + let first = off == 0; + let rem = total_length.checked_sub(off).unwrap(); let size = core::cmp::min(rem, DATA_MAX_SIZE); + let end = off + .checked_add(size) + .ok_or_else(|| other_err!("encode_dvc_messages", "overflow occurred"))?; - let pdu = if off == 0 && total_size >= DATA_MAX_SIZE { - let total_size = cast_length!("encode_dvc_messages", "totalDataSize", total_size)?; - dvc::CommonPdu::DataFirst(dvc::DataFirstPdu::new(channel_id, total_size, DATA_MAX_SIZE)) + let pdu = if needs_splitting && first { + pdu::DrdynvcPdu::DataFirst(pdu::DataFirstPdu::new( + channel_id, + total_length as u8, + msg[off..end].to_vec(), + )) } else { - dvc::CommonPdu::Data(dvc::DataPdu::new(channel_id, size)) + pdu::DrdynvcPdu::Data(pdu::DataPdu::new(channel_id, msg[off..end].to_vec())) }; - let end = off - .checked_add(size) - .ok_or_else(|| other_err!("encode_dvc_messages", "overflow occurred"))?; - let mut data = Vec::new(); - pdu.to_buffer(&mut data) - .map_err(|e| custom_err!("encode_dvc_messages", e))?; - data.extend_from_slice(&msg[off..end]); - let mut svc = SvcMessage::from(data); + let mut svc = SvcMessage::from(pdu); if let Some(flags) = flags { svc = svc.with_flags(flags); } @@ -105,3 +112,117 @@ pub(crate) fn encode_dvc_messages( Ok(res) } + +pub struct DynamicVirtualChannel { + channel_processor: Box, + complete_data: CompleteData, +} + +impl DynamicVirtualChannel { + fn new(handler: T) -> Self { + Self { + channel_processor: Box::new(handler), + complete_data: CompleteData::new(), + } + } + + fn start(&mut self, channel_id: DynamicChannelId) -> PduResult { + self.channel_processor.start(channel_id) + } + + fn process(&mut self, pdu: dvc::CommonPdu, data: &[u8]) -> PduResult { + let channel_id = pdu.channel_id(); + let complete_data = self.complete_data.process_data(pdu, data.into()); + if let Some(complete_data) = complete_data { + self.channel_processor.process(channel_id, &complete_data) + } else { + Ok(vec![]) + } + } + + fn channel_name(&self) -> &str { + self.channel_processor.channel_name() + } + + fn channel_processor_downcast_ref(&self) -> Option<&T> { + self.channel_processor.as_any().downcast_ref() + } + + fn channel_processor_downcast_mut(&mut self) -> Option<&mut T> { + self.channel_processor.as_any_mut().downcast_mut() + } +} + +struct DynamicChannelSet { + channels: BTreeMap, + name_to_channel_id: BTreeMap, + channel_id_to_name: BTreeMap, + type_id_to_name: BTreeMap, +} + +impl DynamicChannelSet { + #[inline] + fn new() -> Self { + Self { + channels: BTreeMap::new(), + name_to_channel_id: BTreeMap::new(), + channel_id_to_name: BTreeMap::new(), + type_id_to_name: BTreeMap::new(), + } + } + + fn insert(&mut self, channel: T) -> Option { + let name = channel.channel_name().to_owned(); + self.type_id_to_name.insert(TypeId::of::(), name.clone()); + self.channels.insert(name, DynamicVirtualChannel::new(channel)) + } + + pub fn attach_channel_id(&mut self, name: DynamicChannelName, id: DynamicChannelId) -> Option { + let channel = self.get_by_channel_name_mut(&name)?; + self.channel_id_to_name.insert(id, name.clone()); + self.name_to_channel_id.insert(name, id) + } + + pub fn get_by_type_id(&self, type_id: TypeId) -> Option<(&DynamicVirtualChannel, Option)> { + self.type_id_to_name.get(&type_id).and_then(|name| { + self.channels + .get(name) + .map(|channel| (channel, self.name_to_channel_id.get(name).copied())) + }) + } + + pub fn get_by_channel_name(&self, name: &DynamicChannelName) -> Option<&DynamicVirtualChannel> { + self.channels.get(name) + } + + pub fn get_by_channel_name_mut(&mut self, name: &DynamicChannelName) -> Option<&mut DynamicVirtualChannel> { + self.channels.get_mut(name) + } + + pub fn get_by_channel_id(&self, id: &DynamicChannelId) -> Option<&DynamicVirtualChannel> { + self.channel_id_to_name.get(id).and_then(|name| self.channels.get(name)) + } + + pub fn get_by_channel_id_mut(&mut self, id: &DynamicChannelId) -> Option<&mut DynamicVirtualChannel> { + self.channel_id_to_name + .get(id) + .and_then(|name| self.channels.get_mut(name)) + } + + pub fn remove_by_channel_id(&mut self, id: &DynamicChannelId) -> Option { + if let Some(name) = self.channel_id_to_name.remove(id) { + return self.name_to_channel_id.remove(&name); + // Channels are retained in the `self.channels` and `self.type_id_to_name` map to allow potential + // dynamic re-addition by the server. + } + None + } + + #[inline] + pub fn values(&self) -> impl Iterator { + self.channels.values() + } +} + +pub type DynamicChannelName = String; +pub type DynamicChannelId = u32; diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs new file mode 100644 index 000000000..88c78296e --- /dev/null +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -0,0 +1,202 @@ +use crate::{DynamicChannelId, Vec}; +use ironrdp_pdu::{cast_length, cursor::WriteCursor, ensure_size, PduEncode, PduResult}; +use ironrdp_svc::SvcPduEncode; + +// TODO: The rest of the PDU's currently in `ironrdp-pdu/src/rdp/vc/dvc.rs` should ultimately be moved here. +pub enum DrdynvcPdu { + DataFirst(DataFirstPdu), + Data(DataPdu), +} + +impl PduEncode for DrdynvcPdu { + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + match self { + DrdynvcPdu::DataFirst(pdu) => pdu.encode(dst), + DrdynvcPdu::Data(pdu) => pdu.encode(dst), + } + } + + fn name(&self) -> &'static str { + match self { + DrdynvcPdu::DataFirst(pdu) => pdu.name(), + DrdynvcPdu::Data(pdu) => pdu.name(), + } + } + + fn size(&self) -> usize { + match self { + DrdynvcPdu::DataFirst(pdu) => pdu.size(), + DrdynvcPdu::Data(pdu) => pdu.size(), + } + } +} + +/// Dynamic virtual channel PDU's are sent over a static virtual channel, so they are `SvcPduEncode`. +impl SvcPduEncode for DrdynvcPdu {} + +/// [2.2] Message Syntax +/// +/// [2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/0b07a750-bf51-4042-bcf2-a991b6729d6e +struct Header { + cb_id: FieldType, // 2 bit + sp: FieldType, // 2 bit; meaning depends on the cmd field + cmd: Cmd, // 4 bit +} + +impl Header { + fn new(cmd: Cmd) -> Self { + // Always using U32 for cb_id and sp + // ensures that their respective values + // always fit. + Self { + cb_id: FieldType::U32, + sp: FieldType::U32, + cmd, + } + } + + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: Self::size()); + dst.write_u8((self.cmd as u8) << 4 | (self.sp as u8) << 2 | (self.cb_id as u8)); + Ok(()) + } + + fn size() -> usize { + 1 + } +} + +/// [2.2] Message Syntax +/// +/// [2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/0b07a750-bf51-4042-bcf2-a991b6729d6e +#[repr(u8)] +#[derive(Copy, Clone)] +enum Cmd { + Create = 0x01, + DataFirst = 0x02, + Data = 0x03, + Close = 0x04, + Capability = 0x05, + DataFirstCompressed = 0x06, + DataCompressed = 0x07, + SoftSyncRequest = 0x08, + SoftSyncResponse = 0x09, +} + +/// 2.2.3.1 DVC Data First PDU (DYNVC_DATA_FIRST) +/// +/// [2.2.3.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/69377767-56a6-4ab8-996b-7758676e9261 +pub struct DataFirstPdu { + header: Header, + channel_id: DynamicChannelId, + /// Length is the *total* length of the data to be sent, including the length + /// of the data that will be sent by subsequent DVC_DATA PDUs. + length: u8, + /// Data is just the data to be sent in this PDU. + data: Vec, +} + +impl DataFirstPdu { + /// Create a new `DataFirstPdu` with the given `channel_id`, `length`, and `data`. + /// + /// `length` is the *total* length of the data to be sent, including the length + /// of the data that will be sent by subsequent `DataPdu`s. + /// + /// `data` is just the data to be sent in this PDU. + pub fn new(channel_id: DynamicChannelId, total_length: u8, data: Vec) -> Self { + Self { + header: Header::new(Cmd::DataFirst), + channel_id, + length: total_length, + data, + } + } + + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: self.size()); + self.header.encode(dst)?; + self.header.cb_id.encode(self.channel_id, dst)?; + self.header + .sp + .encode(cast_length!("DataFirstPdu::Length", self.length)?, dst)?; + dst.write_slice(&self.data); + Ok(()) + } + + fn name(&self) -> &'static str { + "DYNVC_DATA_FIRST" + } + + fn size(&self) -> usize { + Header::size() + + self.header.cb_id.size_of_val() + // ChannelId + self.header.sp.size_of_val() + // Length + self.data.len() // Data + } +} + +#[repr(u8)] +#[derive(Copy, Clone)] +pub enum FieldType { + U8 = 0x00, + U16 = 0x01, + U32 = 0x02, +} + +impl FieldType { + fn encode(&self, value: u32, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: self.size_of_val()); + match self { + FieldType::U8 => dst.write_u8(cast_length!("FieldType::encode", value)?), + FieldType::U16 => dst.write_u16(cast_length!("FieldType::encode", value)?), + FieldType::U32 => dst.write_u32(value), + }; + Ok(()) + } + + /// Returns the size of the value in bytes. + fn size_of_val(&self) -> usize { + match self { + FieldType::U8 => 1, + FieldType::U16 => 2, + FieldType::U32 => 4, + } + } +} + +/// 2.2.3.2 DVC Data PDU (DYNVC_DATA) +/// +/// [2.2.3.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/15b59886-db44-47f1-8da3-47c8fcd82803 +pub struct DataPdu { + header: Header, + channel_id: DynamicChannelId, + data: Vec, +} + +impl DataPdu { + pub fn new(channel_id: DynamicChannelId, data: Vec) -> Self { + Self { + header: Header::new(Cmd::Data), + channel_id, + data, + } + } + + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: self.size()); + self.header.encode(dst)?; + self.header.cb_id.encode(self.channel_id, dst)?; + dst.write_slice(&self.data); + Ok(()) + } + + fn name(&self) -> &'static str { + "DYNVC_DATA" + } + + fn size(&self) -> usize { + Header::size() + + self.header.cb_id.size_of_val() + // ChannelId + self.data.len() // Data + } +} diff --git a/crates/ironrdp-rdpdr/src/pdu/mod.rs b/crates/ironrdp-rdpdr/src/pdu/mod.rs index be35c304c..227a714f1 100644 --- a/crates/ironrdp-rdpdr/src/pdu/mod.rs +++ b/crates/ironrdp-rdpdr/src/pdu/mod.rs @@ -3,6 +3,7 @@ use std::mem::size_of; use ironrdp_pdu::cursor::{ReadCursor, WriteCursor}; use ironrdp_pdu::{ensure_size, invalid_message_err, unsupported_pdu_err, PduDecode, PduEncode, PduError, PduResult}; +use ironrdp_svc::SvcPduEncode; use self::efs::{ ClientDeviceListAnnounce, ClientDriveQueryDirectoryResponse, ClientDriveQueryInformationResponse, @@ -187,6 +188,8 @@ impl PduEncode for RdpdrPdu { } } +impl SvcPduEncode for RdpdrPdu {} + impl fmt::Debug for RdpdrPdu { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/crates/ironrdp-svc/src/lib.rs b/crates/ironrdp-svc/src/lib.rs index 14fdfdfba..f8a138f36 100644 --- a/crates/ironrdp-svc/src/lib.rs +++ b/crates/ironrdp-svc/src/lib.rs @@ -54,11 +54,19 @@ impl From> for Vec { } } +/// Represents a message that, when encoded, forms a complete PDU for a given static virtual channel, sans any [`ChannelPduHeader`]. +/// In other words, this marker should be applied to a message that is ready to be chunkified (have [`ChannelPduHeader`]s added, +/// splitting it into chunks if necessary) and wrapped in MCS, x224, and tpkt headers for sending over the wire. +pub trait SvcPduEncode: PduEncode + Send {} + +/// For legacy reasons, we implement [`SvcPduEncode`] for [`Vec`]. +impl SvcPduEncode for Vec {} + /// Encodable PDU to be sent over a static virtual channel. /// /// Additional SVC header flags can be added via [`SvcMessage::with_flags`] method. pub struct SvcMessage { - pdu: Box, + pdu: Box, flags: ChannelFlags, } @@ -73,7 +81,7 @@ impl SvcMessage { impl From for SvcMessage where - T: PduEncode + Send + 'static, + T: SvcPduEncode + 'static, { fn from(pdu: T) -> Self { Self { From e93cea029dc1b37418424b38f45e3a349b99cdff Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 18 Mar 2024 13:32:10 -0700 Subject: [PATCH 14/84] Removes now no-longer-necesary is_drdynvc. Also fix compilation in server.rs --- crates/ironrdp-dvc/src/client.rs | 4 ---- crates/ironrdp-dvc/src/lib.rs | 3 +++ crates/ironrdp-server/src/server.rs | 4 +++- crates/ironrdp-svc/src/lib.rs | 10 ---------- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 7504c73e1..f8f67ffba 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -185,10 +185,6 @@ impl SvcProcessor for DrdynvcClient { Ok(responses) } - - fn is_drdynvc(&self) -> bool { - true - } } impl SvcClientProcessor for DrdynvcClient {} diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 28c2f5f24..91f3d5e37 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -47,6 +47,9 @@ pub trait DvcPduEncode: PduEncode + Send {} pub type DvcMessage = Box; pub type DvcMessages = Vec; +/// We implement `DvcPduEncode` for `Vec` for legacy reasons. +impl DvcPduEncode for Vec {} + /// A type that is a Dynamic Virtual Channel (DVC) /// /// Dynamic virtual channels may be created at any point during the RDP session. diff --git a/crates/ironrdp-server/src/server.rs b/crates/ironrdp-server/src/server.rs index 4b87676d5..6e393a7ab 100644 --- a/crates/ironrdp-server/src/server.rs +++ b/crates/ironrdp-server/src/server.rs @@ -478,7 +478,9 @@ impl RdpServer { debug!(?data, "McsMessage::SendDataRequest"); if data.channel_id == io_channel_id { return self.handle_io_channel_data(data).await; - } else if let Some(svc) = self.static_channels.get_by_channel_id_mut(data.channel_id) { + } + + if let Some(svc) = self.static_channels.get_by_channel_id_mut(data.channel_id) { let response_pdus = svc.process(&data.user_data)?; let response = server_encode_svc_messages(response_pdus, data.channel_id, user_channel_id)?; framed.write_all(&response).await?; diff --git a/crates/ironrdp-svc/src/lib.rs b/crates/ironrdp-svc/src/lib.rs index f8a138f36..054b1dd6c 100644 --- a/crates/ironrdp-svc/src/lib.rs +++ b/crates/ironrdp-svc/src/lib.rs @@ -143,10 +143,6 @@ impl StaticVirtualChannel { ChunkProcessor::chunkify(messages, CHANNEL_CHUNK_LENGTH) } - pub fn is_drdynvc(&self) -> bool { - self.channel_processor.is_drdynvc() - } - pub fn channel_processor_downcast_ref(&self) -> Option<&T> { self.channel_processor.as_any().downcast_ref() } @@ -249,12 +245,6 @@ pub trait SvcProcessor: AsAny + fmt::Debug + Send { /// /// Returns a list of PDUs to be sent back. fn process(&mut self, payload: &[u8]) -> PduResult>; - - #[doc(hidden)] - fn is_drdynvc(&self) -> bool { - // FIXME(#61): temporary method that will be removed once drdynvc is ported to the new API - false - } } assert_obj_safe!(SvcProcessor); From b382e816998fb9b799c0bf9a7497be68dbbe1502 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 18 Mar 2024 17:26:28 -0700 Subject: [PATCH 15/84] Adds CreateResponsePdu, ClosePdu, and CapabilitiesResponsePdu, gets rid of now superfluous DvcMessage<'a> --- crates/ironrdp-dvc/src/client.rs | 63 ++--------- crates/ironrdp-dvc/src/pdu.rs | 189 +++++++++++++++++++++++++++++-- 2 files changed, 189 insertions(+), 63 deletions(-) diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index f8f67ffba..19e868c37 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -17,6 +17,7 @@ use pdu::{dvc, PduResult}; use pdu::{other_err, PduEncode}; use crate::complete_data::CompleteData; +use crate::pdu::{CapabilitiesResponsePdu, CapsVersion, ClosePdu, CreateResponsePdu, CreationStatus, DrdynvcPdu}; use crate::{encode_dvc_messages, DvcMessages, DvcProcessor, DynamicChannelId, DynamicChannelSet}; pub trait DvcClientProcessor: DvcProcessor {} @@ -80,15 +81,10 @@ impl DrdynvcClient { } fn create_capabilities_response(&mut self) -> SvcMessage { - let caps_response = dvc::ClientPdu::CapabilitiesResponse(dvc::CapabilitiesResponsePdu { - version: dvc::CapsVersion::V1, - }); + let caps_response = DrdynvcPdu::CapabilitiesResponse(CapabilitiesResponsePdu::new(CapsVersion::V1)); debug!("Send DVC Capabilities Response PDU: {caps_response:?}"); self.cap_handshake_done = true; - SvcMessage::from(DvcMessage { - dvc_pdu: caps_response, - dvc_data: &[], - }) + SvcMessage::from(caps_response) } } @@ -138,19 +134,14 @@ impl SvcProcessor for DrdynvcClient { self.dynamic_channels .attach_channel_id(channel_name.clone(), channel_id); let dynamic_channel = self.dynamic_channels.get_by_channel_name_mut(&channel_name).unwrap(); - (dvc::DVC_CREATION_STATUS_OK, dynamic_channel.start(channel_id)?) + (CreationStatus::OK, dynamic_channel.start(channel_id)?) } else { - (dvc::DVC_CREATION_STATUS_NO_LISTENER, vec![]) + (CreationStatus::NO_LISTENER, vec![]) }; - // Send the Create Response PDU. - let create_response = dvc::ClientPdu::CreateResponse(dvc::CreateResponsePdu { - channel_id_type: create_request.channel_id_type, - channel_id, - creation_status, - }); + let create_response = DrdynvcPdu::CreateResponse(CreateResponsePdu::new(channel_id, creation_status)); debug!("Send DVC Create Response PDU: {create_response:?}"); - responses.push(SvcMessage::from(DvcMessage::new(create_response, &[]))); + responses.push(SvcMessage::from(create_response)); // If this DVC has start messages, send them. if !start_messages.is_empty() { @@ -159,15 +150,12 @@ impl SvcProcessor for DrdynvcClient { } dvc::ServerPdu::CloseRequest(close_request) => { debug!("Got DVC Close Request PDU: {close_request:?}"); + self.dynamic_channels.remove_by_channel_id(&close_request.channel_id); - let close_response = dvc::ClientPdu::CloseResponse(dvc::ClosePdu { - channel_id_type: close_request.channel_id_type, - channel_id: close_request.channel_id, - }); + let close_response = DrdynvcPdu::Close(ClosePdu::new(close_request.channel_id)); debug!("Send DVC Close Response PDU: {close_response:?}"); - responses.push(SvcMessage::from(DvcMessage::new(close_response, &[]))); - self.dynamic_channels.remove_by_channel_id(&close_request.channel_id); + responses.push(SvcMessage::from(close_response)); } dvc::ServerPdu::Common(common) => { let channel_id = common.channel_id(); @@ -209,34 +197,3 @@ fn decode_dvc_message(user_data: &[u8]) -> PduResult> { Ok(DynamicChannelCtx { dvc_pdu, dvc_data }) } - -/// TODO: this is the same as server.rs's `DynamicChannelCtx`, can we unify them? -struct DvcMessage<'a> { - dvc_pdu: vc::dvc::ClientPdu, - dvc_data: &'a [u8], -} - -impl<'a> DvcMessage<'a> { - fn new(dvc_pdu: vc::dvc::ClientPdu, dvc_data: &'a [u8]) -> Self { - Self { dvc_pdu, dvc_data } - } -} - -impl PduEncode for DvcMessage<'_> { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - self.dvc_pdu.to_buffer(dst)?; - dst.write_slice(self.dvc_data); - Ok(()) - } - - fn name(&self) -> &'static str { - self.dvc_pdu.as_short_name() - } - - fn size(&self) -> usize { - self.dvc_pdu.buffer_length() + self.dvc_data.len() - } -} - -/// Fully encoded Dynamic Virtual Channel PDUs are sent over a static virtual channel, so they must be `SvcPduEncode`. -impl SvcPduEncode for DvcMessage<'_> {} diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index 88c78296e..5061de5be 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -3,9 +3,13 @@ use ironrdp_pdu::{cast_length, cursor::WriteCursor, ensure_size, PduEncode, PduR use ironrdp_svc::SvcPduEncode; // TODO: The rest of the PDU's currently in `ironrdp-pdu/src/rdp/vc/dvc.rs` should ultimately be moved here. +#[derive(Debug)] pub enum DrdynvcPdu { + CapabilitiesResponse(CapabilitiesResponsePdu), + CreateResponse(CreateResponsePdu), DataFirst(DataFirstPdu), Data(DataPdu), + Close(ClosePdu), } impl PduEncode for DrdynvcPdu { @@ -13,6 +17,9 @@ impl PduEncode for DrdynvcPdu { match self { DrdynvcPdu::DataFirst(pdu) => pdu.encode(dst), DrdynvcPdu::Data(pdu) => pdu.encode(dst), + DrdynvcPdu::CreateResponse(pdu) => pdu.encode(dst), + DrdynvcPdu::Close(pdu) => pdu.encode(dst), + DrdynvcPdu::CapabilitiesResponse(pdu) => pdu.encode(dst), } } @@ -20,6 +27,9 @@ impl PduEncode for DrdynvcPdu { match self { DrdynvcPdu::DataFirst(pdu) => pdu.name(), DrdynvcPdu::Data(pdu) => pdu.name(), + DrdynvcPdu::CreateResponse(pdu) => pdu.name(), + DrdynvcPdu::Close(pdu) => pdu.name(), + DrdynvcPdu::CapabilitiesResponse(pdu) => pdu.name(), } } @@ -27,6 +37,9 @@ impl PduEncode for DrdynvcPdu { match self { DrdynvcPdu::DataFirst(pdu) => pdu.size(), DrdynvcPdu::Data(pdu) => pdu.size(), + DrdynvcPdu::CreateResponse(pdu) => pdu.size(), + DrdynvcPdu::Close(pdu) => pdu.size(), + DrdynvcPdu::CapabilitiesResponse(pdu) => pdu.size(), } } } @@ -37,6 +50,7 @@ impl SvcPduEncode for DrdynvcPdu {} /// [2.2] Message Syntax /// /// [2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/0b07a750-bf51-4042-bcf2-a991b6729d6e +#[derive(Debug)] struct Header { cb_id: FieldType, // 2 bit sp: FieldType, // 2 bit; meaning depends on the cmd field @@ -44,13 +58,13 @@ struct Header { } impl Header { - fn new(cmd: Cmd) -> Self { - // Always using U32 for cb_id and sp - // ensures that their respective values - // always fit. + /// Create a new `Header` with the given `cb_id_val`, `sp_val`, and `cmd`. + /// + /// If `cb_id_val` or `sp_val` is not relevant for a given `cmd`, it should be set to 0 respectively. + fn new(cb_id_val: u32, sp_val: u32, cmd: Cmd) -> Self { Self { - cb_id: FieldType::U32, - sp: FieldType::U32, + cb_id: FieldType::for_val(cb_id_val), + sp: FieldType::for_val(sp_val), cmd, } } @@ -70,7 +84,7 @@ impl Header { /// /// [2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/0b07a750-bf51-4042-bcf2-a991b6729d6e #[repr(u8)] -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone)] enum Cmd { Create = 0x01, DataFirst = 0x02, @@ -86,6 +100,7 @@ enum Cmd { /// 2.2.3.1 DVC Data First PDU (DYNVC_DATA_FIRST) /// /// [2.2.3.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/69377767-56a6-4ab8-996b-7758676e9261 +#[derive(Debug)] pub struct DataFirstPdu { header: Header, channel_id: DynamicChannelId, @@ -105,7 +120,7 @@ impl DataFirstPdu { /// `data` is just the data to be sent in this PDU. pub fn new(channel_id: DynamicChannelId, total_length: u8, data: Vec) -> Self { Self { - header: Header::new(Cmd::DataFirst), + header: Header::new(channel_id, total_length.into(), Cmd::DataFirst), channel_id, length: total_length, data, @@ -136,7 +151,7 @@ impl DataFirstPdu { } #[repr(u8)] -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone)] pub enum FieldType { U8 = 0x00, U16 = 0x01, @@ -162,11 +177,22 @@ impl FieldType { FieldType::U32 => 4, } } + + fn for_val(value: u32) -> Self { + if value <= u8::MAX as u32 { + FieldType::U8 + } else if value <= u16::MAX as u32 { + FieldType::U16 + } else { + FieldType::U32 + } + } } /// 2.2.3.2 DVC Data PDU (DYNVC_DATA) /// /// [2.2.3.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/15b59886-db44-47f1-8da3-47c8fcd82803 +#[derive(Debug)] pub struct DataPdu { header: Header, channel_id: DynamicChannelId, @@ -176,7 +202,7 @@ pub struct DataPdu { impl DataPdu { pub fn new(channel_id: DynamicChannelId, data: Vec) -> Self { Self { - header: Header::new(Cmd::Data), + header: Header::new(channel_id, 0, Cmd::Data), channel_id, data, } @@ -200,3 +226,146 @@ impl DataPdu { self.data.len() // Data } } + +/// 2.2.2.2 DVC Create Response PDU (DYNVC_CREATE_RSP) +/// +/// [2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/8f284ea3-54f3-4c24-8168-8a001c63b581 +#[derive(Debug)] +pub struct CreateResponsePdu { + header: Header, + channel_id: DynamicChannelId, + creation_status: CreationStatus, +} + +impl CreateResponsePdu { + pub fn new(channel_id: DynamicChannelId, creation_status: CreationStatus) -> Self { + Self { + header: Header::new(channel_id, 0, Cmd::Create), + channel_id, + creation_status, + } + } + + fn name(&self) -> &'static str { + "DYNVC_CREATE_RSP" + } + + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: self.size()); + self.header.encode(dst)?; + self.header.cb_id.encode(self.channel_id, dst)?; + self.creation_status.encode(dst)?; + Ok(()) + } + + fn size(&self) -> usize { + Header::size() + + self.header.cb_id.size_of_val() + // ChannelId + CreationStatus::size() // CreationStatus + } +} + +#[derive(Debug)] +pub struct CreationStatus(u32); + +impl CreationStatus { + pub const OK: Self = Self(0x00000000); + pub const NO_LISTENER: Self = Self(0xC0000001); + + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: Self::size()); + dst.write_u32(self.0); + Ok(()) + } + + fn size() -> usize { + 4 + } +} + +/// 2.2.4 Closing a DVC (DYNVC_CLOSE) +/// +/// [2.2.4]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/c02dfd21-ccbc-4254-985b-3ef6dd115dec +#[derive(Debug)] +pub struct ClosePdu { + header: Header, + channel_id: DynamicChannelId, +} + +impl ClosePdu { + pub fn new(channel_id: DynamicChannelId) -> Self { + Self { + header: Header::new(channel_id, 0, Cmd::Close), + channel_id, + } + } + + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: self.size()); + self.header.encode(dst)?; + self.header.cb_id.encode(self.channel_id, dst)?; + Ok(()) + } + + fn name(&self) -> &'static str { + "DYNVC_CLOSE" + } + + fn size(&self) -> usize { + Header::size() + self.header.cb_id.size_of_val() + } +} + +/// 2.2.1.2 DVC Capabilities Response PDU (DYNVC_CAPS_RSP) +/// +/// [2.2.1.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/d45cb2a6-e7bd-453e-8603-9c57600e24ce +#[derive(Debug)] +pub struct CapabilitiesResponsePdu { + header: Header, + version: CapsVersion, +} + +impl CapabilitiesResponsePdu { + pub fn new(version: CapsVersion) -> Self { + Self { + header: Header::new(0, 0, Cmd::Capability), + version, + } + } + + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: self.size()); + self.header.encode(dst)?; + dst.write_u8(0x00); // Pad, MUST be 0x00 + self.version.encode(dst)?; + Ok(()) + } + + fn name(&self) -> &'static str { + "DYNVC_CAPS_RSP" + } + + fn size(&self) -> usize { + Header::size() + 1 /* Pad */ + CapsVersion::size() + } +} + +#[repr(u16)] +#[derive(Debug, Copy, Clone)] +pub enum CapsVersion { + V1 = 0x0001, + V2 = 0x0002, + V3 = 0x0003, +} + +impl CapsVersion { + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: Self::size()); + dst.write_u16(*self as u16); + Ok(()) + } + + fn size() -> usize { + 2 + } +} From 8ec38e1da6c033f21ac829b782e163e40e7cc9e9 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 19 Mar 2024 16:26:01 -0700 Subject: [PATCH 16/84] Removes all legacy ironrdp-pdu/src/rdp/vc/dvc/ pdus from ironrdp-dvc. This has been tested to work for screen resize. The refactored server side code has not been tested. --- crates/ironrdp-dvc/src/client.rs | 59 +-- crates/ironrdp-dvc/src/complete_data.rs | 43 +- crates/ironrdp-dvc/src/display.rs | 2 - crates/ironrdp-dvc/src/lib.rs | 17 +- crates/ironrdp-dvc/src/pdu.rs | 513 +++++++++++++++++++++--- crates/ironrdp-dvc/src/server.rs | 88 ++-- crates/ironrdp-pdu/src/cursor.rs | 4 + 7 files changed, 541 insertions(+), 185 deletions(-) diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 19e868c37..61710b722 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -1,3 +1,9 @@ +use crate::complete_data::CompleteData; +use crate::pdu::{ + CapabilitiesResponsePdu, CapsVersion, ClosePdu, CreateResponsePdu, CreationStatus, DrdynvcClientPdu, + DrdynvcDataPdu, DrdynvcServerPdu, +}; +use crate::{encode_dvc_messages, DvcMessages, DvcProcessor, DynamicChannelId, DynamicChannelSet}; use alloc::borrow::ToOwned; use alloc::boxed::Box; use alloc::collections::BTreeMap; @@ -6,20 +12,15 @@ use alloc::vec; use alloc::vec::Vec; use core::any::{Any, TypeId}; use core::{cmp, fmt}; - use ironrdp_pdu as pdu; - use ironrdp_svc::{impl_as_any, CompressionCondition, SvcClientProcessor, SvcMessage, SvcPduEncode, SvcProcessor}; -use pdu::cursor::WriteCursor; +use pdu::cursor::{ReadCursor, WriteCursor}; use pdu::gcc::ChannelName; use pdu::rdp::vc; +use pdu::PduDecode as _; use pdu::{dvc, PduResult}; use pdu::{other_err, PduEncode}; -use crate::complete_data::CompleteData; -use crate::pdu::{CapabilitiesResponsePdu, CapsVersion, ClosePdu, CreateResponsePdu, CreationStatus, DrdynvcPdu}; -use crate::{encode_dvc_messages, DvcMessages, DvcProcessor, DynamicChannelId, DynamicChannelSet}; - pub trait DvcClientProcessor: DvcProcessor {} /// DRDYNVC Static Virtual Channel (the Remote Desktop Protocol: Dynamic Virtual Channel Extension) @@ -81,7 +82,7 @@ impl DrdynvcClient { } fn create_capabilities_response(&mut self) -> SvcMessage { - let caps_response = DrdynvcPdu::CapabilitiesResponse(CapabilitiesResponsePdu::new(CapsVersion::V1)); + let caps_response = DrdynvcClientPdu::Capabilities(CapabilitiesResponsePdu::new(CapsVersion::V1)); debug!("Send DVC Capabilities Response PDU: {caps_response:?}"); self.cap_handshake_done = true; SvcMessage::from(caps_response) @@ -106,15 +107,15 @@ impl SvcProcessor for DrdynvcClient { } fn process(&mut self, payload: &[u8]) -> PduResult> { - let dvc_ctx = decode_dvc_message(payload)?; + let pdu = decode_dvc_message(payload)?; let mut responses = Vec::new(); - match dvc_ctx.dvc_pdu { - dvc::ServerPdu::CapabilitiesRequest(caps_request) => { + match pdu { + DrdynvcServerPdu::Capabilities(caps_request) => { debug!("Got DVC Capabilities Request PDU: {caps_request:?}"); responses.push(self.create_capabilities_response()); } - dvc::ServerPdu::CreateRequest(create_request) => { + DrdynvcServerPdu::Create(create_request) => { debug!("Got DVC Create Request PDU: {create_request:?}"); let channel_name = create_request.channel_name.clone(); let channel_id = create_request.channel_id; @@ -139,7 +140,7 @@ impl SvcProcessor for DrdynvcClient { (CreationStatus::NO_LISTENER, vec![]) }; - let create_response = DrdynvcPdu::CreateResponse(CreateResponsePdu::new(channel_id, creation_status)); + let create_response = DrdynvcClientPdu::Create(CreateResponsePdu::new(channel_id, creation_status)); debug!("Send DVC Create Response PDU: {create_response:?}"); responses.push(SvcMessage::from(create_response)); @@ -148,24 +149,23 @@ impl SvcProcessor for DrdynvcClient { responses.extend(encode_dvc_messages(channel_id, start_messages, None)?); } } - dvc::ServerPdu::CloseRequest(close_request) => { + DrdynvcServerPdu::Close(close_request) => { debug!("Got DVC Close Request PDU: {close_request:?}"); self.dynamic_channels.remove_by_channel_id(&close_request.channel_id); - let close_response = DrdynvcPdu::Close(ClosePdu::new(close_request.channel_id)); + let close_response = DrdynvcClientPdu::Close(ClosePdu::new(close_request.channel_id)); debug!("Send DVC Close Response PDU: {close_response:?}"); responses.push(SvcMessage::from(close_response)); } - dvc::ServerPdu::Common(common) => { - let channel_id = common.channel_id(); - let dvc_data = dvc_ctx.dvc_data; + DrdynvcServerPdu::Data(data) => { + let channel_id = data.channel_id(); let messages = self .dynamic_channels .get_by_channel_id_mut(&channel_id) .ok_or_else(|| other_err!("DVC", "access to non existing channel"))? - .process(common, dvc_data)?; + .process(data)?; responses.extend(encode_dvc_messages(channel_id, messages, None)?); } @@ -177,23 +177,6 @@ impl SvcProcessor for DrdynvcClient { impl SvcClientProcessor for DrdynvcClient {} -struct DynamicChannelCtx<'a> { - dvc_pdu: vc::dvc::ServerPdu, - dvc_data: &'a [u8], -} - -fn decode_dvc_message(user_data: &[u8]) -> PduResult> { - use ironrdp_pdu::{custom_err, PduParsing as _}; - - let mut user_data = user_data; - let user_data_len = user_data.len(); - - // ā€¦ | dvc::ServerPdu | ā€¦ - let dvc_pdu = - vc::dvc::ServerPdu::from_buffer(&mut user_data, user_data_len).map_err(|e| custom_err!("DVC server PDU", e))?; - - // ā€¦ | DvcData ] - let dvc_data = user_data; - - Ok(DynamicChannelCtx { dvc_pdu, dvc_data }) +fn decode_dvc_message(user_data: &[u8]) -> PduResult { + DrdynvcServerPdu::decode(&mut ReadCursor::new(user_data)) } diff --git a/crates/ironrdp-dvc/src/complete_data.rs b/crates/ironrdp-dvc/src/complete_data.rs index 26facb391..2c9c60a92 100644 --- a/crates/ironrdp-dvc/src/complete_data.rs +++ b/crates/ironrdp-dvc/src/complete_data.rs @@ -1,6 +1,8 @@ use alloc::vec::Vec; use core::cmp; -use ironrdp_pdu::dvc; +use ironrdp_pdu::{cast_length, dvc, invalid_message_err, PduResult}; + +use crate::pdu::{DataFirstPdu, DataPdu, DrdynvcDataPdu}; #[derive(Debug, PartialEq)] pub(crate) struct CompleteData { @@ -16,63 +18,60 @@ impl CompleteData { } } - pub(crate) fn process_data(&mut self, pdu: dvc::CommonPdu, data: Vec) -> Option> { + pub(crate) fn process_data(&mut self, pdu: DrdynvcDataPdu) -> PduResult>> { match pdu { - dvc::CommonPdu::DataFirst(df) => self.process_data_first_pdu(df.total_data_size as usize, data), - dvc::CommonPdu::Data(_) => self.process_data_pdu(data), + DrdynvcDataPdu::DataFirst(data_first) => self.process_data_first_pdu(data_first), + DrdynvcDataPdu::Data(data) => self.process_data_pdu(data), } } - fn process_data_first_pdu(&mut self, total_data_size: usize, data: Vec) -> Option> { + fn process_data_first_pdu(&mut self, data_first: DataFirstPdu) -> PduResult>> { + let total_data_size = cast_length!("DataFirstPdu::length", data_first.length)?; if self.total_size != 0 || !self.data.is_empty() { error!("Incomplete DVC message, it will be skipped"); self.data.clear(); } - if total_data_size == data.len() { - Some(data) + if total_data_size == data_first.data.len() { + Ok(Some(data_first.data)) } else { self.total_size = total_data_size; - self.data = data; + self.data = data_first.data; - None + Ok(None) } } - fn process_data_pdu(&mut self, mut data: Vec) -> Option> { + fn process_data_pdu(&mut self, mut data: DataPdu) -> PduResult>> { if self.total_size == 0 && self.data.is_empty() { // message is not fragmented - Some(data) + Ok(Some(data.data)) } else { // message is fragmented so need to reassemble it - match self.data.len().checked_add(data.len()) { + match self.data.len().checked_add(data.data.len()) { Some(actual_data_length) => { match actual_data_length.cmp(&(self.total_size)) { cmp::Ordering::Less => { // this is one of the fragmented messages, just append it - self.data.append(&mut data); - None + self.data.append(&mut data.data); + Ok(None) } cmp::Ordering::Equal => { // this is the last fragmented message, need to return the whole reassembled message self.total_size = 0; - self.data.append(&mut data); - Some(self.data.drain(..).collect()) + self.data.append(&mut data.data); + Ok(Some(self.data.drain(..).collect())) } cmp::Ordering::Greater => { error!("Actual DVC message size is grater than expected total DVC message size"); self.total_size = 0; self.data.clear(); - - None + Ok(None) } } } - _ => { - error!("DVC message size overflow occurred"); - None - } + _ => Err(invalid_message_err!("DVC message", "data", "overflow occurred")), } } } diff --git a/crates/ironrdp-dvc/src/display.rs b/crates/ironrdp-dvc/src/display.rs index d6cbd078d..146d8231d 100644 --- a/crates/ironrdp-dvc/src/display.rs +++ b/crates/ironrdp-dvc/src/display.rs @@ -12,11 +12,9 @@ use crate::DvcProcessor; use crate::PduResult; use crate::SvcMessage; use crate::Vec; - use bitflags::bitflags; use ironrdp_pdu::cast_length; use ironrdp_pdu::cursor::WriteCursor; -use ironrdp_pdu::dvc; use ironrdp_pdu::ensure_size; use ironrdp_pdu::other_err; use ironrdp_pdu::write_buf::WriteBuf; diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 91f3d5e37..9677e6533 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -11,6 +11,7 @@ extern crate alloc; use alloc::string::String; use core::any::TypeId; +use pdu::DrdynvcDataPdu; use alloc::boxed::Box; use alloc::collections::BTreeMap; @@ -20,8 +21,6 @@ use alloc::vec::Vec; // Re-export ironrdp_pdu crate for convenience #[rustfmt::skip] // do not re-order this pub use pub use ironrdp_pdu; -use ironrdp_pdu::dvc::gfx::ServerPdu; -use ironrdp_pdu::dvc::{self, DataFirstPdu, DataPdu}; use ironrdp_pdu::write_buf::WriteBuf; use ironrdp_pdu::{ assert_obj_safe, cast_length, custom_err, encode_vec, ensure_size, other_err, PduEncode, PduParsing as _, PduResult, @@ -62,11 +61,11 @@ pub trait DvcProcessor: AsAny + Send + Sync { /// Returns any messages that should be sent immediately /// upon the channel being created. - fn start(&mut self, _channel_id: u32) -> PduResult; + fn start(&mut self, channel_id: u32) -> PduResult; fn process(&mut self, channel_id: u32, payload: &[u8]) -> PduResult; - fn close(&mut self, _channel_id: u32) {} + fn close(&mut self, channel_id: u32) {} } assert_obj_safe!(DvcProcessor); @@ -95,13 +94,13 @@ pub(crate) fn encode_dvc_messages( .ok_or_else(|| other_err!("encode_dvc_messages", "overflow occurred"))?; let pdu = if needs_splitting && first { - pdu::DrdynvcPdu::DataFirst(pdu::DataFirstPdu::new( + pdu::DrdynvcDataPdu::DataFirst(pdu::DataFirstPdu::new( channel_id, - total_length as u8, + cast_length!("total_length", total_length)?, msg[off..end].to_vec(), )) } else { - pdu::DrdynvcPdu::Data(pdu::DataPdu::new(channel_id, msg[off..end].to_vec())) + pdu::DrdynvcDataPdu::Data(pdu::DataPdu::new(channel_id, msg[off..end].to_vec())) }; let mut svc = SvcMessage::from(pdu); @@ -133,9 +132,9 @@ impl DynamicVirtualChannel { self.channel_processor.start(channel_id) } - fn process(&mut self, pdu: dvc::CommonPdu, data: &[u8]) -> PduResult { + fn process(&mut self, pdu: DrdynvcDataPdu) -> PduResult { let channel_id = pdu.channel_id(); - let complete_data = self.complete_data.process_data(pdu, data.into()); + let complete_data = self.complete_data.process_data(pdu)?; if let Some(complete_data) = complete_data { self.channel_processor.process(channel_id, &complete_data) } else { diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index 5061de5be..3ce81a3cf 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -1,63 +1,178 @@ -use crate::{DynamicChannelId, Vec}; -use ironrdp_pdu::{cast_length, cursor::WriteCursor, ensure_size, PduEncode, PduResult}; +use crate::{DynamicChannelId, String, Vec}; +use alloc::format; +use ironrdp_pdu::{ + cast_length, + cursor::{ReadCursor, WriteCursor}, + ensure_fixed_part_size, ensure_size, invalid_message_err, unexpected_message_type_err, unsupported_pdu_err, + utils::{encoded_str_len, read_string_from_cursor, write_string_to_cursor, CharacterSet}, + PduDecode, PduEncode, PduError, PduResult, +}; use ironrdp_svc::SvcPduEncode; -// TODO: The rest of the PDU's currently in `ironrdp-pdu/src/rdp/vc/dvc.rs` should ultimately be moved here. +/// Dynamic Virtual Channel PDU's that are sent by both client and server. #[derive(Debug)] -pub enum DrdynvcPdu { - CapabilitiesResponse(CapabilitiesResponsePdu), - CreateResponse(CreateResponsePdu), +pub enum DrdynvcDataPdu { DataFirst(DataFirstPdu), Data(DataPdu), +} + +impl DrdynvcDataPdu { + pub fn channel_id(&self) -> DynamicChannelId { + match self { + DrdynvcDataPdu::DataFirst(pdu) => pdu.channel_id, + DrdynvcDataPdu::Data(pdu) => pdu.channel_id, + } + } +} + +impl PduEncode for DrdynvcDataPdu { + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + match self { + DrdynvcDataPdu::DataFirst(pdu) => pdu.encode(dst), + DrdynvcDataPdu::Data(pdu) => pdu.encode(dst), + } + } + + fn name(&self) -> &'static str { + match self { + DrdynvcDataPdu::DataFirst(pdu) => pdu.name(), + DrdynvcDataPdu::Data(pdu) => pdu.name(), + } + } + + fn size(&self) -> usize { + match self { + DrdynvcDataPdu::DataFirst(pdu) => pdu.size(), + DrdynvcDataPdu::Data(pdu) => pdu.size(), + } + } +} + +/// Dynamic Virtual Channel PDU's that are sent by the client. +#[derive(Debug)] +pub enum DrdynvcClientPdu { + Capabilities(CapabilitiesResponsePdu), + Create(CreateResponsePdu), Close(ClosePdu), + Data(DrdynvcDataPdu), } -impl PduEncode for DrdynvcPdu { +impl PduEncode for DrdynvcClientPdu { fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { match self { - DrdynvcPdu::DataFirst(pdu) => pdu.encode(dst), - DrdynvcPdu::Data(pdu) => pdu.encode(dst), - DrdynvcPdu::CreateResponse(pdu) => pdu.encode(dst), - DrdynvcPdu::Close(pdu) => pdu.encode(dst), - DrdynvcPdu::CapabilitiesResponse(pdu) => pdu.encode(dst), + DrdynvcClientPdu::Capabilities(pdu) => pdu.encode(dst), + DrdynvcClientPdu::Create(pdu) => pdu.encode(dst), + DrdynvcClientPdu::Data(pdu) => pdu.encode(dst), + DrdynvcClientPdu::Close(pdu) => pdu.encode(dst), } } fn name(&self) -> &'static str { match self { - DrdynvcPdu::DataFirst(pdu) => pdu.name(), - DrdynvcPdu::Data(pdu) => pdu.name(), - DrdynvcPdu::CreateResponse(pdu) => pdu.name(), - DrdynvcPdu::Close(pdu) => pdu.name(), - DrdynvcPdu::CapabilitiesResponse(pdu) => pdu.name(), + DrdynvcClientPdu::Capabilities(pdu) => pdu.name(), + DrdynvcClientPdu::Create(pdu) => pdu.name(), + DrdynvcClientPdu::Data(pdu) => pdu.name(), + DrdynvcClientPdu::Close(pdu) => pdu.name(), } } fn size(&self) -> usize { match self { - DrdynvcPdu::DataFirst(pdu) => pdu.size(), - DrdynvcPdu::Data(pdu) => pdu.size(), - DrdynvcPdu::CreateResponse(pdu) => pdu.size(), - DrdynvcPdu::Close(pdu) => pdu.size(), - DrdynvcPdu::CapabilitiesResponse(pdu) => pdu.size(), + DrdynvcClientPdu::Capabilities(pdu) => pdu.size(), + DrdynvcClientPdu::Create(pdu) => pdu.size(), + DrdynvcClientPdu::Data(pdu) => pdu.size(), + DrdynvcClientPdu::Close(pdu) => pdu.size(), } } } -/// Dynamic virtual channel PDU's are sent over a static virtual channel, so they are `SvcPduEncode`. -impl SvcPduEncode for DrdynvcPdu {} +impl PduDecode<'_> for DrdynvcClientPdu { + fn decode(src: &mut ReadCursor<'_>) -> PduResult { + let header = Header::decode(src)?; + match header.cmd { + Cmd::Create => Ok(Self::Create(CreateResponsePdu::decode(header, src)?)), + Cmd::DataFirst => Ok(Self::Data(DrdynvcDataPdu::DataFirst(DataFirstPdu::decode( + header, src, + )?))), + Cmd::Data => Ok(Self::Data(DrdynvcDataPdu::Data(DataPdu::decode(header, src)?))), + Cmd::Close => Ok(Self::Close(ClosePdu::decode(header, src)?)), + Cmd::Capability => Ok(Self::Capabilities(CapabilitiesResponsePdu::decode(header, src)?)), + _ => Err(unsupported_pdu_err!("Cmd", header.cmd.into())), + } + } +} + +/// Dynamic Virtual Channel PDU's that are sent by the server. +#[derive(Debug)] +pub enum DrdynvcServerPdu { + Capabilities(CapabilitiesRequestPdu), + Create(CreateRequestPdu), + Close(ClosePdu), + Data(DrdynvcDataPdu), +} + +impl PduEncode for DrdynvcServerPdu { + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + match self { + DrdynvcServerPdu::Data(pdu) => pdu.encode(dst), + DrdynvcServerPdu::Capabilities(pdu) => pdu.encode(dst), + DrdynvcServerPdu::Create(pdu) => pdu.encode(dst), + DrdynvcServerPdu::Close(pdu) => pdu.encode(dst), + } + } + + fn name(&self) -> &'static str { + match self { + DrdynvcServerPdu::Data(pdu) => pdu.name(), + DrdynvcServerPdu::Capabilities(pdu) => pdu.name(), + DrdynvcServerPdu::Create(pdu) => pdu.name(), + DrdynvcServerPdu::Close(pdu) => pdu.name(), + } + } + + fn size(&self) -> usize { + match self { + DrdynvcServerPdu::Data(pdu) => pdu.size(), + DrdynvcServerPdu::Capabilities(pdu) => pdu.size(), + DrdynvcServerPdu::Create(pdu) => pdu.size(), + DrdynvcServerPdu::Close(pdu) => pdu.size(), + } + } +} + +impl PduDecode<'_> for DrdynvcServerPdu { + fn decode(src: &mut ReadCursor<'_>) -> PduResult { + let header = Header::decode(src)?; + match header.cmd { + Cmd::Create => Ok(Self::Create(CreateRequestPdu::decode(header, src)?)), + Cmd::DataFirst => Ok(Self::Data(DrdynvcDataPdu::DataFirst(DataFirstPdu::decode( + header, src, + )?))), + Cmd::Data => Ok(Self::Data(DrdynvcDataPdu::Data(DataPdu::decode(header, src)?))), + Cmd::Close => Ok(Self::Close(ClosePdu::decode(header, src)?)), + Cmd::Capability => Ok(Self::Capabilities(CapabilitiesRequestPdu::decode(header, src)?)), + _ => Err(unsupported_pdu_err!("Cmd", header.cmd.into())), + } + } +} + +// Dynamic virtual channel PDU's are sent over a static virtual channel, so they are `SvcPduEncode`. +impl SvcPduEncode for DrdynvcDataPdu {} +impl SvcPduEncode for DrdynvcClientPdu {} +impl SvcPduEncode for DrdynvcServerPdu {} /// [2.2] Message Syntax /// /// [2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/0b07a750-bf51-4042-bcf2-a991b6729d6e #[derive(Debug)] -struct Header { +pub struct Header { cb_id: FieldType, // 2 bit sp: FieldType, // 2 bit; meaning depends on the cmd field cmd: Cmd, // 4 bit } impl Header { + pub const FIXED_PART_SIZE: usize = 1; /// Create a new `Header` with the given `cb_id_val`, `sp_val`, and `cmd`. /// /// If `cb_id_val` or `sp_val` is not relevant for a given `cmd`, it should be set to 0 respectively. @@ -71,12 +186,26 @@ impl Header { fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { ensure_size!(in: dst, size: Self::size()); - dst.write_u8((self.cmd as u8) << 4 | (self.sp as u8) << 2 | (self.cb_id as u8)); + dst.write_u8((self.cmd as u8) << 4 | Into::::into(self.sp) << 2 | Into::::into(self.cb_id)); Ok(()) } + fn decode(src: &mut ReadCursor<'_>) -> PduResult { + ensure_size!(in: src, size: Self::size()); + let byte = src.read_u8(); + debug!("Decoded byte: {:08b}", byte); + let cmd = Cmd::try_from(byte >> 4)?; + debug!("Decoded cmd: {:?}", cmd); + debug!("(byte >> 2): {:08b}", (byte >> 2)); + debug!("((byte >> 2) & 0b11): {:08b}", (byte >> 2) & 0b11); + let sp = FieldType::from((byte >> 2) & 0b11); + debug!("(byte & 0b11): {:08b}", byte & 0b11); + let cb_id = FieldType::from(byte & 0b11); + Ok(Self { cb_id, sp, cmd }) + } + fn size() -> usize { - 1 + Self::FIXED_PART_SIZE } } @@ -84,7 +213,7 @@ impl Header { /// /// [2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/0b07a750-bf51-4042-bcf2-a991b6729d6e #[repr(u8)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] enum Cmd { Create = 0x01, DataFirst = 0x02, @@ -97,18 +226,43 @@ enum Cmd { SoftSyncResponse = 0x09, } +impl TryFrom for Cmd { + type Error = PduError; + + fn try_from(byte: u8) -> Result { + match byte { + 0x01 => Ok(Self::Create), + 0x02 => Ok(Self::DataFirst), + 0x03 => Ok(Self::Data), + 0x04 => Ok(Self::Close), + 0x05 => Ok(Self::Capability), + 0x06 => Ok(Self::DataFirstCompressed), + 0x07 => Ok(Self::DataCompressed), + 0x08 => Ok(Self::SoftSyncRequest), + 0x09 => Ok(Self::SoftSyncResponse), + _ => Err(invalid_message_err!("Cmd", "invalid cmd")), + } + } +} + +impl From for String { + fn from(val: Cmd) -> Self { + format!("{:?}", val) + } +} + /// 2.2.3.1 DVC Data First PDU (DYNVC_DATA_FIRST) /// /// [2.2.3.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/69377767-56a6-4ab8-996b-7758676e9261 #[derive(Debug)] pub struct DataFirstPdu { header: Header, - channel_id: DynamicChannelId, + pub channel_id: DynamicChannelId, /// Length is the *total* length of the data to be sent, including the length /// of the data that will be sent by subsequent DVC_DATA PDUs. - length: u8, + pub length: u32, /// Data is just the data to be sent in this PDU. - data: Vec, + pub data: Vec, } impl DataFirstPdu { @@ -118,22 +272,35 @@ impl DataFirstPdu { /// of the data that will be sent by subsequent `DataPdu`s. /// /// `data` is just the data to be sent in this PDU. - pub fn new(channel_id: DynamicChannelId, total_length: u8, data: Vec) -> Self { + pub fn new(channel_id: DynamicChannelId, total_length: u32, data: Vec) -> Self { Self { - header: Header::new(channel_id, total_length.into(), Cmd::DataFirst), + header: Header::new(channel_id, total_length, Cmd::DataFirst), channel_id, length: total_length, data, } } + fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { + ensure_size!(in: src, size: header.cb_id.size_of_val() + header.sp.size_of_val()); + let channel_id = header.cb_id.decode_val(src)?; + let length = header.sp.decode_val(src)?; + let data = src.read_remaining().to_vec(); + Ok(Self { + header, + channel_id, + length, + data, + }) + } + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { ensure_size!(in: dst, size: self.size()); self.header.encode(dst)?; - self.header.cb_id.encode(self.channel_id, dst)?; + self.header.cb_id.encode_val(self.channel_id, dst)?; self.header .sp - .encode(cast_length!("DataFirstPdu::Length", self.length)?, dst)?; + .encode_val(cast_length!("DataFirstPdu::Length", self.length)?, dst)?; dst.write_slice(&self.data); Ok(()) } @@ -150,31 +317,42 @@ impl DataFirstPdu { } } -#[repr(u8)] -#[derive(Debug, Copy, Clone)] -pub enum FieldType { - U8 = 0x00, - U16 = 0x01, - U32 = 0x02, -} +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct FieldType(u8); impl FieldType { - fn encode(&self, value: u32, dst: &mut WriteCursor<'_>) -> PduResult<()> { + pub const U8: Self = Self(0x00); + pub const U16: Self = Self(0x01); + pub const U32: Self = Self(0x02); + + fn encode_val(&self, value: u32, dst: &mut WriteCursor<'_>) -> PduResult<()> { ensure_size!(in: dst, size: self.size_of_val()); - match self { + match *self { FieldType::U8 => dst.write_u8(cast_length!("FieldType::encode", value)?), FieldType::U16 => dst.write_u16(cast_length!("FieldType::encode", value)?), FieldType::U32 => dst.write_u32(value), + _ => return Err(invalid_message_err!("FieldType", "invalid field type")), }; Ok(()) } + fn decode_val(&self, src: &mut ReadCursor<'_>) -> PduResult { + ensure_size!(in: src, size: self.size_of_val()); + match *self { + FieldType::U8 => Ok(src.read_u8() as u32), + FieldType::U16 => Ok(src.read_u16() as u32), + FieldType::U32 => Ok(src.read_u32()), + _ => Err(invalid_message_err!("FieldType", "invalid field type")), + } + } + /// Returns the size of the value in bytes. fn size_of_val(&self) -> usize { - match self { + match *self { FieldType::U8 => 1, FieldType::U16 => 2, FieldType::U32 => 4, + _ => 0, } } @@ -189,14 +367,31 @@ impl FieldType { } } +impl From for FieldType { + fn from(byte: u8) -> Self { + match byte { + 0x00 => Self::U8, + 0x01 => Self::U16, + 0x02 => Self::U32, + _ => Self(byte), + } + } +} + +impl From for u8 { + fn from(field_type: FieldType) -> Self { + field_type.0 + } +} + /// 2.2.3.2 DVC Data PDU (DYNVC_DATA) /// /// [2.2.3.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/15b59886-db44-47f1-8da3-47c8fcd82803 #[derive(Debug)] pub struct DataPdu { header: Header, - channel_id: DynamicChannelId, - data: Vec, + pub channel_id: DynamicChannelId, + pub data: Vec, } impl DataPdu { @@ -208,10 +403,21 @@ impl DataPdu { } } + fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { + ensure_size!(in: src, size: header.cb_id.size_of_val()); + let channel_id = header.cb_id.decode_val(src)?; + let data = src.read_remaining().to_vec(); + Ok(Self { + header, + channel_id, + data, + }) + } + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { ensure_size!(in: dst, size: self.size()); self.header.encode(dst)?; - self.header.cb_id.encode(self.channel_id, dst)?; + self.header.cb_id.encode_val(self.channel_id, dst)?; dst.write_slice(&self.data); Ok(()) } @@ -233,8 +439,8 @@ impl DataPdu { #[derive(Debug)] pub struct CreateResponsePdu { header: Header, - channel_id: DynamicChannelId, - creation_status: CreationStatus, + pub channel_id: DynamicChannelId, + pub creation_status: CreationStatus, } impl CreateResponsePdu { @@ -250,10 +456,21 @@ impl CreateResponsePdu { "DYNVC_CREATE_RSP" } + fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { + ensure_size!(in: src, size: header.cb_id.size_of_val() + CreationStatus::size()); + let channel_id = header.cb_id.decode_val(src)?; + let creation_status = CreationStatus(src.read_u32()); + Ok(Self { + header, + channel_id, + creation_status, + }) + } + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { ensure_size!(in: dst, size: self.size()); self.header.encode(dst)?; - self.header.cb_id.encode(self.channel_id, dst)?; + self.header.cb_id.encode_val(self.channel_id, dst)?; self.creation_status.encode(dst)?; Ok(()) } @@ -265,7 +482,7 @@ impl CreateResponsePdu { } } -#[derive(Debug)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct CreationStatus(u32); impl CreationStatus { @@ -283,13 +500,19 @@ impl CreationStatus { } } +impl From for u32 { + fn from(val: CreationStatus) -> Self { + val.0 + } +} + /// 2.2.4 Closing a DVC (DYNVC_CLOSE) /// /// [2.2.4]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/c02dfd21-ccbc-4254-985b-3ef6dd115dec #[derive(Debug)] pub struct ClosePdu { header: Header, - channel_id: DynamicChannelId, + pub channel_id: DynamicChannelId, } impl ClosePdu { @@ -300,10 +523,15 @@ impl ClosePdu { } } + fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { + let channel_id = header.cb_id.decode_val(src)?; + Ok(Self { header, channel_id }) + } + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { ensure_size!(in: dst, size: self.size()); self.header.encode(dst)?; - self.header.cb_id.encode(self.channel_id, dst)?; + self.header.cb_id.encode_val(self.channel_id, dst)?; Ok(()) } @@ -333,6 +561,13 @@ impl CapabilitiesResponsePdu { } } + fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { + ensure_size!(in: src, size: 1 /* Pad */ + CapsVersion::size()); + let _pad = src.read_u8(); + let version = CapsVersion::try_from(src.read_u16())?; + Ok(Self { header, version }) + } + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { ensure_size!(in: dst, size: self.size()); self.header.encode(dst)?; @@ -369,3 +604,173 @@ impl CapsVersion { 2 } } + +impl TryFrom for CapsVersion { + type Error = PduError; + + fn try_from(value: u16) -> Result { + match value { + 0x0001 => Ok(Self::V1), + 0x0002 => Ok(Self::V2), + 0x0003 => Ok(Self::V3), + _ => Err(invalid_message_err!("CapsVersion", "invalid version")), + } + } +} + +impl From for u16 { + fn from(version: CapsVersion) -> Self { + version as u16 + } +} + +/// 2.2.1.1 DVC Capabilities Request PDU +/// +/// [2.2.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/c07b15ae-304e-46b8-befe-39c6d95c25e0 +#[derive(Debug)] +pub enum CapabilitiesRequestPdu { + V1 { + header: Header, + }, + V2 { + header: Header, + charges: [u16; CapabilitiesRequestPdu::PRIORITY_CHARGE_COUNT], + }, + V3 { + header: Header, + charges: [u16; CapabilitiesRequestPdu::PRIORITY_CHARGE_COUNT], + }, +} + +impl CapabilitiesRequestPdu { + const HEADERLESS_FIXED_PART_SIZE: usize = 1 /* Pad */ + 2 /* Version */; + const FIXED_PART_SIZE: usize = Header::FIXED_PART_SIZE + Self::HEADERLESS_FIXED_PART_SIZE; + const PRIORITY_CHARGE_SIZE: usize = 2; // 2 bytes for each priority charge + const PRIORITY_CHARGE_COUNT: usize = 4; // 4 priority charges + const PRIORITY_CHARGES_SIZE: usize = Self::PRIORITY_CHARGE_COUNT * Self::PRIORITY_CHARGE_SIZE; + + pub fn new(version: CapsVersion) -> Self { + let header = Header::new(0, 0, Cmd::Capability); + match version { + CapsVersion::V1 => Self::V1 { header }, + CapsVersion::V2 => Self::V2 { + header, + charges: [0; Self::PRIORITY_CHARGE_COUNT], + }, + CapsVersion::V3 => Self::V3 { + header, + charges: [0; Self::PRIORITY_CHARGE_COUNT], + }, + } + } + + fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { + ensure_size!(in: src, size: Self::HEADERLESS_FIXED_PART_SIZE); + let _pad = src.read_u8(); + let version = CapsVersion::try_from(src.read_u16())?; + match version { + CapsVersion::V1 => Ok(Self::V1 { header }), + _ => { + ensure_size!(in: src, size: Self::PRIORITY_CHARGES_SIZE); + let mut charges = [0u16; Self::PRIORITY_CHARGE_COUNT]; + for charge in charges.iter_mut() { + *charge = src.read_u16(); + } + + match version { + CapsVersion::V2 => Ok(Self::V2 { header, charges }), + CapsVersion::V3 => Ok(Self::V3 { header, charges }), + _ => unreachable!(), + } + } + } + } + + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: self.size()); + match self { + CapabilitiesRequestPdu::V1 { header } + | CapabilitiesRequestPdu::V2 { header, .. } + | CapabilitiesRequestPdu::V3 { header, .. } => header.encode(dst), + }; + dst.write_u8(0x00); // Pad, MUST be 0x00 + match self { + CapabilitiesRequestPdu::V1 { .. } => dst.write_u16(CapsVersion::V1.into()), + CapabilitiesRequestPdu::V2 { .. } => dst.write_u16(CapsVersion::V2.into()), + CapabilitiesRequestPdu::V3 { .. } => dst.write_u16(CapsVersion::V3.into()), + } + match self { + CapabilitiesRequestPdu::V1 { .. } => {} + CapabilitiesRequestPdu::V2 { charges, .. } | CapabilitiesRequestPdu::V3 { charges, .. } => { + for charge in charges.iter() { + dst.write_u16(*charge); + } + } + } + Ok(()) + } + + fn size(&self) -> usize { + match self { + Self::V1 { header } => Self::FIXED_PART_SIZE, + _ => Self::FIXED_PART_SIZE + Self::PRIORITY_CHARGES_SIZE, + } + } + + fn name(&self) -> &'static str { + match self { + Self::V1 { .. } => "DYNVC_CAPS_VERSION1", + Self::V2 { .. } => "DYNVC_CAPS_VERSION2", + Self::V3 { .. } => "DYNVC_CAPS_VERSION3", + } + } +} + +/// 2.2.2.1 DVC Create Request PDU (DYNVC_CREATE_REQ) +/// +/// [2.2.2.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/4448ba4d-9a72-429f-8b65-6f4ec44f2985 +#[derive(Debug)] +pub struct CreateRequestPdu { + header: Header, + pub channel_id: DynamicChannelId, + pub channel_name: String, +} + +impl CreateRequestPdu { + pub fn new(channel_id: DynamicChannelId, channel_name: String) -> Self { + Self { + header: Header::new(channel_id, 0, Cmd::Create), + channel_id, + channel_name, + } + } + + fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { + ensure_size!(in: src, size: header.cb_id.size_of_val()); + let channel_id = header.cb_id.decode_val(src)?; + let channel_name = read_string_from_cursor(src, CharacterSet::Ansi, true)?; + Ok(Self { + header, + channel_id, + channel_name, + }) + } + + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: self.size()); + self.header.encode(dst)?; + self.header.cb_id.encode_val(self.channel_id, dst)?; + write_string_to_cursor(dst, &self.channel_name, CharacterSet::Ansi, true); + Ok(()) + } + + fn name(&self) -> &'static str { + "DYNVC_CREATE_REQ" + } + + fn size(&self) -> usize { + Header::size() + + self.header.cb_id.size_of_val() + // ChannelId + encoded_str_len(&self.channel_name, CharacterSet::Ansi, true) // ChannelName + Null terminator + } +} diff --git a/crates/ironrdp-dvc/src/server.rs b/crates/ironrdp-dvc/src/server.rs index 52317123f..a982807b0 100644 --- a/crates/ironrdp-dvc/src/server.rs +++ b/crates/ironrdp-dvc/src/server.rs @@ -1,3 +1,8 @@ +use crate::pdu::{ + CapabilitiesRequestPdu, CapsVersion, CreateRequestPdu, CreationStatus, DrdynvcClientPdu, DrdynvcDataPdu, + DrdynvcServerPdu, +}; +use crate::{encode_dvc_messages, CompleteData, DvcMessages, DvcProcessor}; use alloc::borrow::ToOwned; use alloc::boxed::Box; use alloc::collections::BTreeMap; @@ -5,20 +10,16 @@ use alloc::string::String; use alloc::vec::Vec; use core::any::Any; use core::fmt; -use pdu::dvc::{CreateRequestPdu, DataFirstPdu, DataPdu}; -use slab::Slab; - use ironrdp_pdu as pdu; - use ironrdp_svc::{impl_as_any, ChannelFlags, CompressionCondition, SvcMessage, SvcProcessor, SvcServerProcessor}; -use pdu::cursor::WriteCursor; +use pdu::cursor::{ReadCursor, WriteCursor}; use pdu::gcc::ChannelName; use pdu::rdp::vc; use pdu::write_buf::WriteBuf; +use pdu::PduDecode as _; +use pdu::PduResult; use pdu::{cast_length, custom_err, encode_vec, invalid_message_err, other_err, PduEncode, PduParsing}; -use pdu::{dvc, PduResult}; - -use crate::{encode_dvc_messages, CompleteData, DvcMessages, DvcProcessor}; +use slab::Slab; pub trait DvcServerProcessor: DvcProcessor {} @@ -116,40 +117,40 @@ impl SvcProcessor for DrdynvcServer { } fn start(&mut self) -> PduResult> { - let cap = dvc::CapabilitiesRequestPdu::V1; - let req = dvc::ServerPdu::CapabilitiesRequest(cap); - let msg = encode_dvc_message(req)?; + let cap = CapabilitiesRequestPdu::new(CapsVersion::V1); + let req = DrdynvcServerPdu::Capabilities(cap); + let msg = as_svc_msg_with_flag(req)?; Ok(alloc::vec![msg]) } fn process(&mut self, payload: &[u8]) -> PduResult> { - let dvc_ctx = decode_dvc_message(payload)?; + let pdu = decode_dvc_message(payload)?; let mut resp = Vec::new(); - match dvc_ctx.dvc_pdu { - dvc::ClientPdu::CapabilitiesResponse(caps_resp) => { + match pdu { + DrdynvcClientPdu::Capabilities(caps_resp) => { debug!("Got DVC Capabilities Response PDU: {caps_resp:?}"); for (id, c) in self.dynamic_channels.iter_mut() { if c.state != ChannelState::Closed { continue; } - let req = dvc::ServerPdu::CreateRequest(CreateRequestPdu::new( + let req = DrdynvcServerPdu::Create(CreateRequestPdu::new( id.try_into().map_err(|e| custom_err!("invalid channel id", e))?, c.processor.channel_name().into(), )); c.state = ChannelState::Creation; - resp.push(encode_dvc_message(req)?); + resp.push(as_svc_msg_with_flag(req)?); } } - dvc::ClientPdu::CreateResponse(create_resp) => { + DrdynvcClientPdu::Create(create_resp) => { debug!("Got DVC Create Response PDU: {create_resp:?}"); let id = create_resp.channel_id; let c = self.channel_by_id(id)?; if c.state != ChannelState::Creation { return Err(invalid_message_err!("DRDYNVC", "", "invalid channel state")); } - if create_resp.creation_status != dvc::DVC_CREATION_STATUS_OK { - c.state = ChannelState::CreationFailed(create_resp.creation_status); + if create_resp.creation_status != CreationStatus::OK { + c.state = ChannelState::CreationFailed(create_resp.creation_status.into()); return Ok(resp); } c.state = ChannelState::Opened; @@ -160,7 +161,7 @@ impl SvcProcessor for DrdynvcServer { Some(ironrdp_svc::ChannelFlags::SHOW_PROTOCOL), )?); } - dvc::ClientPdu::CloseResponse(close_resp) => { + DrdynvcClientPdu::Close(close_resp) => { debug!("Got DVC Close Response PDU: {close_resp:?}"); let c = self.channel_by_id(close_resp.channel_id)?; if c.state != ChannelState::Opened { @@ -168,28 +169,13 @@ impl SvcProcessor for DrdynvcServer { } c.state = ChannelState::Closed; } - dvc::ClientPdu::Common(dvc::CommonPdu::DataFirst(data)) => { - let channel_id = data.channel_id; - let c = self.channel_by_id(channel_id)?; - if c.state != ChannelState::Opened { - return Err(invalid_message_err!("DRDYNVC", "", "invalid channel state")); - } - if let Some(complete) = c.complete_data.process_data(data.into(), dvc_ctx.dvc_data.into()) { - let msg = c.processor.process(channel_id, &complete)?; - resp.extend(encode_dvc_messages( - channel_id, - msg, - Some(ironrdp_svc::ChannelFlags::SHOW_PROTOCOL), - )?); - } - } - dvc::ClientPdu::Common(dvc::CommonPdu::Data(data)) => { - let channel_id = data.channel_id; + DrdynvcClientPdu::Data(data) => { + let channel_id = data.channel_id(); let c = self.channel_by_id(channel_id)?; if c.state != ChannelState::Opened { return Err(invalid_message_err!("DRDYNVC", "", "invalid channel state")); } - if let Some(complete) = c.complete_data.process_data(data.into(), dvc_ctx.dvc_data.into()) { + if let Some(complete) = c.complete_data.process_data(data)? { let msg = c.processor.process(channel_id, &complete)?; resp.extend(encode_dvc_messages( channel_id, @@ -206,28 +192,10 @@ impl SvcProcessor for DrdynvcServer { impl SvcServerProcessor for DrdynvcServer {} -struct DynamicChannelCtx<'a> { - dvc_pdu: vc::dvc::ClientPdu, - dvc_data: &'a [u8], -} - -fn decode_dvc_message(user_data: &[u8]) -> PduResult> { - let mut user_data = user_data; - let user_data_len = user_data.len(); - - // ā€¦ | dvc::ClientPdu | ā€¦ - let dvc_pdu = - vc::dvc::ClientPdu::from_buffer(&mut user_data, user_data_len).map_err(|e| custom_err!("DVC client PDU", e))?; - - // ā€¦ | DvcData ] - let dvc_data = user_data; - - Ok(DynamicChannelCtx { dvc_pdu, dvc_data }) +fn decode_dvc_message(user_data: &[u8]) -> PduResult { + DrdynvcClientPdu::decode(&mut ReadCursor::new(user_data)) } -fn encode_dvc_message(pdu: vc::dvc::ServerPdu) -> PduResult { - // FIXME: use PduEncode instead - let mut buf = Vec::new(); - pdu.to_buffer(&mut buf).map_err(|e| custom_err!("DVC server pdu", e))?; - Ok(SvcMessage::from(buf).with_flags(ChannelFlags::SHOW_PROTOCOL)) +fn as_svc_msg_with_flag(pdu: DrdynvcServerPdu) -> PduResult { + Ok(SvcMessage::from(pdu).with_flags(ChannelFlags::SHOW_PROTOCOL)) } diff --git a/crates/ironrdp-pdu/src/cursor.rs b/crates/ironrdp-pdu/src/cursor.rs index df5c6bca4..8aa192f4a 100644 --- a/crates/ironrdp-pdu/src/cursor.rs +++ b/crates/ironrdp-pdu/src/cursor.rs @@ -61,6 +61,10 @@ impl<'a> ReadCursor<'a> { bytes } + pub fn read_remaining(&mut self) -> &[u8] { + self.read_slice(self.len()) + } + #[inline] #[track_caller] pub fn read_u8(&mut self) -> u8 { From c72391acd5e93eb1882edb704f2e9b0190e93919 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 19 Mar 2024 17:49:36 -0700 Subject: [PATCH 17/84] Moves relevant DataFirst tests from ironrdp-pdu to ironrdp-dvc --- Cargo.lock | 1 + crates/ironrdp-dvc/Cargo.toml | 3 + crates/ironrdp-dvc/src/pdu.rs | 12 +- .../tests.rs => ironrdp-dvc/src/pdu/test.rs} | 196 +++++++----------- crates/ironrdp-pdu/src/lib.rs | 4 - crates/ironrdp-pdu/src/rdp/vc/dvc.rs | 148 ++++--------- .../ironrdp-pdu/src/rdp/vc/dvc/data_first.rs | 3 - 7 files changed, 122 insertions(+), 245 deletions(-) rename crates/{ironrdp-pdu/src/rdp/vc/dvc/data_first/tests.rs => ironrdp-dvc/src/pdu/test.rs} (66%) diff --git a/Cargo.lock b/Cargo.lock index 8d565eed3..97007ebad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1780,6 +1780,7 @@ dependencies = [ "bitflags 2.4.2", "ironrdp-pdu", "ironrdp-svc", + "lazy_static", "slab", "tracing", ] diff --git a/crates/ironrdp-dvc/Cargo.toml b/crates/ironrdp-dvc/Cargo.toml index d3ddb95f3..61cea9887 100644 --- a/crates/ironrdp-dvc/Cargo.toml +++ b/crates/ironrdp-dvc/Cargo.toml @@ -25,3 +25,6 @@ ironrdp-svc.workspace = true ironrdp-pdu = { workspace = true, features = ["alloc"] } tracing.workspace = true slab = "0.4.9" + +[dev-dependencies] +lazy_static = "1.4" diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index 3ce81a3cf..3e0390c50 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod test; + use crate::{DynamicChannelId, String, Vec}; use alloc::format; use ironrdp_pdu::{ @@ -164,7 +167,7 @@ impl SvcPduEncode for DrdynvcServerPdu {} /// [2.2] Message Syntax /// /// [2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/0b07a750-bf51-4042-bcf2-a991b6729d6e -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct Header { cb_id: FieldType, // 2 bit sp: FieldType, // 2 bit; meaning depends on the cmd field @@ -193,13 +196,8 @@ impl Header { fn decode(src: &mut ReadCursor<'_>) -> PduResult { ensure_size!(in: src, size: Self::size()); let byte = src.read_u8(); - debug!("Decoded byte: {:08b}", byte); let cmd = Cmd::try_from(byte >> 4)?; - debug!("Decoded cmd: {:?}", cmd); - debug!("(byte >> 2): {:08b}", (byte >> 2)); - debug!("((byte >> 2) & 0b11): {:08b}", (byte >> 2) & 0b11); let sp = FieldType::from((byte >> 2) & 0b11); - debug!("(byte & 0b11): {:08b}", byte & 0b11); let cb_id = FieldType::from(byte & 0b11); Ok(Self { cb_id, sp, cmd }) } @@ -254,7 +252,7 @@ impl From for String { /// 2.2.3.1 DVC Data First PDU (DYNVC_DATA_FIRST) /// /// [2.2.3.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/69377767-56a6-4ab8-996b-7758676e9261 -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct DataFirstPdu { header: Header, pub channel_id: DynamicChannelId, diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first/tests.rs b/crates/ironrdp-dvc/src/pdu/test.rs similarity index 66% rename from crates/ironrdp-pdu/src/rdp/vc/dvc/data_first/tests.rs rename to crates/ironrdp-dvc/src/pdu/test.rs index ce32b699b..f36713ee7 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first/tests.rs +++ b/crates/ironrdp-dvc/src/pdu/test.rs @@ -1,24 +1,19 @@ +use super::{Cmd, DataFirstPdu, DataPdu, DrdynvcClientPdu, DrdynvcDataPdu, DrdynvcServerPdu, FieldType, Header}; +use crate::{vec, Vec}; +use ironrdp_pdu::cursor::{ReadCursor, WriteCursor}; +use ironrdp_pdu::PduDecode; use lazy_static::lazy_static; -use super::*; -use crate::{cursor::WriteCursor, dvc::CommonPdu, rdp::vc::dvc::ClientPdu}; +const DATA_FIRST_DATA_LENGTH: u32 = 0xC7B; +const DATA_FIRST_CHANNEL_ID: u32 = 0x03; +const DATA_FIRST_PREFIX: [u8; 4] = [0x24, 0x03, 0x7b, 0x0c]; +const DATA_FIRST_DATA: [u8; 12] = [0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71]; -const DVC_TEST_CHANNEL_ID_U8: u32 = 0x03; -const DVC_TEST_DATA_LENGTH: u32 = 0x0000_0C7B; - -const DVC_FULL_DATA_FIRST_BUFFER_SIZE: usize = 16; -const DVC_DATA_FIRST_PREFIX: [u8; 4] = [0x24, 0x03, 0x7b, 0x0c]; -const DVC_DATA_FIRST_BUFFER: [u8; 12] = [0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71]; - -const DVC_DATA_FIRST_WITH_INVALID_TOTAL_MESSAGE_SIZE_BUFFER_SIZE: usize = 0x06; -const DVC_DATA_FIRST_WITH_INVALID_TOTAL_MESSAGE_SIZE_BUFFER: [u8; - DVC_DATA_FIRST_WITH_INVALID_TOTAL_MESSAGE_SIZE_BUFFER_SIZE] = [0x03, 0x03, 0x71, 0x71, 0x71, 0x71]; - -const DVC_INVALID_DATA_MESSAGE_BUFFER: [u8; PDU_WITH_DATA_MAX_SIZE] = [0x77; PDU_WITH_DATA_MAX_SIZE]; - -const DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_PREFIX: [u8; 4] = [0x24, 0x7, 0x39, 0x6]; - -const DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_BUFFER: [u8; 1593] = [ +// Edge case is when the total length is equal to data length +const DATA_FIRST_EDGE_CASE_DATA_LENGTH: u32 = 0x639; +const DATA_FIRST_EDGE_CASE_CHANNEL_ID: u32 = 0x07; +const DATA_FIRST_EDGE_CASE_PREFIX: [u8; 4] = [0x24, 0x7, 0x39, 0x6]; +const DATA_FIRST_EDGE_CASE_DATA: [u8; DATA_FIRST_EDGE_CASE_DATA_LENGTH as usize] = [ 0xe0, 0x24, 0xa9, 0xba, 0xe0, 0x68, 0xa9, 0xba, 0x8a, 0x73, 0x41, 0x25, 0x12, 0x12, 0x1c, 0x28, 0x3b, 0xa6, 0x34, 0x8, 0x8, 0x7a, 0x38, 0x34, 0x2c, 0xe8, 0xf8, 0xd0, 0xef, 0x18, 0xc2, 0xc, 0x27, 0x1f, 0xb1, 0x83, 0x3c, 0x58, 0x8a, 0x67, 0x1, 0x58, 0x9d, 0x50, 0x8b, 0x8c, 0x60, 0x31, 0x53, 0x55, 0x54, 0xd8, 0x51, 0x32, 0x23, 0x54, 0xd9, @@ -105,130 +100,79 @@ const DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_BUFFER: [u8; 1593] = 0x74, 0x36, 0x76, 0xa6, 0x53, 0x9f, 0x33, 0x56, 0x98, 0x88, 0x92, 0x2a, 0xd1, 0x90, 0x1, ]; -const DVC_TEST_HEADER_SIZE: usize = 0x01; - lazy_static! { - static ref DVC_FULL_DATA_FIRST_BUFFER: Vec = { - let mut result = DVC_DATA_FIRST_PREFIX.to_vec(); - result.extend(DVC_DATA_FIRST_BUFFER); - + static ref DATA_FIRST_ENCODED: Vec = { + let mut result = DATA_FIRST_PREFIX.to_vec(); + result.extend(DATA_FIRST_DATA); result }; - static ref FULL_DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_BUFFER: Vec = { - let mut result = DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_PREFIX.to_vec(); - result.append(&mut DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_BUFFER.to_vec()); - - result + static ref DATA_FIRST_DECODED: DataFirstPdu = { + let mut res = DataFirstPdu::new(DATA_FIRST_CHANNEL_ID, DATA_FIRST_DATA_LENGTH, DATA_FIRST_DATA.to_vec()); + res.header.cb_id = FieldType::U8; + res.header.sp = FieldType::U16; + res }; - static ref DVC_DATA_FIRST: DataFirstPdu = DataFirstPdu { - channel_id_type: FieldType::U8, - channel_id: DVC_TEST_CHANNEL_ID_U8, - total_data_size_type: FieldType::U16, - total_data_size: DVC_TEST_DATA_LENGTH, - data_size: DVC_DATA_FIRST_BUFFER.len() + static ref DATA_FIRST_EDGE_CASE_ENCODED: Vec = { + let mut result = DATA_FIRST_EDGE_CASE_PREFIX.to_vec(); + result.append(&mut DATA_FIRST_EDGE_CASE_DATA.to_vec()); + result }; - static ref DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH: ClientPdu = - ClientPdu::Common(CommonPdu::DataFirst(DataFirstPdu { - channel_id_type: FieldType::U8, - channel_id: 0x7, - total_data_size_type: FieldType::U16, - total_data_size: 0x639, - data_size: DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_BUFFER.len(), - })); -} - -#[test] -fn from_buffer_parsing_for_dvc_data_first_pdu_with_invalid_message_size_fails() { - match DataFirstPdu::from_buffer( - DVC_INVALID_DATA_MESSAGE_BUFFER.as_ref(), - FieldType::U8, - FieldType::U16, - PDU_WITH_DATA_MAX_SIZE, - ) { - Err(ChannelError::InvalidDvcMessageSize) => (), - res => panic!("Expected InvalidDvcMessageSize error, got: {res:?}"), + static ref DATA_FIRST_EDGE_CASE_DECODED: DataFirstPdu = { + let mut res = DataFirstPdu::new( + DATA_FIRST_EDGE_CASE_CHANNEL_ID, + DATA_FIRST_EDGE_CASE_DATA_LENGTH, + DATA_FIRST_EDGE_CASE_DATA.to_vec(), + ); + res.header.cb_id = FieldType::U8; + res.header.sp = FieldType::U16; + res }; } #[test] -fn from_buffer_parsing_for_dvc_data_first_pdu_with_invalid_total_message_size_fails() { - match DataFirstPdu::from_buffer( - DVC_DATA_FIRST_WITH_INVALID_TOTAL_MESSAGE_SIZE_BUFFER.as_ref(), - FieldType::U8, - FieldType::U8, - DVC_DATA_FIRST_WITH_INVALID_TOTAL_MESSAGE_SIZE_BUFFER_SIZE, - ) { - Err(ChannelError::InvalidDvcTotalMessageSize { .. }) => (), - res => panic!("Expected InvalidDvcTotalMessageSize error, got: {res:?}"), - }; +fn decodes_data_first_pdu() { + let mut src = ReadCursor::new(&DATA_FIRST_ENCODED); + match DrdynvcClientPdu::decode(&mut src).unwrap() { + DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*DATA_FIRST_DECODED, df), + _ => panic!("Expected DataFirst"), + } + + let mut src = ReadCursor::new(&DATA_FIRST_ENCODED); + match DrdynvcServerPdu::decode(&mut src).unwrap() { + DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*DATA_FIRST_DECODED, df), + _ => panic!("Expected DataFirst"), + } } #[test] -fn from_buffer_correct_parses_dvc_data_first_pdu() { - assert_eq!( - *DVC_DATA_FIRST, - DataFirstPdu::from_buffer( - &DVC_FULL_DATA_FIRST_BUFFER[1..], - FieldType::U8, - FieldType::U16, - DVC_FULL_DATA_FIRST_BUFFER_SIZE - DVC_TEST_HEADER_SIZE - ) - .unwrap(), - ); +fn encodes_data_first_pdu() { + let data_first = &*DATA_FIRST_DECODED; + let mut buffer = vec![0x00; data_first.size()]; + let mut cursor = WriteCursor::new(&mut buffer); + data_first.encode(&mut cursor).unwrap(); + assert_eq!(DATA_FIRST_ENCODED.as_slice(), buffer.as_slice()); } #[test] -fn to_buffer_correct_serializes_dvc_data_first_pdu() { - let data_first = &*DVC_DATA_FIRST; - - let mut buffer = Vec::new(); - data_first.to_buffer(&mut buffer).unwrap(); - - assert_eq!(DVC_DATA_FIRST_PREFIX.as_ref(), buffer.as_slice()); +fn decodes_data_first_edge_case() { + let mut src = ReadCursor::new(&DATA_FIRST_EDGE_CASE_ENCODED); + match DrdynvcClientPdu::decode(&mut src).unwrap() { + DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*DATA_FIRST_EDGE_CASE_DECODED, df), + _ => panic!("Expected DataFirst"), + } + + let mut src = ReadCursor::new(&DATA_FIRST_EDGE_CASE_ENCODED); + match DrdynvcServerPdu::decode(&mut src).unwrap() { + DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*DATA_FIRST_EDGE_CASE_DECODED, df), + _ => panic!("Expected DataFirst"), + } } #[test] -fn buffer_length_is_correct_for_dvc_data_first_pdu() { - let data_first = DVC_DATA_FIRST.clone(); - let expected_buf_len = DVC_DATA_FIRST_PREFIX.len(); - - let len = data_first.buffer_length(); - - assert_eq!(expected_buf_len, len); -} - -#[test] -fn from_buffer_correct_parses_dvc_server_pdu_with_data_first_where_total_length_equals_to_buffer_length() { - assert_eq!( - *DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH, - ClientPdu::from_buffer( - FULL_DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_BUFFER.as_slice(), - FULL_DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_BUFFER.len(), - ) - .unwrap(), - ); -} - -#[test] -fn to_buffer_correct_serializes_dvc_server_pdu_with_data_first_where_total_length_equals_to_buffer_length() { - let data_first = &*DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH; - - let mut b = vec![0x00; data_first.buffer_length()]; - let mut buffer = WriteCursor::new(&mut b); - data_first.to_buffer(&mut buffer).unwrap(); - - assert_eq!( - DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_PREFIX.as_ref(), - buffer.inner() - ); -} - -#[test] -fn buffer_length_is_correct_for_dvc_server_pdu_with_data_first_where_total_length_equals_to_buffer_length() { - let data_first = &*DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH; - let expected_buf_len = DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_PREFIX.len(); - - let len = data_first.buffer_length(); - - assert_eq!(expected_buf_len, len); +fn encodes_data_first_edge_case() { + let data_first = &*DATA_FIRST_EDGE_CASE_DECODED; + let mut buffer = vec![0x00; data_first.size()]; + let mut cursor = WriteCursor::new(&mut buffer); + data_first.encode(&mut cursor).unwrap(); + assert_eq!(DATA_FIRST_EDGE_CASE_ENCODED.as_slice(), buffer.as_slice()); } diff --git a/crates/ironrdp-pdu/src/lib.rs b/crates/ironrdp-pdu/src/lib.rs index 64f9012bc..fb977ed4d 100644 --- a/crates/ironrdp-pdu/src/lib.rs +++ b/crates/ironrdp-pdu/src/lib.rs @@ -88,10 +88,6 @@ impl fmt::Display for PduErrorKind { } } -impl ironrdp_error::legacy::CatchAllKind for PduErrorKind { - const CATCH_ALL_VALUE: Self = Self::Custom; -} - pub trait PduErrorExt { fn not_enough_bytes(context: &'static str, received: usize, expected: usize) -> Self; fn invalid_message(context: &'static str, field: &'static str, reason: &'static str) -> Self; diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs index de25e620f..a387490fb 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs @@ -6,7 +6,6 @@ use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use super::ChannelError; -use crate::cursor::WriteCursor; use crate::PduParsing; #[cfg(test)] @@ -42,89 +41,13 @@ pub enum PduType { Capabilities = 0x05, } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum CommonPdu { - DataFirst(DataFirstPdu), - Data(DataPdu), -} - -impl CommonPdu { - pub fn from_buffer( - pdu_type: PduType, - mut stream: impl io::Read, - dvc_data_size: usize, - channel_id_type: FieldType, - ) -> Result { - match pdu_type { - PduType::DataFirst => { - let data_length_type = - FieldType::from_u8(channel_id_type as u8).ok_or(ChannelError::InvalidDvcDataLength)?; - - Ok(CommonPdu::DataFirst(DataFirstPdu::from_buffer( - &mut stream, - channel_id_type, - data_length_type, - dvc_data_size, - )?)) - } - PduType::Data => Ok(CommonPdu::Data(DataPdu::from_buffer( - &mut stream, - channel_id_type, - dvc_data_size, - )?)), - _ => Err(ChannelError::InvalidDvcPduType), - } - } - - pub fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), ChannelError> { - match self { - CommonPdu::DataFirst(data_first) => data_first.to_buffer(&mut stream)?, - CommonPdu::Data(data) => data.to_buffer(&mut stream)?, - }; - - Ok(()) - } - - pub fn buffer_length(&self) -> usize { - match self { - CommonPdu::DataFirst(data_first) => data_first.buffer_length(), - CommonPdu::Data(data) => data.buffer_length(), - } - } - - pub fn as_short_name(&self) -> &'static str { - match self { - CommonPdu::DataFirst(_) => "Data First PDU", - CommonPdu::Data(_) => "Data PDU", - } - } - - pub fn channel_id(&self) -> u32 { - match self { - CommonPdu::DataFirst(data_first) => data_first.channel_id, - CommonPdu::Data(data) => data.channel_id, - } - } -} - -impl From for CommonPdu { - fn from(data_first: DataFirstPdu) -> Self { - CommonPdu::DataFirst(data_first) - } -} - -impl From for CommonPdu { - fn from(data: DataPdu) -> Self { - CommonPdu::Data(data) - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub enum ServerPdu { CapabilitiesRequest(CapabilitiesRequestPdu), CreateRequest(CreateRequestPdu), + DataFirst(DataFirstPdu), + Data(DataPdu), CloseRequest(ClosePdu), - Common(CommonPdu), } impl ServerPdu { @@ -144,17 +67,21 @@ impl ServerPdu { channel_id_type, dvc_data_size, )?)), - PduType::DataFirst => Ok(ServerPdu::Common(CommonPdu::from_buffer( - PduType::DataFirst, + PduType::DataFirst => { + let data_length_type = + FieldType::from_u8(dvc_header.pdu_dependent).ok_or(ChannelError::InvalidDvcDataLength)?; + + Ok(ServerPdu::DataFirst(DataFirstPdu::from_buffer( + &mut stream, + channel_id_type, + data_length_type, + dvc_data_size, + )?)) + } + PduType::Data => Ok(ServerPdu::Data(DataPdu::from_buffer( &mut stream, - dvc_data_size, channel_id_type, - )?)), - PduType::Data => Ok(ServerPdu::Common(CommonPdu::from_buffer( - PduType::Data, - &mut stream, dvc_data_size, - channel_id_type, )?)), PduType::Close => Ok(ServerPdu::CloseRequest(ClosePdu::from_buffer( &mut stream, @@ -167,7 +94,8 @@ impl ServerPdu { match self { ServerPdu::CapabilitiesRequest(caps_request) => caps_request.to_buffer(&mut stream)?, ServerPdu::CreateRequest(create_request) => create_request.to_buffer(&mut stream)?, - ServerPdu::Common(common) => common.to_buffer(&mut stream)?, + ServerPdu::DataFirst(data_first) => data_first.to_buffer(&mut stream)?, + ServerPdu::Data(data) => data.to_buffer(&mut stream)?, ServerPdu::CloseRequest(close_request) => close_request.to_buffer(&mut stream)?, }; @@ -178,16 +106,18 @@ impl ServerPdu { match self { ServerPdu::CapabilitiesRequest(caps_request) => caps_request.buffer_length(), ServerPdu::CreateRequest(create_request) => create_request.buffer_length(), - ServerPdu::Common(common) => common.buffer_length(), + ServerPdu::DataFirst(data_first) => data_first.buffer_length(), + ServerPdu::Data(data) => data.buffer_length(), ServerPdu::CloseRequest(close_request) => close_request.buffer_length(), } } - pub fn as_short_name(&self) -> &'static str { + pub fn as_short_name(&self) -> &str { match self { ServerPdu::CapabilitiesRequest(_) => "Capabilities Request PDU", ServerPdu::CreateRequest(_) => "Create Request PDU", - ServerPdu::Common(common) => common.as_short_name(), + ServerPdu::DataFirst(_) => "Data First PDU", + ServerPdu::Data(_) => "Data PDU", ServerPdu::CloseRequest(_) => "Close Request PDU", } } @@ -197,8 +127,9 @@ impl ServerPdu { pub enum ClientPdu { CapabilitiesResponse(CapabilitiesResponsePdu), CreateResponse(CreateResponsePdu), + DataFirst(DataFirstPdu), + Data(DataPdu), CloseResponse(ClosePdu), - Common(CommonPdu), } impl ClientPdu { @@ -217,17 +148,21 @@ impl ClientPdu { &mut stream, channel_id_type, )?)), - PduType::DataFirst => Ok(ClientPdu::Common(CommonPdu::from_buffer( - PduType::DataFirst, + PduType::DataFirst => { + let data_length_type = + FieldType::from_u8(dvc_header.pdu_dependent).ok_or(ChannelError::InvalidDvcDataLength)?; + + Ok(ClientPdu::DataFirst(DataFirstPdu::from_buffer( + &mut stream, + channel_id_type, + data_length_type, + dvc_data_size, + )?)) + } + PduType::Data => Ok(ClientPdu::Data(DataPdu::from_buffer( &mut stream, - dvc_data_size, channel_id_type, - )?)), - PduType::Data => Ok(ClientPdu::Common(CommonPdu::from_buffer( - PduType::Data, - &mut stream, dvc_data_size, - channel_id_type, )?)), PduType::Close => Ok(ClientPdu::CloseResponse(ClosePdu::from_buffer( &mut stream, @@ -236,11 +171,12 @@ impl ClientPdu { } } - pub fn to_buffer(&self, mut stream: &mut WriteCursor<'_>) -> Result<(), ChannelError> { + pub fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), ChannelError> { match self { ClientPdu::CapabilitiesResponse(caps_request) => caps_request.to_buffer(&mut stream)?, ClientPdu::CreateResponse(create_request) => create_request.to_buffer(&mut stream)?, - ClientPdu::Common(common) => common.to_buffer(&mut stream)?, + ClientPdu::DataFirst(data_first) => data_first.to_buffer(&mut stream)?, + ClientPdu::Data(data) => data.to_buffer(&mut stream)?, ClientPdu::CloseResponse(close_response) => close_response.to_buffer(&mut stream)?, }; @@ -251,16 +187,18 @@ impl ClientPdu { match self { ClientPdu::CapabilitiesResponse(caps_request) => caps_request.buffer_length(), ClientPdu::CreateResponse(create_request) => create_request.buffer_length(), - ClientPdu::Common(common) => common.buffer_length(), + ClientPdu::DataFirst(data_first) => data_first.buffer_length(), + ClientPdu::Data(data) => data.buffer_length(), ClientPdu::CloseResponse(close_response) => close_response.buffer_length(), } } - pub fn as_short_name(&self) -> &'static str { + pub fn as_short_name(&self) -> &str { match self { ClientPdu::CapabilitiesResponse(_) => "Capabilities Response PDU", ClientPdu::CreateResponse(_) => "Create Response PDU", - ClientPdu::Common(common) => common.as_short_name(), + ClientPdu::DataFirst(_) => "Data First PDU", + ClientPdu::Data(_) => "Data PDU", ClientPdu::CloseResponse(_) => "Close Response PDU", } } diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first.rs index a0b701ce6..6f656d538 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first.rs @@ -1,6 +1,3 @@ -#[cfg(test)] -mod tests; - use std::io; use super::{FieldType, Header, PduType, HEADER_SIZE, PDU_WITH_DATA_MAX_SIZE}; From c384c40b4e906e67c7918891c799058483493e5c Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 19 Mar 2024 18:14:21 -0700 Subject: [PATCH 18/84] Moves relevant Data tests from ironrdp-pdu to ironrdp-dvc --- crates/ironrdp-dvc/src/pdu.rs | 4 +- crates/ironrdp-dvc/src/pdu/tests.rs | 4 ++ crates/ironrdp-dvc/src/pdu/tests/data.rs | 40 +++++++++++ .../src/pdu/{test.rs => tests/data_first.rs} | 3 +- .../ironrdp-pdu/src/rdp/vc/dvc/data/tests.rs | 72 ------------------- 5 files changed, 48 insertions(+), 75 deletions(-) create mode 100644 crates/ironrdp-dvc/src/pdu/tests.rs create mode 100644 crates/ironrdp-dvc/src/pdu/tests/data.rs rename crates/ironrdp-dvc/src/pdu/{test.rs => tests/data_first.rs} (99%) delete mode 100644 crates/ironrdp-pdu/src/rdp/vc/dvc/data/tests.rs diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index 3e0390c50..8888a1ff1 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -1,5 +1,5 @@ #[cfg(test)] -mod test; +mod tests; use crate::{DynamicChannelId, String, Vec}; use alloc::format; @@ -385,7 +385,7 @@ impl From for u8 { /// 2.2.3.2 DVC Data PDU (DYNVC_DATA) /// /// [2.2.3.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/15b59886-db44-47f1-8da3-47c8fcd82803 -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct DataPdu { header: Header, pub channel_id: DynamicChannelId, diff --git a/crates/ironrdp-dvc/src/pdu/tests.rs b/crates/ironrdp-dvc/src/pdu/tests.rs new file mode 100644 index 000000000..62f8da493 --- /dev/null +++ b/crates/ironrdp-dvc/src/pdu/tests.rs @@ -0,0 +1,4 @@ +use super::*; + +mod data; +mod data_first; diff --git a/crates/ironrdp-dvc/src/pdu/tests/data.rs b/crates/ironrdp-dvc/src/pdu/tests/data.rs new file mode 100644 index 000000000..c3501385d --- /dev/null +++ b/crates/ironrdp-dvc/src/pdu/tests/data.rs @@ -0,0 +1,40 @@ +use super::*; +use crate::vec; +use lazy_static::lazy_static; + +const DATA_CHANNEL_ID: u32 = 0x03; +const DATA_PREFIX: [u8; 2] = [0x30, 0x03]; +const DATA_DATA: [u8; 12] = [0x71; 12]; + +lazy_static! { + static ref DATA_ENCODED: Vec = { + let mut result = DATA_PREFIX.to_vec(); + result.extend(DATA_DATA); + result + }; + static ref DATA_DECODED: DataPdu = DataPdu::new(DATA_CHANNEL_ID, DATA_DATA.to_vec()); +} + +#[test] +fn decodes_data_pdu() { + let mut src = ReadCursor::new(&DATA_ENCODED); + match DrdynvcClientPdu::decode(&mut src).unwrap() { + DrdynvcClientPdu::Data(DrdynvcDataPdu::Data(d)) => assert_eq!(*DATA_DECODED, d), + _ => panic!("Expected DataFirst"), + } + + let mut src = ReadCursor::new(&DATA_ENCODED); + match DrdynvcServerPdu::decode(&mut src).unwrap() { + DrdynvcServerPdu::Data(DrdynvcDataPdu::Data(d)) => assert_eq!(*DATA_DECODED, d), + _ => panic!("Expected DataFirst"), + } +} + +#[test] +fn encodes_data_pdu() { + let data = &*DATA_DECODED; + let mut buffer = vec![0x00; data.size()]; + let mut cursor = WriteCursor::new(&mut buffer); + data.encode(&mut cursor).unwrap(); + assert_eq!(DATA_ENCODED.as_slice(), buffer.as_slice()); +} diff --git a/crates/ironrdp-dvc/src/pdu/test.rs b/crates/ironrdp-dvc/src/pdu/tests/data_first.rs similarity index 99% rename from crates/ironrdp-dvc/src/pdu/test.rs rename to crates/ironrdp-dvc/src/pdu/tests/data_first.rs index f36713ee7..e05b0b13a 100644 --- a/crates/ironrdp-dvc/src/pdu/test.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/data_first.rs @@ -1,4 +1,5 @@ -use super::{Cmd, DataFirstPdu, DataPdu, DrdynvcClientPdu, DrdynvcDataPdu, DrdynvcServerPdu, FieldType, Header}; +use super::*; + use crate::{vec, Vec}; use ironrdp_pdu::cursor::{ReadCursor, WriteCursor}; use ironrdp_pdu::PduDecode; diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/data/tests.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/data/tests.rs deleted file mode 100644 index 03cc95ade..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/data/tests.rs +++ /dev/null @@ -1,72 +0,0 @@ -use lazy_static::lazy_static; - -use super::*; - -const DVC_TEST_CHANNEL_ID_U8: u32 = 0x03; - -const DVC_FULL_DATA_BUFFER_SIZE: usize = 14; -const DVC_DATA_PREFIX: [u8; 2] = [0x30, 0x03]; -const DVC_DATA_BUFFER: [u8; 12] = [0x71; 12]; - -const DVC_INVALID_DATA_MESSAGE_BUFFER: [u8; PDU_WITH_DATA_MAX_SIZE] = [0x77; PDU_WITH_DATA_MAX_SIZE]; - -const DVC_TEST_HEADER_SIZE: usize = 0x01; - -lazy_static! { - static ref DVC_FULL_DATA_BUFFER: Vec = { - let mut result = DVC_DATA_PREFIX.to_vec(); - result.extend(DVC_DATA_BUFFER); - - result - }; - static ref DVC_DATA: DataPdu = DataPdu { - channel_id_type: FieldType::U8, - channel_id: DVC_TEST_CHANNEL_ID_U8, - data_size: DVC_DATA_BUFFER.len() - }; -} - -#[test] -fn from_buffer_parsing_for_dvc_data_pdu_with_invalid_message_size_fails() { - match DataPdu::from_buffer( - DVC_INVALID_DATA_MESSAGE_BUFFER.as_ref(), - FieldType::U8, - PDU_WITH_DATA_MAX_SIZE, - ) { - Err(ChannelError::InvalidDvcMessageSize) => (), - res => panic!("Expected InvalidDvcMessageSize error, got: {res:?}"), - }; -} - -#[test] -fn from_buffer_correct_parses_dvc_data_pdu() { - assert_eq!( - DVC_DATA.clone(), - DataPdu::from_buffer( - &DVC_FULL_DATA_BUFFER[1..], - FieldType::U8, - DVC_FULL_DATA_BUFFER_SIZE - DVC_TEST_HEADER_SIZE - ) - .unwrap(), - ); -} - -#[test] -fn to_buffer_correct_serializes_dvc_data_pdu() { - let data = DVC_DATA.clone(); - - let mut buffer = Vec::new(); - data.to_buffer(&mut buffer).unwrap(); - - assert_eq!(DVC_DATA_PREFIX.to_vec(), buffer); -} - -#[test] -fn buffer_length_is_correct_for_dvc_data_pdu() { - let data = DVC_DATA.clone(); - let expected_buf_len = DVC_DATA_PREFIX.len(); - - let len = data.buffer_length(); - - assert_eq!(expected_buf_len, len); -} From 28c9f685c3586c958d4b1c48eb609beb61015639 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 19 Mar 2024 18:19:41 -0700 Subject: [PATCH 19/84] Simplify naming in tests --- crates/ironrdp-dvc/src/pdu/tests/data.rs | 26 ++++---- .../ironrdp-dvc/src/pdu/tests/data_first.rs | 64 +++++++++---------- 2 files changed, 43 insertions(+), 47 deletions(-) diff --git a/crates/ironrdp-dvc/src/pdu/tests/data.rs b/crates/ironrdp-dvc/src/pdu/tests/data.rs index c3501385d..69b0e68e4 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/data.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/data.rs @@ -2,39 +2,39 @@ use super::*; use crate::vec; use lazy_static::lazy_static; -const DATA_CHANNEL_ID: u32 = 0x03; -const DATA_PREFIX: [u8; 2] = [0x30, 0x03]; -const DATA_DATA: [u8; 12] = [0x71; 12]; +const CHANNEL_ID: u32 = 0x03; +const PREFIX: [u8; 2] = [0x30, 0x03]; +const DATA: [u8; 12] = [0x71; 12]; lazy_static! { - static ref DATA_ENCODED: Vec = { - let mut result = DATA_PREFIX.to_vec(); - result.extend(DATA_DATA); + static ref ENCODED: Vec = { + let mut result = PREFIX.to_vec(); + result.extend(DATA); result }; - static ref DATA_DECODED: DataPdu = DataPdu::new(DATA_CHANNEL_ID, DATA_DATA.to_vec()); + static ref DECODED: DataPdu = DataPdu::new(CHANNEL_ID, DATA.to_vec()); } #[test] fn decodes_data_pdu() { - let mut src = ReadCursor::new(&DATA_ENCODED); + let mut src = ReadCursor::new(&ENCODED); match DrdynvcClientPdu::decode(&mut src).unwrap() { - DrdynvcClientPdu::Data(DrdynvcDataPdu::Data(d)) => assert_eq!(*DATA_DECODED, d), + DrdynvcClientPdu::Data(DrdynvcDataPdu::Data(d)) => assert_eq!(*DECODED, d), _ => panic!("Expected DataFirst"), } - let mut src = ReadCursor::new(&DATA_ENCODED); + let mut src = ReadCursor::new(&ENCODED); match DrdynvcServerPdu::decode(&mut src).unwrap() { - DrdynvcServerPdu::Data(DrdynvcDataPdu::Data(d)) => assert_eq!(*DATA_DECODED, d), + DrdynvcServerPdu::Data(DrdynvcDataPdu::Data(d)) => assert_eq!(*DECODED, d), _ => panic!("Expected DataFirst"), } } #[test] fn encodes_data_pdu() { - let data = &*DATA_DECODED; + let data = &*DECODED; let mut buffer = vec![0x00; data.size()]; let mut cursor = WriteCursor::new(&mut buffer); data.encode(&mut cursor).unwrap(); - assert_eq!(DATA_ENCODED.as_slice(), buffer.as_slice()); + assert_eq!(ENCODED.as_slice(), buffer.as_slice()); } diff --git a/crates/ironrdp-dvc/src/pdu/tests/data_first.rs b/crates/ironrdp-dvc/src/pdu/tests/data_first.rs index e05b0b13a..e522ac7ec 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/data_first.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/data_first.rs @@ -5,16 +5,16 @@ use ironrdp_pdu::cursor::{ReadCursor, WriteCursor}; use ironrdp_pdu::PduDecode; use lazy_static::lazy_static; -const DATA_FIRST_DATA_LENGTH: u32 = 0xC7B; -const DATA_FIRST_CHANNEL_ID: u32 = 0x03; -const DATA_FIRST_PREFIX: [u8; 4] = [0x24, 0x03, 0x7b, 0x0c]; -const DATA_FIRST_DATA: [u8; 12] = [0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71]; +const LENGTH: u32 = 0xC7B; +const CHANNEL_ID: u32 = 0x03; +const PREFIX: [u8; 4] = [0x24, 0x03, 0x7b, 0x0c]; +const DATA: [u8; 12] = [0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71]; // Edge case is when the total length is equal to data length -const DATA_FIRST_EDGE_CASE_DATA_LENGTH: u32 = 0x639; -const DATA_FIRST_EDGE_CASE_CHANNEL_ID: u32 = 0x07; -const DATA_FIRST_EDGE_CASE_PREFIX: [u8; 4] = [0x24, 0x7, 0x39, 0x6]; -const DATA_FIRST_EDGE_CASE_DATA: [u8; DATA_FIRST_EDGE_CASE_DATA_LENGTH as usize] = [ +const EDGE_CASE_LENGTH: u32 = 0x639; +const EDGE_CASE_CHANNEL_ID: u32 = 0x07; +const EDGE_CASE_PREFIX: [u8; 4] = [0x24, 0x7, 0x39, 0x6]; +const EDGE_CASE_DATA: [u8; EDGE_CASE_LENGTH as usize] = [ 0xe0, 0x24, 0xa9, 0xba, 0xe0, 0x68, 0xa9, 0xba, 0x8a, 0x73, 0x41, 0x25, 0x12, 0x12, 0x1c, 0x28, 0x3b, 0xa6, 0x34, 0x8, 0x8, 0x7a, 0x38, 0x34, 0x2c, 0xe8, 0xf8, 0xd0, 0xef, 0x18, 0xc2, 0xc, 0x27, 0x1f, 0xb1, 0x83, 0x3c, 0x58, 0x8a, 0x67, 0x1, 0x58, 0x9d, 0x50, 0x8b, 0x8c, 0x60, 0x31, 0x53, 0x55, 0x54, 0xd8, 0x51, 0x32, 0x23, 0x54, 0xd9, @@ -102,28 +102,24 @@ const DATA_FIRST_EDGE_CASE_DATA: [u8; DATA_FIRST_EDGE_CASE_DATA_LENGTH as usize] ]; lazy_static! { - static ref DATA_FIRST_ENCODED: Vec = { - let mut result = DATA_FIRST_PREFIX.to_vec(); - result.extend(DATA_FIRST_DATA); + static ref ENCODED: Vec = { + let mut result = PREFIX.to_vec(); + result.extend(DATA); result }; - static ref DATA_FIRST_DECODED: DataFirstPdu = { - let mut res = DataFirstPdu::new(DATA_FIRST_CHANNEL_ID, DATA_FIRST_DATA_LENGTH, DATA_FIRST_DATA.to_vec()); + static ref DECODED: DataFirstPdu = { + let mut res = DataFirstPdu::new(CHANNEL_ID, LENGTH, DATA.to_vec()); res.header.cb_id = FieldType::U8; res.header.sp = FieldType::U16; res }; - static ref DATA_FIRST_EDGE_CASE_ENCODED: Vec = { - let mut result = DATA_FIRST_EDGE_CASE_PREFIX.to_vec(); - result.append(&mut DATA_FIRST_EDGE_CASE_DATA.to_vec()); + static ref EDGE_CASE_ENCODED: Vec = { + let mut result = EDGE_CASE_PREFIX.to_vec(); + result.append(&mut EDGE_CASE_DATA.to_vec()); result }; - static ref DATA_FIRST_EDGE_CASE_DECODED: DataFirstPdu = { - let mut res = DataFirstPdu::new( - DATA_FIRST_EDGE_CASE_CHANNEL_ID, - DATA_FIRST_EDGE_CASE_DATA_LENGTH, - DATA_FIRST_EDGE_CASE_DATA.to_vec(), - ); + static ref EDGE_CASE_DECODED: DataFirstPdu = { + let mut res = DataFirstPdu::new(EDGE_CASE_CHANNEL_ID, EDGE_CASE_LENGTH, EDGE_CASE_DATA.to_vec()); res.header.cb_id = FieldType::U8; res.header.sp = FieldType::U16; res @@ -132,48 +128,48 @@ lazy_static! { #[test] fn decodes_data_first_pdu() { - let mut src = ReadCursor::new(&DATA_FIRST_ENCODED); + let mut src = ReadCursor::new(&ENCODED); match DrdynvcClientPdu::decode(&mut src).unwrap() { - DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*DATA_FIRST_DECODED, df), + DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*DECODED, df), _ => panic!("Expected DataFirst"), } - let mut src = ReadCursor::new(&DATA_FIRST_ENCODED); + let mut src = ReadCursor::new(&ENCODED); match DrdynvcServerPdu::decode(&mut src).unwrap() { - DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*DATA_FIRST_DECODED, df), + DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*DECODED, df), _ => panic!("Expected DataFirst"), } } #[test] fn encodes_data_first_pdu() { - let data_first = &*DATA_FIRST_DECODED; + let data_first = &*DECODED; let mut buffer = vec![0x00; data_first.size()]; let mut cursor = WriteCursor::new(&mut buffer); data_first.encode(&mut cursor).unwrap(); - assert_eq!(DATA_FIRST_ENCODED.as_slice(), buffer.as_slice()); + assert_eq!(ENCODED.as_slice(), buffer.as_slice()); } #[test] fn decodes_data_first_edge_case() { - let mut src = ReadCursor::new(&DATA_FIRST_EDGE_CASE_ENCODED); + let mut src = ReadCursor::new(&EDGE_CASE_ENCODED); match DrdynvcClientPdu::decode(&mut src).unwrap() { - DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*DATA_FIRST_EDGE_CASE_DECODED, df), + DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*EDGE_CASE_DECODED, df), _ => panic!("Expected DataFirst"), } - let mut src = ReadCursor::new(&DATA_FIRST_EDGE_CASE_ENCODED); + let mut src = ReadCursor::new(&EDGE_CASE_ENCODED); match DrdynvcServerPdu::decode(&mut src).unwrap() { - DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*DATA_FIRST_EDGE_CASE_DECODED, df), + DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*EDGE_CASE_DECODED, df), _ => panic!("Expected DataFirst"), } } #[test] fn encodes_data_first_edge_case() { - let data_first = &*DATA_FIRST_EDGE_CASE_DECODED; + let data_first = &*EDGE_CASE_DECODED; let mut buffer = vec![0x00; data_first.size()]; let mut cursor = WriteCursor::new(&mut buffer); data_first.encode(&mut cursor).unwrap(); - assert_eq!(DATA_FIRST_EDGE_CASE_ENCODED.as_slice(), buffer.as_slice()); + assert_eq!(EDGE_CASE_ENCODED.as_slice(), buffer.as_slice()); } From 266c735a86a7bfe66099ba3fcaced652371218f7 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 19 Mar 2024 18:45:34 -0700 Subject: [PATCH 20/84] Moves relevant Create tests from ironrdp-pdu to ironrdp-dvc. Also enables testing in ironrdp-dvc by eliminating the test = false line in Cargo.toml --- crates/ironrdp-dvc/Cargo.toml | 1 - crates/ironrdp-dvc/src/lib.rs | 2 +- crates/ironrdp-dvc/src/pdu.rs | 4 +- crates/ironrdp-dvc/src/pdu/tests.rs | 8 +- crates/ironrdp-dvc/src/pdu/tests/create.rs | 68 ++++++++++++++ crates/ironrdp-pdu/src/rdp/vc/dvc/create.rs | 3 - .../src/rdp/vc/dvc/create/tests.rs | 88 ------------------- crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs | 3 - 8 files changed, 77 insertions(+), 100 deletions(-) create mode 100644 crates/ironrdp-dvc/src/pdu/tests/create.rs delete mode 100644 crates/ironrdp-pdu/src/rdp/vc/dvc/create/tests.rs diff --git a/crates/ironrdp-dvc/Cargo.toml b/crates/ironrdp-dvc/Cargo.toml index 61cea9887..549dbbaaa 100644 --- a/crates/ironrdp-dvc/Cargo.toml +++ b/crates/ironrdp-dvc/Cargo.toml @@ -13,7 +13,6 @@ categories.workspace = true [lib] doctest = false -test = false [features] default = [] diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 9677e6533..afdc2c72a 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -37,7 +37,7 @@ mod server; pub use server::*; pub mod display; -mod pdu; +pub mod pdu; /// Represents a message that, when encoded, forms a complete PDU for a given dynamic virtual channel. /// This means a message that is ready to be wrapped in [`dvc::CommonPdu::DataFirst`] and [`dvc::CommonPdu::Data`] PDUs diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index 8888a1ff1..9689a68cd 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -434,7 +434,7 @@ impl DataPdu { /// 2.2.2.2 DVC Create Response PDU (DYNVC_CREATE_RSP) /// /// [2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/8f284ea3-54f3-4c24-8168-8a001c63b581 -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct CreateResponsePdu { header: Header, pub channel_id: DynamicChannelId, @@ -727,7 +727,7 @@ impl CapabilitiesRequestPdu { /// 2.2.2.1 DVC Create Request PDU (DYNVC_CREATE_REQ) /// /// [2.2.2.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/4448ba4d-9a72-429f-8b65-6f4ec44f2985 -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct CreateRequestPdu { header: Header, pub channel_id: DynamicChannelId, diff --git a/crates/ironrdp-dvc/src/pdu/tests.rs b/crates/ironrdp-dvc/src/pdu/tests.rs index 62f8da493..3ff7dad66 100644 --- a/crates/ironrdp-dvc/src/pdu/tests.rs +++ b/crates/ironrdp-dvc/src/pdu/tests.rs @@ -1,4 +1,8 @@ use super::*; -mod data; -mod data_first; +#[cfg(test)] +pub mod create; +#[cfg(test)] +pub mod data; +#[cfg(test)] +pub mod data_first; diff --git a/crates/ironrdp-dvc/src/pdu/tests/create.rs b/crates/ironrdp-dvc/src/pdu/tests/create.rs new file mode 100644 index 000000000..382c84544 --- /dev/null +++ b/crates/ironrdp-dvc/src/pdu/tests/create.rs @@ -0,0 +1,68 @@ +use super::*; +use crate::vec; +use lazy_static::lazy_static; + +const CHANNEL_ID: u32 = 0x0000_0003; +const REQ_ENCODED: [u8; 10] = [0x10, 0x03, 0x74, 0x65, 0x73, 0x74, 0x64, 0x76, 0x63, 0x00]; +const RESP_ENCODED: [u8; 6] = [0x10, 0x03, 0x00, 0x00, 0x00, 0x00]; + +lazy_static! { + static ref REQ_DECODED: CreateRequestPdu = CreateRequestPdu::new(CHANNEL_ID, String::from("testdvc")); + static ref RESP_DECODED: CreateResponsePdu = CreateResponsePdu::new(CHANNEL_ID, CreationStatus::OK); +} + +#[test] +fn decodes_create_request() { + let mut src = ReadCursor::new(&REQ_ENCODED); + match DrdynvcServerPdu::decode(&mut src).unwrap() { + DrdynvcServerPdu::Create(pdu) => assert_eq!(*REQ_DECODED, pdu), + _ => panic!("Expected DataFirst"), + } +} + +#[test] +fn encodes_create_request() { + let data = &*REQ_DECODED; + let mut buffer = vec![0x00; data.size()]; + let mut cursor = WriteCursor::new(&mut buffer); + data.encode(&mut cursor).unwrap(); + assert_eq!(REQ_ENCODED.as_slice(), buffer.as_slice()); +} + +#[test] +fn decodes_create_response() { + let mut src = ReadCursor::new(&RESP_ENCODED); + match DrdynvcClientPdu::decode(&mut src).unwrap() { + DrdynvcClientPdu::Create(pdu) => assert_eq!(*RESP_DECODED, pdu), + _ => panic!("Expected DataFirst"), + } +} + +#[test] +fn encodes_create_response() { + let data = &*RESP_DECODED; + let mut buffer = vec![0x00; data.size()]; + let mut cursor = WriteCursor::new(&mut buffer); + data.encode(&mut cursor).unwrap(); + assert_eq!(RESP_ENCODED.as_slice(), buffer.as_slice()); +} + +// #[test] +// fn to_buffer_correct_serializes_dvc_create_response_pdu() { +// let create_response = DVC_CREATE_RESPONSE.clone(); + +// let mut buffer = Vec::new(); +// create_response.to_buffer(&mut buffer).unwrap(); + +// assert_eq!(RESP_ENCODED.as_ref(), buffer.as_slice()); +// } + +// #[test] +// fn buffer_length_is_correct_for_dvc_create_response_pdu() { +// let create_response = DVC_CREATE_RESPONSE.clone(); +// let expected_buf_len = RESP_ENCODED.len(); + +// let len = create_response.buffer_length(); + +// assert_eq!(expected_buf_len, len); +// } diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/create.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/create.rs index be89c28ca..b0ed9514e 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/create.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/create.rs @@ -1,6 +1,3 @@ -#[cfg(test)] -mod tests; - use std::io; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/create/tests.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/create/tests.rs deleted file mode 100644 index 39e18b2cd..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/create/tests.rs +++ /dev/null @@ -1,88 +0,0 @@ -use lazy_static::lazy_static; - -use super::*; - -const TEST_CHANNEL_ID: u32 = 0x0000_0003; - -const DVC_CREATE_REQUEST_BUFFER_SIZE: usize = 10; -const DVC_CREATE_REQUEST_BUFFER: [u8; DVC_CREATE_REQUEST_BUFFER_SIZE] = - [0x10, 0x03, 0x74, 0x65, 0x73, 0x74, 0x64, 0x76, 0x63, 0x00]; - -const DVC_CREATE_RESPONSE_BUFFER_SIZE: usize = 6; -const DVC_CREATE_RESPONSE_BUFFER: [u8; DVC_CREATE_RESPONSE_BUFFER_SIZE] = [0x10, 0x03, 0x00, 0x00, 0x00, 0x00]; - -const DVC_TEST_HEADER_SIZE: usize = 0x01; - -lazy_static! { - static ref DVC_CREATE_REQUEST: CreateRequestPdu = CreateRequestPdu { - channel_id_type: FieldType::U8, - channel_id: TEST_CHANNEL_ID, - channel_name: String::from("testdvc") - }; - static ref DVC_CREATE_RESPONSE: CreateResponsePdu = CreateResponsePdu { - channel_id_type: FieldType::U8, - channel_id: TEST_CHANNEL_ID, - creation_status: DVC_CREATION_STATUS_OK - }; -} - -#[test] -fn from_buffer_correct_parses_dvc_create_request_pdu() { - assert_eq!( - DVC_CREATE_REQUEST.clone(), - CreateRequestPdu::from_buffer( - &DVC_CREATE_REQUEST_BUFFER[1..], - FieldType::U8, - DVC_CREATE_REQUEST_BUFFER_SIZE - DVC_TEST_HEADER_SIZE - ) - .unwrap(), - ); -} - -#[test] -fn to_buffer_correct_serializes_dvc_create_request_pdu() { - let create_request = DVC_CREATE_REQUEST.clone(); - - let mut buffer = Vec::new(); - create_request.to_buffer(&mut buffer).unwrap(); - - assert_eq!(DVC_CREATE_REQUEST_BUFFER.as_ref(), buffer.as_slice()); -} - -#[test] -fn buffer_length_is_correct_for_dvc_create_request_pdu() { - let create_request = DVC_CREATE_REQUEST.clone(); - let expected_buf_len = DVC_CREATE_REQUEST_BUFFER.len(); - - let len = create_request.buffer_length(); - - assert_eq!(expected_buf_len, len); -} - -#[test] -fn from_buffer_correct_parses_dvc_create_response_pdu() { - assert_eq!( - DVC_CREATE_RESPONSE.clone(), - CreateResponsePdu::from_buffer(&DVC_CREATE_RESPONSE_BUFFER[1..], FieldType::U8).unwrap(), - ); -} - -#[test] -fn to_buffer_correct_serializes_dvc_create_response_pdu() { - let create_response = DVC_CREATE_RESPONSE.clone(); - - let mut buffer = Vec::new(); - create_response.to_buffer(&mut buffer).unwrap(); - - assert_eq!(DVC_CREATE_RESPONSE_BUFFER.as_ref(), buffer.as_slice()); -} - -#[test] -fn buffer_length_is_correct_for_dvc_create_response_pdu() { - let create_response = DVC_CREATE_RESPONSE.clone(); - let expected_buf_len = DVC_CREATE_RESPONSE_BUFFER.len(); - - let len = create_response.buffer_length(); - - assert_eq!(expected_buf_len, len); -} diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs index 4539a58d9..b813dae9c 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs @@ -1,6 +1,3 @@ -#[cfg(test)] -mod tests; - use std::io; use super::{FieldType, Header, PduType, HEADER_SIZE, PDU_WITH_DATA_MAX_SIZE, UNUSED_U8}; From 856a0aaa14ff3f3edc2575ce42e3cc84e1371ced Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 19 Mar 2024 18:54:00 -0700 Subject: [PATCH 21/84] Moves relevant Close tests from ironrdp-pdu to ironrdp-dvc. --- crates/ironrdp-dvc/src/pdu.rs | 2 +- crates/ironrdp-dvc/src/pdu/tests.rs | 8 ++-- crates/ironrdp-dvc/src/pdu/tests/close.rs | 39 +++++++++++++++++ crates/ironrdp-dvc/src/pdu/tests/data.rs | 4 +- .../ironrdp-dvc/src/pdu/tests/data_first.rs | 4 +- crates/ironrdp-pdu/src/rdp/vc/dvc/close.rs | 3 -- .../ironrdp-pdu/src/rdp/vc/dvc/close/tests.rs | 43 ------------------- 7 files changed, 49 insertions(+), 54 deletions(-) create mode 100644 crates/ironrdp-dvc/src/pdu/tests/close.rs delete mode 100644 crates/ironrdp-pdu/src/rdp/vc/dvc/close/tests.rs diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index 9689a68cd..00755564a 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -507,7 +507,7 @@ impl From for u32 { /// 2.2.4 Closing a DVC (DYNVC_CLOSE) /// /// [2.2.4]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/c02dfd21-ccbc-4254-985b-3ef6dd115dec -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct ClosePdu { header: Header, pub channel_id: DynamicChannelId, diff --git a/crates/ironrdp-dvc/src/pdu/tests.rs b/crates/ironrdp-dvc/src/pdu/tests.rs index 3ff7dad66..48c57f7b4 100644 --- a/crates/ironrdp-dvc/src/pdu/tests.rs +++ b/crates/ironrdp-dvc/src/pdu/tests.rs @@ -1,8 +1,10 @@ use super::*; #[cfg(test)] -pub mod create; +mod close; #[cfg(test)] -pub mod data; +mod create; #[cfg(test)] -pub mod data_first; +mod data; +#[cfg(test)] +mod data_first; diff --git a/crates/ironrdp-dvc/src/pdu/tests/close.rs b/crates/ironrdp-dvc/src/pdu/tests/close.rs new file mode 100644 index 000000000..8f9accc9e --- /dev/null +++ b/crates/ironrdp-dvc/src/pdu/tests/close.rs @@ -0,0 +1,39 @@ +use crate::vec; +use lazy_static::lazy_static; + +use super::*; + +const CHANNEL_ID: u32 = 0x0303; +const ENCODED: [u8; 3] = [0x41, 0x03, 0x03]; + +lazy_static! { + static ref DECODED: ClosePdu = { + let mut pdu = ClosePdu::new(CHANNEL_ID); + pdu.header.cb_id = FieldType::U16; + pdu + }; +} + +#[test] +fn decodes_close() { + let mut src = ReadCursor::new(&ENCODED); + match DrdynvcClientPdu::decode(&mut src).unwrap() { + DrdynvcClientPdu::Close(pdu) => assert_eq!(*DECODED, pdu), + _ => panic!("Expected DataFirst"), + } + + let mut src = ReadCursor::new(&ENCODED); + match DrdynvcServerPdu::decode(&mut src).unwrap() { + DrdynvcServerPdu::Close(pdu) => assert_eq!(*DECODED, pdu), + _ => panic!("Expected DataFirst"), + } +} + +#[test] +fn encodes_close() { + let data = &*DECODED; + let mut buffer = vec![0x00; data.size()]; + let mut cursor = WriteCursor::new(&mut buffer); + data.encode(&mut cursor).unwrap(); + assert_eq!(ENCODED.as_slice(), buffer.as_slice()); +} diff --git a/crates/ironrdp-dvc/src/pdu/tests/data.rs b/crates/ironrdp-dvc/src/pdu/tests/data.rs index 69b0e68e4..844309405 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/data.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/data.rs @@ -16,7 +16,7 @@ lazy_static! { } #[test] -fn decodes_data_pdu() { +fn decodes_data() { let mut src = ReadCursor::new(&ENCODED); match DrdynvcClientPdu::decode(&mut src).unwrap() { DrdynvcClientPdu::Data(DrdynvcDataPdu::Data(d)) => assert_eq!(*DECODED, d), @@ -31,7 +31,7 @@ fn decodes_data_pdu() { } #[test] -fn encodes_data_pdu() { +fn encodes_data() { let data = &*DECODED; let mut buffer = vec![0x00; data.size()]; let mut cursor = WriteCursor::new(&mut buffer); diff --git a/crates/ironrdp-dvc/src/pdu/tests/data_first.rs b/crates/ironrdp-dvc/src/pdu/tests/data_first.rs index e522ac7ec..29fc09ffb 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/data_first.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/data_first.rs @@ -127,7 +127,7 @@ lazy_static! { } #[test] -fn decodes_data_first_pdu() { +fn decodes_data_first() { let mut src = ReadCursor::new(&ENCODED); match DrdynvcClientPdu::decode(&mut src).unwrap() { DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*DECODED, df), @@ -142,7 +142,7 @@ fn decodes_data_first_pdu() { } #[test] -fn encodes_data_first_pdu() { +fn encodes_data_first() { let data_first = &*DECODED; let mut buffer = vec![0x00; data_first.size()]; let mut cursor = WriteCursor::new(&mut buffer); diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/close.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/close.rs index 14e5bcf8c..11fad4095 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/close.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/close.rs @@ -1,6 +1,3 @@ -#[cfg(test)] -mod tests; - use std::io; use super::{FieldType, Header, PduType, HEADER_SIZE, UNUSED_U8}; diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/close/tests.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/close/tests.rs deleted file mode 100644 index 2cf6be4a1..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/close/tests.rs +++ /dev/null @@ -1,43 +0,0 @@ -use lazy_static::lazy_static; - -use super::*; - -const DVC_TEST_CHANNEL_ID_U16: u32 = 0x0303; - -const DVC_CLOSE_BUFFER_SIZE: usize = 3; -const DVC_CLOSE_BUFFER: [u8; DVC_CLOSE_BUFFER_SIZE] = [0x41, 0x03, 0x03]; - -lazy_static! { - static ref DVC_CLOSE: ClosePdu = ClosePdu { - channel_id_type: FieldType::U16, - channel_id: DVC_TEST_CHANNEL_ID_U16 - }; -} - -#[test] -fn from_buffer_correct_parses_dvc_close_pdu() { - assert_eq!( - DVC_CLOSE.clone(), - ClosePdu::from_buffer(&DVC_CLOSE_BUFFER[1..], FieldType::U16).unwrap(), - ); -} - -#[test] -fn to_buffer_correct_serializes_dvc_close_pdu() { - let close = DVC_CLOSE.clone(); - - let mut buffer = Vec::new(); - close.to_buffer(&mut buffer).unwrap(); - - assert_eq!(DVC_CLOSE_BUFFER.as_ref(), buffer.as_slice()); -} - -#[test] -fn buffer_length_is_correct_for_dvc_close_pdu() { - let close = DVC_CLOSE.clone(); - let expected_buf_len = DVC_CLOSE_BUFFER.len(); - - let len = close.buffer_length(); - - assert_eq!(expected_buf_len, len); -} From 0dea43914689ae6b79966847a53a59d4991e0d3f Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 19 Mar 2024 19:10:52 -0700 Subject: [PATCH 22/84] Moves relevant Capabilities tests from ironrdp-pdu to ironrdp-dvc. --- crates/ironrdp-dvc/src/pdu.rs | 20 ++- crates/ironrdp-dvc/src/pdu/tests.rs | 2 + .../ironrdp-dvc/src/pdu/tests/capabilities.rs | 69 ++++++++++ crates/ironrdp-dvc/src/server.rs | 2 +- .../src/rdp/vc/dvc/capabilities.rs | 3 - .../src/rdp/vc/dvc/capabilities/tests.rs | 125 ------------------ 6 files changed, 80 insertions(+), 141 deletions(-) create mode 100644 crates/ironrdp-dvc/src/pdu/tests/capabilities.rs delete mode 100644 crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities/tests.rs diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index 00755564a..1ec60a0a2 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -545,7 +545,7 @@ impl ClosePdu { /// 2.2.1.2 DVC Capabilities Response PDU (DYNVC_CAPS_RSP) /// /// [2.2.1.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/d45cb2a6-e7bd-453e-8603-9c57600e24ce -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct CapabilitiesResponsePdu { header: Header, version: CapsVersion, @@ -584,7 +584,7 @@ impl CapabilitiesResponsePdu { } #[repr(u16)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum CapsVersion { V1 = 0x0001, V2 = 0x0002, @@ -625,7 +625,7 @@ impl From for u16 { /// 2.2.1.1 DVC Capabilities Request PDU /// /// [2.2.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/c07b15ae-304e-46b8-befe-39c6d95c25e0 -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum CapabilitiesRequestPdu { V1 { header: Header, @@ -647,18 +647,14 @@ impl CapabilitiesRequestPdu { const PRIORITY_CHARGE_COUNT: usize = 4; // 4 priority charges const PRIORITY_CHARGES_SIZE: usize = Self::PRIORITY_CHARGE_COUNT * Self::PRIORITY_CHARGE_SIZE; - pub fn new(version: CapsVersion) -> Self { + pub fn new(version: CapsVersion, charges: Option<[u16; Self::PRIORITY_CHARGE_COUNT]>) -> Self { let header = Header::new(0, 0, Cmd::Capability); + let charges = charges.unwrap_or([0; Self::PRIORITY_CHARGE_COUNT]); + match version { CapsVersion::V1 => Self::V1 { header }, - CapsVersion::V2 => Self::V2 { - header, - charges: [0; Self::PRIORITY_CHARGE_COUNT], - }, - CapsVersion::V3 => Self::V3 { - header, - charges: [0; Self::PRIORITY_CHARGE_COUNT], - }, + CapsVersion::V2 => Self::V2 { header, charges }, + CapsVersion::V3 => Self::V3 { header, charges }, } } diff --git a/crates/ironrdp-dvc/src/pdu/tests.rs b/crates/ironrdp-dvc/src/pdu/tests.rs index 48c57f7b4..e9d87c377 100644 --- a/crates/ironrdp-dvc/src/pdu/tests.rs +++ b/crates/ironrdp-dvc/src/pdu/tests.rs @@ -1,5 +1,7 @@ use super::*; +#[cfg(test)] +mod capabilities; #[cfg(test)] mod close; #[cfg(test)] diff --git a/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs b/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs new file mode 100644 index 000000000..bbda43ffa --- /dev/null +++ b/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs @@ -0,0 +1,69 @@ +use crate::vec; +use lazy_static::lazy_static; + +use super::*; + +const REQ_V1_ENCODED: [u8; 4] = [0x50, 0x00, 0x01, 0x00]; +const REQ_V2_ENCODED: [u8; 12] = [0x50, 0x00, 0x02, 0x00, 0x33, 0x33, 0x11, 0x11, 0x3d, 0x0a, 0xa7, 0x04]; +const RESP_V1_ENCODED: [u8; 4] = [0x50, 0x00, 0x01, 0x00]; + +lazy_static! { + static ref REQ_V1_DECODED: CapabilitiesRequestPdu = CapabilitiesRequestPdu::new(CapsVersion::V1, None); + static ref REQ_V2_DECODED: CapabilitiesRequestPdu = + CapabilitiesRequestPdu::new(CapsVersion::V2, Some([0x3333, 0x1111, 0x0a3d, 0x04a7])); + static ref RESP_V1_DECODED: CapabilitiesResponsePdu = CapabilitiesResponsePdu::new(CapsVersion::V1); +} + +#[test] +fn decodes_request_v1() { + let mut src = ReadCursor::new(&REQ_V1_ENCODED); + match DrdynvcServerPdu::decode(&mut src).unwrap() { + DrdynvcServerPdu::Capabilities(pdu) => assert_eq!(*REQ_V1_DECODED, pdu), + _ => panic!("Expected DataFirst"), + } +} + +#[test] +fn encodes_request_v1() { + let data = &*REQ_V1_DECODED; + let mut buffer = vec![0x00; data.size()]; + let mut cursor = WriteCursor::new(&mut buffer); + data.encode(&mut cursor).unwrap(); + assert_eq!(REQ_V1_ENCODED.as_ref(), buffer.as_slice()); +} + +#[test] +fn decodes_request_v2() { + let mut src = ReadCursor::new(&REQ_V2_ENCODED); + match DrdynvcServerPdu::decode(&mut src).unwrap() { + DrdynvcServerPdu::Capabilities(pdu) => assert_eq!(*REQ_V2_DECODED, pdu), + _ => panic!("Expected DataFirst"), + } +} + +#[test] +fn encodes_request_v2() { + let data = &*REQ_V2_DECODED; + let mut buffer = vec![0x00; data.size()]; + let mut cursor = WriteCursor::new(&mut buffer); + data.encode(&mut cursor).unwrap(); + assert_eq!(REQ_V2_ENCODED.as_ref(), buffer.as_slice()); +} + +#[test] +fn decodes_response_v1() { + let mut src = ReadCursor::new(&RESP_V1_ENCODED); + match DrdynvcClientPdu::decode(&mut src).unwrap() { + DrdynvcClientPdu::Capabilities(pdu) => assert_eq!(*RESP_V1_DECODED, pdu), + _ => panic!("Expected DataFirst"), + } +} + +#[test] +fn encodes_response_v1() { + let data = &*RESP_V1_DECODED; + let mut buffer = vec![0x00; data.size()]; + let mut cursor = WriteCursor::new(&mut buffer); + data.encode(&mut cursor).unwrap(); + assert_eq!(RESP_V1_ENCODED.as_ref(), buffer.as_slice()); +} diff --git a/crates/ironrdp-dvc/src/server.rs b/crates/ironrdp-dvc/src/server.rs index a982807b0..8f3771e1e 100644 --- a/crates/ironrdp-dvc/src/server.rs +++ b/crates/ironrdp-dvc/src/server.rs @@ -117,7 +117,7 @@ impl SvcProcessor for DrdynvcServer { } fn start(&mut self) -> PduResult> { - let cap = CapabilitiesRequestPdu::new(CapsVersion::V1); + let cap = CapabilitiesRequestPdu::new(CapsVersion::V1, None); let req = DrdynvcServerPdu::Capabilities(cap); let msg = as_svc_msg_with_flag(req)?; Ok(alloc::vec![msg]) diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities.rs index 5e51f81cd..8104ed285 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities.rs @@ -1,6 +1,3 @@ -#[cfg(test)] -mod tests; - use std::io; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities/tests.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities/tests.rs deleted file mode 100644 index 732591102..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities/tests.rs +++ /dev/null @@ -1,125 +0,0 @@ -use lazy_static::lazy_static; - -use super::*; - -const DVC_CAPABILITIES_REQUEST_V1_SIZE: usize = 4; -const DVC_CAPABILITIES_REQUEST_V1_BUFFER: [u8; DVC_CAPABILITIES_REQUEST_V1_SIZE] = [0x50, 0x00, 0x01, 0x00]; - -const DVC_CAPABILITIES_REQUEST_V2_SIZE: usize = 12; -const DVC_CAPABILITIES_REQUEST_V2_BUFFER: [u8; DVC_CAPABILITIES_REQUEST_V2_SIZE] = - [0x50, 0x00, 0x02, 0x00, 0x33, 0x33, 0x11, 0x11, 0x3d, 0x0a, 0xa7, 0x04]; - -const DVC_CAPABILITIES_RESPONSE_SIZE: usize = 4; -const DVC_CAPABILITIES_RESPONSE_BUFFER: [u8; DVC_CAPABILITIES_RESPONSE_SIZE] = [0x50, 0x00, 0x01, 0x00]; - -lazy_static! { - static ref DVC_CAPABILITIES_REQUEST_V1: CapabilitiesRequestPdu = CapabilitiesRequestPdu::V1; - static ref DVC_CAPABILITIES_REQUEST_V2: CapabilitiesRequestPdu = CapabilitiesRequestPdu::V2 { - charges: [0x3333, 0x1111, 0x0a3d, 0x04a7] - }; - static ref DVC_CAPABILITIES_RESPONSE: CapabilitiesResponsePdu = CapabilitiesResponsePdu { - version: CapsVersion::V1 - }; -} - -#[test] -fn from_buffer_parsing_for_dvc_caps_request_pdu_with_invalid_caps_version_fails() { - let buffer_with_invalid_caps_version = vec![0x00, 0x01, 0x01]; - match CapabilitiesRequestPdu::from_buffer(buffer_with_invalid_caps_version.as_slice()) { - Err(ChannelError::InvalidDvcCapabilitiesVersion) => (), - res => panic!("Expected InvalidDvcCapabilitiesVersion error, got: {res:?}"), - }; -} - -#[test] -fn from_buffer_correct_parses_dvc_capabilities_request_pdu_v1() { - assert_eq!( - DVC_CAPABILITIES_REQUEST_V1.clone(), - CapabilitiesRequestPdu::from_buffer(&DVC_CAPABILITIES_REQUEST_V1_BUFFER[1..]).unwrap(), - ); -} - -#[test] -fn to_buffer_correct_serializes_dvc_capabilities_request_pdu_v1() { - let dvc_capabilities_request_pdu_v1 = DVC_CAPABILITIES_REQUEST_V1.clone(); - - let mut buffer = Vec::new(); - dvc_capabilities_request_pdu_v1.to_buffer(&mut buffer).unwrap(); - - assert_eq!(DVC_CAPABILITIES_REQUEST_V1_BUFFER.as_ref(), buffer.as_slice()); -} - -#[test] -fn buffer_length_is_correct_for_dvc_capabilities_request_pdu_v1() { - let dvc_capabilities_request_pdu_v1 = DVC_CAPABILITIES_REQUEST_V1.clone(); - let expected_buf_len = DVC_CAPABILITIES_REQUEST_V1_BUFFER.len(); - - let len = dvc_capabilities_request_pdu_v1.buffer_length(); - - assert_eq!(expected_buf_len, len); -} - -#[test] -fn from_buffer_correct_parses_dvc_capabilities_request_pdu_v2() { - assert_eq!( - DVC_CAPABILITIES_REQUEST_V2.clone(), - CapabilitiesRequestPdu::from_buffer(&DVC_CAPABILITIES_REQUEST_V2_BUFFER[1..]).unwrap(), - ); -} - -#[test] -fn to_buffer_correct_serializes_dvc_capabilities_request_pdu_v2() { - let dvc_capabilities_request_pdu_v2 = DVC_CAPABILITIES_REQUEST_V2.clone(); - - let mut buffer = Vec::new(); - dvc_capabilities_request_pdu_v2.to_buffer(&mut buffer).unwrap(); - - assert_eq!(DVC_CAPABILITIES_REQUEST_V2_BUFFER.as_ref(), buffer.as_slice()); -} - -#[test] -fn buffer_length_is_correct_for_dvc_capabilities_request_pdu_v2() { - let dvc_capabilities_request_pdu_v2 = DVC_CAPABILITIES_REQUEST_V2.clone(); - let expected_buf_len = DVC_CAPABILITIES_REQUEST_V2_BUFFER.len(); - - let len = dvc_capabilities_request_pdu_v2.buffer_length(); - - assert_eq!(expected_buf_len, len); -} - -#[test] -fn from_buffer_parsing_for_dvc_caps_response_pdu_with_invalid_caps_version_fails() { - let buffer_with_invalid_caps_version = vec![0x00, 0x01, 0x01]; - match CapabilitiesResponsePdu::from_buffer(buffer_with_invalid_caps_version.as_slice()) { - Err(ChannelError::InvalidDvcCapabilitiesVersion) => (), - res => panic!("Expected InvalidDvcCapabilitiesVersion error, got: {res:?}"), - }; -} - -#[test] -fn from_buffer_correct_parses_dvc_capabilities_response() { - assert_eq!( - DVC_CAPABILITIES_RESPONSE.clone(), - CapabilitiesResponsePdu::from_buffer(&DVC_CAPABILITIES_RESPONSE_BUFFER[1..]).unwrap(), - ); -} - -#[test] -fn to_buffer_correct_serializes_dvc_capabilities_response() { - let capabilities_response = DVC_CAPABILITIES_RESPONSE.clone(); - - let mut buffer = Vec::new(); - capabilities_response.to_buffer(&mut buffer).unwrap(); - - assert_eq!(DVC_CAPABILITIES_RESPONSE_BUFFER.as_ref(), buffer.as_slice()); -} - -#[test] -fn buffer_length_is_correct_for_dvc_capabilities_response() { - let capabilities_response = DVC_CAPABILITIES_RESPONSE.clone(); - let expected_buf_len = DVC_CAPABILITIES_RESPONSE_BUFFER.len(); - - let len = capabilities_response.buffer_length(); - - assert_eq!(expected_buf_len, len); -} From 69cc89bc54ad5586fab3618a9c3a7a8a70c86d5a Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 19 Mar 2024 19:12:32 -0700 Subject: [PATCH 23/84] Fix test panics --- crates/ironrdp-dvc/src/pdu/tests/capabilities.rs | 6 +++--- crates/ironrdp-dvc/src/pdu/tests/close.rs | 4 ++-- crates/ironrdp-dvc/src/pdu/tests/create.rs | 4 ++-- crates/ironrdp-dvc/src/pdu/tests/data.rs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs b/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs index bbda43ffa..b511f31ff 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs @@ -19,7 +19,7 @@ fn decodes_request_v1() { let mut src = ReadCursor::new(&REQ_V1_ENCODED); match DrdynvcServerPdu::decode(&mut src).unwrap() { DrdynvcServerPdu::Capabilities(pdu) => assert_eq!(*REQ_V1_DECODED, pdu), - _ => panic!("Expected DataFirst"), + _ => panic!("Expected Capabilities"), } } @@ -37,7 +37,7 @@ fn decodes_request_v2() { let mut src = ReadCursor::new(&REQ_V2_ENCODED); match DrdynvcServerPdu::decode(&mut src).unwrap() { DrdynvcServerPdu::Capabilities(pdu) => assert_eq!(*REQ_V2_DECODED, pdu), - _ => panic!("Expected DataFirst"), + _ => panic!("Expected Capabilities"), } } @@ -55,7 +55,7 @@ fn decodes_response_v1() { let mut src = ReadCursor::new(&RESP_V1_ENCODED); match DrdynvcClientPdu::decode(&mut src).unwrap() { DrdynvcClientPdu::Capabilities(pdu) => assert_eq!(*RESP_V1_DECODED, pdu), - _ => panic!("Expected DataFirst"), + _ => panic!("Expected Capabilities"), } } diff --git a/crates/ironrdp-dvc/src/pdu/tests/close.rs b/crates/ironrdp-dvc/src/pdu/tests/close.rs index 8f9accc9e..4ae5efc9d 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/close.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/close.rs @@ -19,13 +19,13 @@ fn decodes_close() { let mut src = ReadCursor::new(&ENCODED); match DrdynvcClientPdu::decode(&mut src).unwrap() { DrdynvcClientPdu::Close(pdu) => assert_eq!(*DECODED, pdu), - _ => panic!("Expected DataFirst"), + _ => panic!("Expected Close"), } let mut src = ReadCursor::new(&ENCODED); match DrdynvcServerPdu::decode(&mut src).unwrap() { DrdynvcServerPdu::Close(pdu) => assert_eq!(*DECODED, pdu), - _ => panic!("Expected DataFirst"), + _ => panic!("Expected Close"), } } diff --git a/crates/ironrdp-dvc/src/pdu/tests/create.rs b/crates/ironrdp-dvc/src/pdu/tests/create.rs index 382c84544..6a7313ca0 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/create.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/create.rs @@ -16,7 +16,7 @@ fn decodes_create_request() { let mut src = ReadCursor::new(&REQ_ENCODED); match DrdynvcServerPdu::decode(&mut src).unwrap() { DrdynvcServerPdu::Create(pdu) => assert_eq!(*REQ_DECODED, pdu), - _ => panic!("Expected DataFirst"), + _ => panic!("Expected Create"), } } @@ -34,7 +34,7 @@ fn decodes_create_response() { let mut src = ReadCursor::new(&RESP_ENCODED); match DrdynvcClientPdu::decode(&mut src).unwrap() { DrdynvcClientPdu::Create(pdu) => assert_eq!(*RESP_DECODED, pdu), - _ => panic!("Expected DataFirst"), + _ => panic!("Expected Create"), } } diff --git a/crates/ironrdp-dvc/src/pdu/tests/data.rs b/crates/ironrdp-dvc/src/pdu/tests/data.rs index 844309405..95fc9461b 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/data.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/data.rs @@ -20,13 +20,13 @@ fn decodes_data() { let mut src = ReadCursor::new(&ENCODED); match DrdynvcClientPdu::decode(&mut src).unwrap() { DrdynvcClientPdu::Data(DrdynvcDataPdu::Data(d)) => assert_eq!(*DECODED, d), - _ => panic!("Expected DataFirst"), + _ => panic!("Expected Data"), } let mut src = ReadCursor::new(&ENCODED); match DrdynvcServerPdu::decode(&mut src).unwrap() { DrdynvcServerPdu::Data(DrdynvcDataPdu::Data(d)) => assert_eq!(*DECODED, d), - _ => panic!("Expected DataFirst"), + _ => panic!("Expected Data"), } } From ead24a5f96bd9e8a402faf7fa227b0fdd406ddd9 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 19 Mar 2024 19:38:12 -0700 Subject: [PATCH 24/84] Delete as much legacy code as possible --- crates/ironrdp-dvc/src/client.rs | 2 +- crates/ironrdp-dvc/src/complete_data.rs | 2 +- crates/ironrdp-dvc/src/display.rs | 4 +- crates/ironrdp-pdu/src/rdp/vc/dvc.rs | 279 ------------------ .../src/rdp/vc/dvc/capabilities.rs | 130 -------- crates/ironrdp-pdu/src/rdp/vc/dvc/close.rs | 39 --- crates/ironrdp-pdu/src/rdp/vc/dvc/create.rs | 103 ------- crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs | 60 ---- .../ironrdp-pdu/src/rdp/vc/dvc/data_first.rs | 78 ----- crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs | 4 + crates/ironrdp-pdu/src/rdp/vc/dvc/tests.rs | 154 ---------- crates/ironrdp-session/src/legacy.rs | 84 +----- crates/ironrdp-session/src/x224/display.rs | 14 - 13 files changed, 10 insertions(+), 943 deletions(-) delete mode 100644 crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities.rs delete mode 100644 crates/ironrdp-pdu/src/rdp/vc/dvc/close.rs delete mode 100644 crates/ironrdp-pdu/src/rdp/vc/dvc/create.rs delete mode 100644 crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs delete mode 100644 crates/ironrdp-pdu/src/rdp/vc/dvc/data_first.rs delete mode 100644 crates/ironrdp-pdu/src/rdp/vc/dvc/tests.rs delete mode 100644 crates/ironrdp-session/src/x224/display.rs diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 61710b722..8d812b8af 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -18,7 +18,7 @@ use pdu::cursor::{ReadCursor, WriteCursor}; use pdu::gcc::ChannelName; use pdu::rdp::vc; use pdu::PduDecode as _; -use pdu::{dvc, PduResult}; +use pdu::PduResult; use pdu::{other_err, PduEncode}; pub trait DvcClientProcessor: DvcProcessor {} diff --git a/crates/ironrdp-dvc/src/complete_data.rs b/crates/ironrdp-dvc/src/complete_data.rs index 2c9c60a92..9b63a1f8f 100644 --- a/crates/ironrdp-dvc/src/complete_data.rs +++ b/crates/ironrdp-dvc/src/complete_data.rs @@ -1,6 +1,6 @@ use alloc::vec::Vec; use core::cmp; -use ironrdp_pdu::{cast_length, dvc, invalid_message_err, PduResult}; +use ironrdp_pdu::{cast_length, invalid_message_err, PduResult}; use crate::pdu::{DataFirstPdu, DataPdu, DrdynvcDataPdu}; diff --git a/crates/ironrdp-dvc/src/display.rs b/crates/ironrdp-dvc/src/display.rs index 146d8231d..30991d387 100644 --- a/crates/ironrdp-dvc/src/display.rs +++ b/crates/ironrdp-dvc/src/display.rs @@ -22,6 +22,8 @@ use ironrdp_pdu::PduEncode; use ironrdp_pdu::PduParsing; use ironrdp_svc::impl_as_any; +pub const CHANNEL_NAME: &str = "Microsoft::Windows::RDS::DisplayControl"; + /// A client for the Display Control Virtual Channel. pub struct DisplayControlClient {} @@ -29,7 +31,7 @@ impl_as_any!(DisplayControlClient); impl DvcProcessor for DisplayControlClient { fn channel_name(&self) -> &str { - "Microsoft::Windows::RDS::DisplayControl" + CHANNEL_NAME } fn start(&mut self, _channel_id: u32) -> PduResult { diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs index a387490fb..1fd69f4d6 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs @@ -1,281 +1,2 @@ -use std::{io, mem}; - -use bit_field::BitField; -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use num_derive::{FromPrimitive, ToPrimitive}; -use num_traits::{FromPrimitive, ToPrimitive}; - -use super::ChannelError; -use crate::PduParsing; - -#[cfg(test)] -mod tests; - pub mod display; pub mod gfx; - -mod capabilities; -mod close; -mod create; -mod data; -mod data_first; - -pub use self::capabilities::{CapabilitiesRequestPdu, CapabilitiesResponsePdu, CapsVersion}; -pub use self::close::ClosePdu; -pub use self::create::{CreateRequestPdu, CreateResponsePdu, DVC_CREATION_STATUS_NO_LISTENER, DVC_CREATION_STATUS_OK}; -pub use self::data::DataPdu; -pub use self::data_first::DataFirstPdu; - -const HEADER_SIZE: usize = 1; -const PDU_WITH_DATA_MAX_SIZE: usize = 1600; - -const UNUSED_U8: u8 = 0; - -#[repr(u8)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] -pub enum PduType { - Create = 0x01, - DataFirst = 0x02, - Data = 0x03, - Close = 0x04, - Capabilities = 0x05, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ServerPdu { - CapabilitiesRequest(CapabilitiesRequestPdu), - CreateRequest(CreateRequestPdu), - DataFirst(DataFirstPdu), - Data(DataPdu), - CloseRequest(ClosePdu), -} - -impl ServerPdu { - pub fn from_buffer(mut stream: impl io::Read, mut dvc_data_size: usize) -> Result { - let dvc_header = Header::from_buffer(&mut stream)?; - let channel_id_type = - FieldType::from_u8(dvc_header.channel_id_type).ok_or(ChannelError::InvalidDVChannelIdLength)?; - - dvc_data_size -= HEADER_SIZE; - - match dvc_header.pdu_type { - PduType::Capabilities => Ok(ServerPdu::CapabilitiesRequest(CapabilitiesRequestPdu::from_buffer( - &mut stream, - )?)), - PduType::Create => Ok(ServerPdu::CreateRequest(CreateRequestPdu::from_buffer( - &mut stream, - channel_id_type, - dvc_data_size, - )?)), - PduType::DataFirst => { - let data_length_type = - FieldType::from_u8(dvc_header.pdu_dependent).ok_or(ChannelError::InvalidDvcDataLength)?; - - Ok(ServerPdu::DataFirst(DataFirstPdu::from_buffer( - &mut stream, - channel_id_type, - data_length_type, - dvc_data_size, - )?)) - } - PduType::Data => Ok(ServerPdu::Data(DataPdu::from_buffer( - &mut stream, - channel_id_type, - dvc_data_size, - )?)), - PduType::Close => Ok(ServerPdu::CloseRequest(ClosePdu::from_buffer( - &mut stream, - channel_id_type, - )?)), - } - } - - pub fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), ChannelError> { - match self { - ServerPdu::CapabilitiesRequest(caps_request) => caps_request.to_buffer(&mut stream)?, - ServerPdu::CreateRequest(create_request) => create_request.to_buffer(&mut stream)?, - ServerPdu::DataFirst(data_first) => data_first.to_buffer(&mut stream)?, - ServerPdu::Data(data) => data.to_buffer(&mut stream)?, - ServerPdu::CloseRequest(close_request) => close_request.to_buffer(&mut stream)?, - }; - - Ok(()) - } - - pub fn buffer_length(&self) -> usize { - match self { - ServerPdu::CapabilitiesRequest(caps_request) => caps_request.buffer_length(), - ServerPdu::CreateRequest(create_request) => create_request.buffer_length(), - ServerPdu::DataFirst(data_first) => data_first.buffer_length(), - ServerPdu::Data(data) => data.buffer_length(), - ServerPdu::CloseRequest(close_request) => close_request.buffer_length(), - } - } - - pub fn as_short_name(&self) -> &str { - match self { - ServerPdu::CapabilitiesRequest(_) => "Capabilities Request PDU", - ServerPdu::CreateRequest(_) => "Create Request PDU", - ServerPdu::DataFirst(_) => "Data First PDU", - ServerPdu::Data(_) => "Data PDU", - ServerPdu::CloseRequest(_) => "Close Request PDU", - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ClientPdu { - CapabilitiesResponse(CapabilitiesResponsePdu), - CreateResponse(CreateResponsePdu), - DataFirst(DataFirstPdu), - Data(DataPdu), - CloseResponse(ClosePdu), -} - -impl ClientPdu { - pub fn from_buffer(mut stream: impl io::Read, mut dvc_data_size: usize) -> Result { - let dvc_header = Header::from_buffer(&mut stream)?; - let channel_id_type = - FieldType::from_u8(dvc_header.channel_id_type).ok_or(ChannelError::InvalidDVChannelIdLength)?; - - dvc_data_size -= HEADER_SIZE; - - match dvc_header.pdu_type { - PduType::Capabilities => Ok(ClientPdu::CapabilitiesResponse(CapabilitiesResponsePdu::from_buffer( - &mut stream, - )?)), - PduType::Create => Ok(ClientPdu::CreateResponse(CreateResponsePdu::from_buffer( - &mut stream, - channel_id_type, - )?)), - PduType::DataFirst => { - let data_length_type = - FieldType::from_u8(dvc_header.pdu_dependent).ok_or(ChannelError::InvalidDvcDataLength)?; - - Ok(ClientPdu::DataFirst(DataFirstPdu::from_buffer( - &mut stream, - channel_id_type, - data_length_type, - dvc_data_size, - )?)) - } - PduType::Data => Ok(ClientPdu::Data(DataPdu::from_buffer( - &mut stream, - channel_id_type, - dvc_data_size, - )?)), - PduType::Close => Ok(ClientPdu::CloseResponse(ClosePdu::from_buffer( - &mut stream, - channel_id_type, - )?)), - } - } - - pub fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), ChannelError> { - match self { - ClientPdu::CapabilitiesResponse(caps_request) => caps_request.to_buffer(&mut stream)?, - ClientPdu::CreateResponse(create_request) => create_request.to_buffer(&mut stream)?, - ClientPdu::DataFirst(data_first) => data_first.to_buffer(&mut stream)?, - ClientPdu::Data(data) => data.to_buffer(&mut stream)?, - ClientPdu::CloseResponse(close_response) => close_response.to_buffer(&mut stream)?, - }; - - Ok(()) - } - - pub fn buffer_length(&self) -> usize { - match self { - ClientPdu::CapabilitiesResponse(caps_request) => caps_request.buffer_length(), - ClientPdu::CreateResponse(create_request) => create_request.buffer_length(), - ClientPdu::DataFirst(data_first) => data_first.buffer_length(), - ClientPdu::Data(data) => data.buffer_length(), - ClientPdu::CloseResponse(close_response) => close_response.buffer_length(), - } - } - - pub fn as_short_name(&self) -> &str { - match self { - ClientPdu::CapabilitiesResponse(_) => "Capabilities Response PDU", - ClientPdu::CreateResponse(_) => "Create Response PDU", - ClientPdu::DataFirst(_) => "Data First PDU", - ClientPdu::Data(_) => "Data PDU", - ClientPdu::CloseResponse(_) => "Close Response PDU", - } - } -} - -#[repr(u8)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] -pub enum FieldType { - U8 = 0x00, - U16 = 0x01, - U32 = 0x02, -} - -impl FieldType { - pub fn read_buffer_according_to_type(self, mut stream: impl io::Read) -> Result { - let value = match self { - FieldType::U8 => u32::from(stream.read_u8()?), - FieldType::U16 => u32::from(stream.read_u16::()?), - FieldType::U32 => stream.read_u32::()?, - }; - - Ok(value) - } - - pub fn to_buffer_according_to_type(self, mut stream: impl io::Write, value: u32) -> Result<(), io::Error> { - match self { - FieldType::U8 => stream.write_u8(value as u8)?, - FieldType::U16 => stream.write_u16::(value as u16)?, - FieldType::U32 => stream.write_u32::(value)?, - }; - - Ok(()) - } - - pub fn get_type_size(self) -> usize { - match self { - FieldType::U8 => mem::size_of::(), - FieldType::U16 => mem::size_of::(), - FieldType::U32 => mem::size_of::(), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -struct Header { - channel_id_type: u8, // 2 bit - pdu_dependent: u8, // 2 bit - pdu_type: PduType, // 4 bit -} - -impl PduParsing for Header { - type Error = ChannelError; - - fn from_buffer(mut stream: impl io::Read) -> Result { - let dvc_header = stream.read_u8()?; - let channel_id_type = dvc_header.get_bits(0..2); - let pdu_dependent = dvc_header.get_bits(2..4); - let pdu_type = PduType::from_u8(dvc_header.get_bits(4..8)).ok_or(ChannelError::InvalidDvcPduType)?; - - Ok(Self { - channel_id_type, - pdu_dependent, - pdu_type, - }) - } - - fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), Self::Error> { - let mut dvc_header: u8 = 0; - dvc_header.set_bits(0..2, self.channel_id_type); - dvc_header.set_bits(2..4, self.pdu_dependent); - dvc_header.set_bits(4..8, self.pdu_type.to_u8().unwrap()); - stream.write_u8(dvc_header)?; - - Ok(()) - } - - fn buffer_length(&self) -> usize { - HEADER_SIZE - } -} diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities.rs deleted file mode 100644 index 8104ed285..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::io; - -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use num_derive::{FromPrimitive, ToPrimitive}; -use num_traits::{FromPrimitive, ToPrimitive}; - -use super::{Header, PduType, HEADER_SIZE, UNUSED_U8}; -use crate::rdp::vc::ChannelError; -use crate::PduParsing; - -const DVC_CAPABILITIES_PAD_SIZE: usize = 1; -const DVC_CAPABILITIES_VERSION_SIZE: usize = 2; -const DVC_CAPABILITIES_CHARGE_SIZE: usize = 2; -const DVC_CAPABILITIES_CHARGE_COUNT: usize = 4; - -#[repr(u16)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] -pub enum CapsVersion { - V1 = 0x0001, - V2 = 0x0002, - V3 = 0x0003, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum CapabilitiesRequestPdu { - V1, - V2 { - charges: [u16; DVC_CAPABILITIES_CHARGE_COUNT], - }, - V3 { - charges: [u16; DVC_CAPABILITIES_CHARGE_COUNT], - }, -} - -impl PduParsing for CapabilitiesRequestPdu { - type Error = ChannelError; - - fn from_buffer(mut stream: impl io::Read) -> Result { - let _pad = stream.read_u8()?; - let version = CapsVersion::from_u16(stream.read_u16::()?) - .ok_or(ChannelError::InvalidDvcCapabilitiesVersion)?; - - match version { - CapsVersion::V1 => Ok(Self::V1), - CapsVersion::V2 => { - let mut charges = [0; DVC_CAPABILITIES_CHARGE_COUNT]; - stream.read_u16_into::(&mut charges)?; - Ok(Self::V2 { charges }) - } - CapsVersion::V3 => { - let mut charges = [0; DVC_CAPABILITIES_CHARGE_COUNT]; - stream.read_u16_into::(&mut charges)?; - Ok(Self::V3 { charges }) - } - } - } - - fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), Self::Error> { - let dvc_header = Header { - channel_id_type: UNUSED_U8, - pdu_dependent: UNUSED_U8, - pdu_type: PduType::Capabilities, - }; - dvc_header.to_buffer(&mut stream)?; - stream.write_u8(UNUSED_U8)?; - - match self { - CapabilitiesRequestPdu::V1 => stream.write_u16::(CapsVersion::V1.to_u16().unwrap())?, - CapabilitiesRequestPdu::V2 { charges } => { - stream.write_u16::(CapsVersion::V2.to_u16().unwrap())?; - for charge in charges.iter() { - stream.write_u16::(*charge)?; - } - } - CapabilitiesRequestPdu::V3 { charges } => { - stream.write_u16::(CapsVersion::V3.to_u16().unwrap())?; - for charge in charges.iter() { - stream.write_u16::(*charge)?; - } - } - } - - Ok(()) - } - - fn buffer_length(&self) -> usize { - let charges_length = match self { - CapabilitiesRequestPdu::V1 => 0, - CapabilitiesRequestPdu::V2 { charges } | CapabilitiesRequestPdu::V3 { charges } => { - charges.len() * DVC_CAPABILITIES_CHARGE_SIZE - } - }; - - HEADER_SIZE + DVC_CAPABILITIES_PAD_SIZE + DVC_CAPABILITIES_VERSION_SIZE + charges_length - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CapabilitiesResponsePdu { - pub version: CapsVersion, -} - -impl PduParsing for CapabilitiesResponsePdu { - type Error = ChannelError; - - fn from_buffer(mut stream: impl io::Read) -> Result { - let _pad = stream.read_u8()?; - let version = CapsVersion::from_u16(stream.read_u16::()?) - .ok_or(ChannelError::InvalidDvcCapabilitiesVersion)?; - - Ok(Self { version }) - } - - fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), Self::Error> { - let dvc_header = Header { - channel_id_type: UNUSED_U8, - pdu_dependent: UNUSED_U8, - pdu_type: PduType::Capabilities, - }; - dvc_header.to_buffer(&mut stream)?; - stream.write_u8(UNUSED_U8)?; - stream.write_u16::(self.version.to_u16().unwrap())?; - - Ok(()) - } - - fn buffer_length(&self) -> usize { - HEADER_SIZE + DVC_CAPABILITIES_PAD_SIZE + DVC_CAPABILITIES_VERSION_SIZE - } -} diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/close.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/close.rs deleted file mode 100644 index 11fad4095..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/close.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::io; - -use super::{FieldType, Header, PduType, HEADER_SIZE, UNUSED_U8}; -use crate::rdp::vc::ChannelError; -use crate::PduParsing; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ClosePdu { - pub channel_id_type: FieldType, - pub channel_id: u32, -} - -impl ClosePdu { - pub fn from_buffer(mut stream: impl io::Read, channel_id_type: FieldType) -> Result { - let channel_id = channel_id_type.read_buffer_according_to_type(&mut stream)?; - - Ok(Self { - channel_id_type, - channel_id, - }) - } - - pub fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), ChannelError> { - let dvc_header = Header { - channel_id_type: self.channel_id_type as u8, - pdu_dependent: UNUSED_U8, - pdu_type: PduType::Close, - }; - dvc_header.to_buffer(&mut stream)?; - self.channel_id_type - .to_buffer_according_to_type(&mut stream, self.channel_id)?; - - Ok(()) - } - - pub fn buffer_length(&self) -> usize { - HEADER_SIZE + self.channel_id_type.get_type_size() - } -} diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/create.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/create.rs deleted file mode 100644 index b0ed9514e..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/create.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::io; - -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; - -use super::{FieldType, Header, PduType, HEADER_SIZE, UNUSED_U8}; -use crate::rdp::vc::ChannelError; -use crate::{utils, PduParsing}; - -pub const DVC_CREATION_STATUS_OK: u32 = 0x0000_0000; -pub const DVC_CREATION_STATUS_NO_LISTENER: u32 = 0xC000_0001; - -const DVC_CREATION_STATUS_SIZE: usize = 4; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CreateRequestPdu { - pub channel_id_type: FieldType, - pub channel_id: u32, - pub channel_name: String, -} - -impl CreateRequestPdu { - pub fn new(channel_id: u32, channel_name: String) -> Self { - Self { - channel_id_type: FieldType::U32, - channel_id, - channel_name, - } - } - - pub fn from_buffer( - mut stream: impl io::Read, - channel_id_type: FieldType, - mut data_size: usize, - ) -> Result { - let channel_id = channel_id_type.read_buffer_according_to_type(&mut stream)?; - - data_size -= channel_id_type.get_type_size(); - let channel_name = utils::read_string_from_stream(&mut stream, data_size, utils::CharacterSet::Ansi, false)?; - - Ok(Self { - channel_id_type, - channel_id, - channel_name, - }) - } - - pub fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), ChannelError> { - let dvc_header = Header { - channel_id_type: self.channel_id_type as u8, - pdu_dependent: UNUSED_U8, // because DYNVC_CAPS_VERSION1 - pdu_type: PduType::Create, - }; - dvc_header.to_buffer(&mut stream)?; - self.channel_id_type - .to_buffer_according_to_type(&mut stream, self.channel_id)?; - stream.write_all(self.channel_name.as_ref())?; - stream.write_all(b"\0")?; - - Ok(()) - } - - pub fn buffer_length(&self) -> usize { - HEADER_SIZE + self.channel_id_type.get_type_size() + self.channel_name.len() + "\0".len() - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CreateResponsePdu { - pub channel_id_type: FieldType, - pub channel_id: u32, - pub creation_status: u32, -} - -impl CreateResponsePdu { - pub fn from_buffer(mut stream: impl io::Read, channel_id_type: FieldType) -> Result { - let channel_id = channel_id_type.read_buffer_according_to_type(&mut stream)?; - let creation_status = stream.read_u32::()?; - - Ok(Self { - channel_id_type, - channel_id, - creation_status, - }) - } - - pub fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), ChannelError> { - let dvc_header = Header { - channel_id_type: self.channel_id_type as u8, - pdu_dependent: UNUSED_U8, - pdu_type: PduType::Create, - }; - dvc_header.to_buffer(&mut stream)?; - self.channel_id_type - .to_buffer_according_to_type(&mut stream, self.channel_id)?; - stream.write_u32::(self.creation_status)?; - - Ok(()) - } - - pub fn buffer_length(&self) -> usize { - HEADER_SIZE + self.channel_id_type.get_type_size() + DVC_CREATION_STATUS_SIZE - } -} diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs deleted file mode 100644 index b813dae9c..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::io; - -use super::{FieldType, Header, PduType, HEADER_SIZE, PDU_WITH_DATA_MAX_SIZE, UNUSED_U8}; -use crate::rdp::vc::ChannelError; -use crate::PduParsing; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DataPdu { - pub channel_id_type: FieldType, - pub channel_id: u32, - pub data_size: usize, -} - -impl DataPdu { - pub fn new(channel_id: u32, data_size: usize) -> Self { - Self { - channel_id_type: FieldType::U8, - channel_id, - data_size, - } - } - - pub fn from_buffer( - mut stream: impl io::Read, - channel_id_type: FieldType, - mut data_size: usize, - ) -> Result { - let channel_id = channel_id_type.read_buffer_according_to_type(&mut stream)?; - data_size -= channel_id_type.get_type_size(); - - let expected_max_data_size = PDU_WITH_DATA_MAX_SIZE - (HEADER_SIZE + channel_id_type.get_type_size()); - - if data_size > expected_max_data_size { - Err(ChannelError::InvalidDvcMessageSize) - } else { - Ok(Self { - channel_id_type, - channel_id, - data_size, - }) - } - } - - pub fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), ChannelError> { - let dvc_header = Header { - channel_id_type: self.channel_id_type as u8, - pdu_dependent: UNUSED_U8, - pdu_type: PduType::Data, - }; - dvc_header.to_buffer(&mut stream)?; - self.channel_id_type - .to_buffer_according_to_type(&mut stream, self.channel_id)?; - - Ok(()) - } - - pub fn buffer_length(&self) -> usize { - HEADER_SIZE + self.channel_id_type.get_type_size() - } -} diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first.rs deleted file mode 100644 index 6f656d538..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::io; - -use super::{FieldType, Header, PduType, HEADER_SIZE, PDU_WITH_DATA_MAX_SIZE}; -use crate::rdp::vc::ChannelError; -use crate::PduParsing; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DataFirstPdu { - pub channel_id_type: FieldType, - pub channel_id: u32, - pub total_data_size_type: FieldType, - pub total_data_size: u32, - pub data_size: usize, -} - -impl DataFirstPdu { - pub fn new(channel_id: u32, total_data_size: u32, data_size: usize) -> Self { - Self { - channel_id_type: FieldType::U32, - channel_id, - total_data_size_type: FieldType::U32, - total_data_size, - data_size, - } - } - - pub fn from_buffer( - mut stream: impl io::Read, - channel_id_type: FieldType, - total_data_size_type: FieldType, - mut data_size: usize, - ) -> Result { - let channel_id = channel_id_type.read_buffer_according_to_type(&mut stream)?; - let total_data_size = total_data_size_type.read_buffer_according_to_type(&mut stream)?; - - data_size -= channel_id_type.get_type_size() + total_data_size_type.get_type_size(); - if data_size > total_data_size as usize { - return Err(ChannelError::InvalidDvcTotalMessageSize { - actual: data_size, - expected: total_data_size as usize, - }); - } - - let expected_max_data_size = PDU_WITH_DATA_MAX_SIZE - - (HEADER_SIZE + channel_id_type.get_type_size() + total_data_size_type.get_type_size()); - - if data_size > expected_max_data_size { - Err(ChannelError::InvalidDvcMessageSize) - } else { - Ok(Self { - channel_id_type, - channel_id, - total_data_size_type, - total_data_size, - data_size, - }) - } - } - - pub fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), ChannelError> { - let dvc_header = Header { - channel_id_type: self.channel_id_type as u8, - pdu_dependent: self.total_data_size_type as u8, - pdu_type: PduType::DataFirst, - }; - dvc_header.to_buffer(&mut stream)?; - self.channel_id_type - .to_buffer_according_to_type(&mut stream, self.channel_id)?; - self.total_data_size_type - .to_buffer_according_to_type(&mut stream, self.total_data_size)?; - - Ok(()) - } - - pub fn buffer_length(&self) -> usize { - HEADER_SIZE + self.channel_id_type.get_type_size() + self.total_data_size_type.get_type_size() - } -} diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs index 2ddd04070..afe6b45b6 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs @@ -63,6 +63,8 @@ pub enum Orientation { /// [2.2.2.2.1] DISPLAYCONTROL_MONITOR_LAYOUT_PDU /// +/// Deprecated in favor of the struct by the same name in crates/ironrdp-dvc. +/// /// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c #[derive(Debug, Clone, PartialEq, Eq)] pub struct Monitor { @@ -134,6 +136,8 @@ impl PduParsing for Monitor { /// [2.2.2.2] DISPLAYCONTROL_MONITOR_LAYOUT_PDU /// +/// Deprecated in favor of the struct by the same name in crates/ironrdp-dvc. +/// /// [2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/22741217-12a0-4fb8-b5a0-df43905aaf06 #[derive(Debug, Clone, PartialEq, Eq)] pub struct MonitorLayoutPdu { diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/tests.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/tests.rs deleted file mode 100644 index 93a29ff06..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/tests.rs +++ /dev/null @@ -1,154 +0,0 @@ -use lazy_static::lazy_static; - -use super::*; - -const DVC_HEADER_BUFFER: [u8; HEADER_SIZE] = [0x11]; -const DVC_HEADER_WITH_INVALID_ID_LENGTH_TYPE_BUFFER: [u8; HEADER_SIZE] = [0x13]; - -const TEST_BUFFER_SIZE: usize = 4; -const TEST_BUFFER: [u8; TEST_BUFFER_SIZE] = [0x01, 0x02, 0x03, 0x04]; - -lazy_static! { - static ref DYNAMIC_CHANNEL_HEADER: Header = Header { - channel_id_type: FieldType::U16.to_u8().unwrap(), - pdu_dependent: 0, - pdu_type: PduType::Create, - }; -} - -#[test] -fn from_buffer_parsing_for_dvc_header_with_invalid_pdu_type_fails() { - let invalid_header: u8 = 0xA0; - match Header::from_buffer([invalid_header].as_ref()) { - Err(ChannelError::InvalidDvcPduType) => (), - res => panic!("Expected InvalidDvcPduType error, got: {res:?}"), - }; -} - -#[test] -fn from_buffer_correct_parses_dvc_header() { - assert_eq!( - DYNAMIC_CHANNEL_HEADER.clone(), - Header::from_buffer(DVC_HEADER_BUFFER.as_ref()).unwrap(), - ); -} - -#[test] -fn to_buffer_correct_serializes_dvc_header() { - let channel_header = DYNAMIC_CHANNEL_HEADER.clone(); - - let mut buffer = Vec::new(); - channel_header.to_buffer(&mut buffer).unwrap(); - - assert_eq!(DVC_HEADER_BUFFER.as_ref(), buffer.as_slice()); -} - -#[test] -fn buffer_length_is_correct_for_dvc_header() { - let channel_header = DYNAMIC_CHANNEL_HEADER.clone(); - let expected_buf_len = DVC_HEADER_BUFFER.len(); - - let len = channel_header.buffer_length(); - - assert_eq!(expected_buf_len, len); -} - -#[test] -fn from_buffer_parsing_for_client_dvc_pdu_with_invalid_id_length_type_fails() { - match ClientPdu::from_buffer(DVC_HEADER_WITH_INVALID_ID_LENGTH_TYPE_BUFFER.as_ref(), HEADER_SIZE) { - Err(ChannelError::InvalidDVChannelIdLength) => (), - res => panic!("Expected InvalidDVChannelIdLength error, got: {res:?}"), - }; -} - -#[test] -fn from_buffer_parsing_for_server_dvc_pdu_with_invalid_id_length_type_fails() { - match ServerPdu::from_buffer(DVC_HEADER_WITH_INVALID_ID_LENGTH_TYPE_BUFFER.as_ref(), HEADER_SIZE) { - Err(ChannelError::InvalidDVChannelIdLength) => (), - res => panic!("Expected InvalidDVChannelIdLength error, got: {res:?}"), - }; -} - -#[test] -fn from_buffer_according_to_type_u8_test() { - let channel_id = FieldType::U8 - .read_buffer_according_to_type(TEST_BUFFER.as_ref()) - .unwrap(); - let expected_channel_id = 0x01; - - assert_eq!(expected_channel_id, channel_id); -} - -#[test] -fn from_buffer_according_to_type_u16_test() { - let channel_id = FieldType::U16 - .read_buffer_according_to_type(TEST_BUFFER.as_ref()) - .unwrap(); - let expected_channel_id = 0x0201; - - assert_eq!(expected_channel_id, channel_id); -} - -#[test] -fn from_buffer_according_to_type_u32_test() { - let channel_id = FieldType::U32 - .read_buffer_according_to_type(TEST_BUFFER.as_ref()) - .unwrap(); - let expected_channel_id = 0x0403_0201; - - assert_eq!(expected_channel_id, channel_id); -} - -#[test] -fn to_buffer_according_to_type_u8_test() { - let channel_id = 0x01; - let mut buffer = Vec::new(); - FieldType::U8 - .to_buffer_according_to_type(&mut buffer, channel_id) - .unwrap(); - - let expected_buffer = vec![0x01]; - assert_eq!(expected_buffer, buffer); -} - -#[test] -fn to_buffer_according_to_type_u16_test() { - let channel_id = 0x0201; - let mut buffer = Vec::new(); - FieldType::U16 - .to_buffer_according_to_type(&mut buffer, channel_id) - .unwrap(); - - let expected_buffer = vec![0x01, 0x02]; - assert_eq!(expected_buffer, buffer); -} - -#[test] -fn to_buffer_according_to_type_u32_test() { - let channel_id = 0x0403_0201; - let mut buffer = Vec::new(); - FieldType::U32 - .to_buffer_according_to_type(&mut buffer, channel_id) - .unwrap(); - - let expected_buffer = vec![0x01, 0x02, 0x03, 0x04]; - assert_eq!(expected_buffer, buffer); -} - -#[test] -fn get_length_according_to_type_u8_test() { - let length = FieldType::U8.get_type_size(); - assert_eq!(mem::size_of::(), length); -} - -#[test] -fn get_length_according_to_type_u16_test() { - let length = FieldType::U16.get_type_size(); - assert_eq!(mem::size_of::(), length); -} - -#[test] -fn get_length_according_to_type_u32_test() { - let length = FieldType::U32.get_type_size(); - assert_eq!(mem::size_of::(), length); -} diff --git a/crates/ironrdp-session/src/legacy.rs b/crates/ironrdp-session/src/legacy.rs index fba1d0971..9d4ab5899 100644 --- a/crates/ironrdp-session/src/legacy.rs +++ b/crates/ironrdp-session/src/legacy.rs @@ -1,86 +1,4 @@ -use ironrdp_connector::legacy::{encode_send_data_request, SendDataIndicationCtx}; -use ironrdp_pdu::rdp::vc; -use ironrdp_pdu::rdp::vc::ChannelError; -use ironrdp_pdu::write_buf::WriteBuf; -use ironrdp_pdu::PduParsing; -use std::io::{Read, Write}; - -use crate::{SessionError, SessionResult}; - -pub fn encode_dvc_message( - initiator_id: u16, - drdynvc_id: u16, - dvc_pdu: vc::dvc::ClientPdu, - dvc_data: &[u8], - buf: &mut WriteBuf, -) -> SessionResult<()> { - let dvc_length = dvc_pdu.buffer_length() + dvc_data.len(); - - let channel_header = vc::ChannelPduHeader { - length: u32::try_from(dvc_length).expect("dvc message size"), - flags: vc::ChannelControlFlags::FLAG_FIRST | vc::ChannelControlFlags::FLAG_LAST, - }; - - let dvc_message = DvcMessage { - channel_header, - dvc_pdu, - dvc_data, - }; - - let previous_length = buf.filled_len(); - // [ TPKT | TPDU | SendDataRequest | vc::ChannelPduHeader | vc::dvc::ClientPdu | DvcData ] - let written = encode_send_data_request(initiator_id, drdynvc_id, &dvc_message, buf).map_err(map_error)?; - debug_assert_eq!(written, buf.filled_len() - previous_length); - - Ok(()) -} - -struct DvcMessage<'a> { - channel_header: vc::ChannelPduHeader, - dvc_pdu: vc::dvc::ClientPdu, - dvc_data: &'a [u8], -} - -impl PduParsing for DvcMessage<'_> { - type Error = ChannelError; - - fn from_buffer(_: impl Read) -> Result - where - Self: Sized, - { - Err(std::io::Error::other("legacy::DvcMessage::from_buffer called - this is a bug").into()) - } - - fn to_buffer(&self, mut _stream: impl Write) -> Result<(), Self::Error> { - Err(std::io::Error::other("legacy::DvcMessage::to_buffer called - this is a bug").into()) - } - - fn buffer_length(&self) -> usize { - self.channel_header.buffer_length() + self.dvc_pdu.buffer_length() + self.dvc_data.len() - } -} - -pub struct DynamicChannelCtx<'a> { - pub dvc_pdu: vc::dvc::ServerPdu, - pub dvc_data: &'a [u8], -} - -pub fn decode_dvc_message(ctx: SendDataIndicationCtx<'_>) -> SessionResult> { - let mut user_data = ctx.user_data; - - // [ vc::ChannelPduHeader | ā€¦ - let channel_header = vc::ChannelPduHeader::from_buffer(&mut user_data)?; - let dvc_data_len = user_data.len(); - debug_assert_eq!(dvc_data_len, channel_header.length as usize); - - // ā€¦ | dvc::ServerPdu | ā€¦ - let dvc_pdu = vc::dvc::ServerPdu::from_buffer(&mut user_data, dvc_data_len)?; - - // ā€¦ | DvcData ] - let dvc_data = user_data; - - Ok(DynamicChannelCtx { dvc_pdu, dvc_data }) -} +use crate::SessionError; // FIXME: code should be fixed so that we never need this conversion // For that, some code from this ironrdp_session::legacy and ironrdp_connector::legacy modules should be moved to ironrdp_pdu itself diff --git a/crates/ironrdp-session/src/x224/display.rs b/crates/ironrdp-session/src/x224/display.rs deleted file mode 100644 index a65243b4e..000000000 --- a/crates/ironrdp-session/src/x224/display.rs +++ /dev/null @@ -1,14 +0,0 @@ -use ironrdp_pdu::dvc::display::ServerPdu; -use ironrdp_pdu::PduParsing; - -use crate::SessionResult; - -pub(crate) struct Handler; - -impl Handler { - fn process_complete_data(&mut self, complete_data: Vec) -> SessionResult>> { - let gfx_pdu = ServerPdu::from_buffer(&mut complete_data.as_slice())?; - debug!("Got Display PDU: {:?}", gfx_pdu); - Ok(None) - } -} From e9cc83a839cb8af7d01d07a9219639e6843e1cbb Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 12:18:43 -0700 Subject: [PATCH 25/84] Creates connection_activation module - Abstracts the CapabilitiesExchange and ConnectionFinalization sequences into a separate connection_activation module. This allows us to reuse them when we receive a Server Deactivate All. - Creates IoChannelPdu enum to be used to account for the potential of receiving a ServerDeactivateAll on the I/O channel --- Cargo.toml | 1 + crates/ironrdp-client/src/rdp.rs | 1 + crates/ironrdp-connector/src/connection.rs | 287 +++------------ .../src/connection_activation.rs | 341 ++++++++++++++++++ .../src/connection_finalization.rs | 4 +- crates/ironrdp-connector/src/legacy.rs | 32 +- crates/ironrdp-connector/src/lib.rs | 1 + crates/ironrdp-graphics/Cargo.toml | 2 +- crates/ironrdp-pdu/Cargo.toml | 2 +- crates/ironrdp-pdu/src/rdp/capability_sets.rs | 9 + crates/ironrdp-pdu/src/rdp/headers.rs | 40 ++ crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs | 6 + crates/ironrdp-session/src/active_stage.rs | 3 + crates/ironrdp-session/src/x224/mod.rs | 108 +++--- crates/ironrdp-web/src/session.rs | 1 + 15 files changed, 543 insertions(+), 295 deletions(-) create mode 100644 crates/ironrdp-connector/src/connection_activation.rs diff --git a/Cargo.toml b/Cargo.toml index e8938c6c4..a79ce9f7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ tracing = { version = "0.1", features = ["log"] } thiserror = "1.0" png = "0.17" bitflags = "2.4" +byteorder = "1.5" [profile.dev] opt-level = 1 diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index a7e5f2774..46f0db754 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -280,6 +280,7 @@ async fn active_session( ActiveStageOutput::PointerBitmap(_) => { // Not applicable, because we use the software cursor rendering. } + ActiveStageOutput::DeactivateAll(_) => todo!("DeactivateAll"), ActiveStageOutput::Terminate(reason) => break 'outer reason, } } diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 907c0efc7..46f53dd89 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -2,22 +2,19 @@ use std::borrow::Cow; use std::mem; use std::net::SocketAddr; -use ironrdp_pdu::rdp::capability_sets::CapabilitySet; use ironrdp_pdu::rdp::client_info::{PerformanceFlags, TimezoneInfo}; use ironrdp_pdu::write_buf::WriteBuf; use ironrdp_pdu::{decode, encode_vec, gcc, mcs, nego, rdp, PduEncode, PduHint}; use ironrdp_svc::{StaticChannelSet, StaticVirtualChannel, SvcClientProcessor}; use crate::channel_connection::{ChannelConnectionSequence, ChannelConnectionState}; -use crate::connection_finalization::ConnectionFinalizationSequence; +use crate::connection_activation::{ConnectionActivationSequence, ConnectionActivationState}; use crate::license_exchange::LicenseExchangeSequence; use crate::{ - encode_x224_packet, legacy, Config, ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize, Sequence, - State, Written, + encode_x224_packet, Config, ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize, Sequence, State, + Written, }; -const DEFAULT_POINTER_CACHE_SIZE: u16 = 32; - #[derive(Debug)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct ConnectionResult { @@ -75,14 +72,10 @@ pub enum ClientConnectorState { user_channel_id: u16, }, CapabilitiesExchange { - io_channel_id: u16, - user_channel_id: u16, + connection_activation: ConnectionActivationSequence, }, ConnectionFinalization { - io_channel_id: u16, - user_channel_id: u16, - desktop_size: DesktopSize, - connection_finalization: ConnectionFinalizationSequence, + connection_activation: ConnectionActivationSequence, }, Connected { result: ConnectionResult, @@ -104,8 +97,12 @@ impl State for ClientConnectorState { Self::ConnectTimeAutoDetection { .. } => "ConnectTimeAutoDetection", Self::LicensingExchange { .. } => "LicensingExchange", Self::MultitransportBootstrapping { .. } => "MultitransportBootstrapping", - Self::CapabilitiesExchange { .. } => "CapabilitiesExchange", - Self::ConnectionFinalization { .. } => "ConnectionFinalization", + Self::CapabilitiesExchange { + connection_activation, .. + } => connection_activation.state().name(), + Self::ConnectionFinalization { + connection_activation, .. + } => connection_activation.state().name(), Self::Connected { .. } => "Connected", } } @@ -203,11 +200,12 @@ impl Sequence for ClientConnector { ClientConnectorState::ConnectTimeAutoDetection { .. } => None, ClientConnectorState::LicensingExchange { license_exchange, .. } => license_exchange.next_pdu_hint(), ClientConnectorState::MultitransportBootstrapping { .. } => None, - ClientConnectorState::CapabilitiesExchange { .. } => Some(&ironrdp_pdu::X224_HINT), + ClientConnectorState::CapabilitiesExchange { + connection_activation, .. + } => connection_activation.next_pdu_hint(), ClientConnectorState::ConnectionFinalization { - connection_finalization, - .. - } => connection_finalization.next_pdu_hint(), + connection_activation, .. + } => connection_activation.next_pdu_hint(), ClientConnectorState::Connected { .. } => None, } } @@ -514,120 +512,57 @@ impl Sequence for ClientConnector { } => ( Written::Nothing, ClientConnectorState::CapabilitiesExchange { - io_channel_id, - user_channel_id, + connection_activation: ConnectionActivationSequence::new( + self.config.clone(), + io_channel_id, + user_channel_id, + ), }, ), //== Capabilities Exchange ==/ // The server sends the set of capabilities it supports to the client. ClientConnectorState::CapabilitiesExchange { - io_channel_id, - user_channel_id, + mut connection_activation, } => { - debug!("Capabilities Exchange"); - - let send_data_indication_ctx = legacy::decode_send_data_indication(input)?; - let share_control_ctx = legacy::decode_share_control(send_data_indication_ctx)?; - - debug!(message = ?share_control_ctx.pdu, "Received"); - - if share_control_ctx.channel_id != io_channel_id { - warn!( - io_channel_id, - share_control_ctx.channel_id, "Unexpected channel ID for received Share Control Pdu" - ); - } - - let capability_sets = if let rdp::headers::ShareControlPdu::ServerDemandActive(server_demand_active) = - share_control_ctx.pdu - { - server_demand_active.pdu.capability_sets - } else { - return Err(general_err!( - "unexpected Share Control Pdu (expected ServerDemandActive)", - )); - }; - - for c in &capability_sets { - if let rdp::capability_sets::CapabilitySet::General(g) = c { - if g.protocol_version != rdp::capability_sets::PROTOCOL_VER { - warn!(version = g.protocol_version, "Unexpected protocol version"); - } - break; - } + let written = connection_activation.step(input, output)?; + match connection_activation.state { + ConnectionActivationState::ConnectionFinalization { .. } => ( + written, + ClientConnectorState::ConnectionFinalization { connection_activation }, + ), + _ => return Err(general_err!("invalid state (this is a bug)")), } - - let desktop_size = capability_sets - .iter() - .find_map(|c| match c { - rdp::capability_sets::CapabilitySet::Bitmap(b) => Some(DesktopSize { - width: b.desktop_width, - height: b.desktop_height, - }), - _ => None, - }) - .unwrap_or(DesktopSize { - width: self.config.desktop_size.width, - height: self.config.desktop_size.height, - }); - - let client_confirm_active = rdp::headers::ShareControlPdu::ClientConfirmActive( - create_client_confirm_active(&self.config, capability_sets), - ); - - debug!(message = ?client_confirm_active, "Send"); - - let written = legacy::encode_share_control( - user_channel_id, - io_channel_id, - share_control_ctx.share_id, - client_confirm_active, - output, - )?; - - ( - Written::from_size(written)?, - ClientConnectorState::ConnectionFinalization { - io_channel_id, - user_channel_id, - desktop_size, - connection_finalization: ConnectionFinalizationSequence::new(io_channel_id, user_channel_id), - }, - ) } //== Connection Finalization ==// // Client and server exchange a few PDUs in order to finalize the connection. // Client may send PDUs one after the other without waiting for a response in order to speed up the process. ClientConnectorState::ConnectionFinalization { - io_channel_id, - user_channel_id, - desktop_size, - mut connection_finalization, + mut connection_activation, } => { - debug!("Connection Finalization"); - - let written = connection_finalization.step(input, output)?; + let written = connection_activation.step(input, output)?; - let next_state = if connection_finalization.state.is_terminal() { - ClientConnectorState::Connected { - result: ConnectionResult { + let next_state = if !connection_activation.state.is_terminal() { + ClientConnectorState::ConnectionFinalization { connection_activation } + } else { + match connection_activation.state { + ConnectionActivationState::Finalized { io_channel_id, user_channel_id, - static_channels: mem::take(&mut self.static_channels), desktop_size, - graphics_config: self.config.graphics.clone(), - no_server_pointer: self.config.no_server_pointer, - pointer_software_rendering: self.config.pointer_software_rendering, + } => ClientConnectorState::Connected { + result: ConnectionResult { + io_channel_id, + user_channel_id, + static_channels: mem::take(&mut self.static_channels), + desktop_size, + graphics_config: self.config.graphics.clone(), + no_server_pointer: self.config.no_server_pointer, + pointer_software_rendering: self.config.pointer_software_rendering, + }, }, - } - } else { - ClientConnectorState::ConnectionFinalization { - io_channel_id, - user_channel_id, - desktop_size, - connection_finalization, + _ => return Err(general_err!("invalid state (this is a bug)")), } }; @@ -826,131 +761,3 @@ fn create_client_info_pdu(config: &Config, routing_addr: &SocketAddr) -> rdp::Cl client_info, } } - -fn create_client_confirm_active( - config: &Config, - mut server_capability_sets: Vec, -) -> rdp::capability_sets::ClientConfirmActive { - use ironrdp_pdu::rdp::capability_sets::*; - - server_capability_sets.retain(|capability_set| matches!(capability_set, CapabilitySet::MultiFragmentUpdate(_))); - - let lossy_bitmap_compression = config - .bitmap - .as_ref() - .map(|bitmap| bitmap.lossy_compression) - .unwrap_or(false); - - let drawing_flags = if lossy_bitmap_compression { - BitmapDrawingFlags::ALLOW_SKIP_ALPHA - | BitmapDrawingFlags::ALLOW_DYNAMIC_COLOR_FIDELITY - | BitmapDrawingFlags::ALLOW_COLOR_SUBSAMPLING - } else { - BitmapDrawingFlags::ALLOW_SKIP_ALPHA - }; - - server_capability_sets.extend_from_slice(&[ - CapabilitySet::General(General { - major_platform_type: config.platform, - extra_flags: GeneralExtraFlags::FASTPATH_OUTPUT_SUPPORTED | GeneralExtraFlags::NO_BITMAP_COMPRESSION_HDR, - ..Default::default() - }), - CapabilitySet::Bitmap(Bitmap { - pref_bits_per_pix: 32, - desktop_width: config.desktop_size.width, - desktop_height: config.desktop_size.height, - desktop_resize_flag: false, - drawing_flags, - }), - CapabilitySet::Order(Order::new( - OrderFlags::NEGOTIATE_ORDER_SUPPORT | OrderFlags::ZERO_BOUNDS_DELTAS_SUPPORT, - OrderSupportExFlags::empty(), - 0, - 0, - )), - CapabilitySet::BitmapCache(BitmapCache { - caches: [CacheEntry { - entries: 0, - max_cell_size: 0, - }; BITMAP_CACHE_ENTRIES_NUM], - }), - CapabilitySet::Input(Input { - input_flags: InputFlags::all(), - keyboard_layout: 0, - keyboard_type: Some(config.keyboard_type), - keyboard_subtype: config.keyboard_subtype, - keyboard_function_key: config.keyboard_functional_keys_count, - keyboard_ime_filename: config.ime_file_name.clone(), - }), - CapabilitySet::Pointer(Pointer { - // Pointer cache should be set to non-zero value to enable client-side pointer rendering. - color_pointer_cache_size: DEFAULT_POINTER_CACHE_SIZE, - pointer_cache_size: DEFAULT_POINTER_CACHE_SIZE, - }), - CapabilitySet::Brush(Brush { - support_level: SupportLevel::Default, - }), - CapabilitySet::GlyphCache(GlyphCache { - glyph_cache: [CacheDefinition { - entries: 0, - max_cell_size: 0, - }; GLYPH_CACHE_NUM], - frag_cache: CacheDefinition { - entries: 0, - max_cell_size: 0, - }, - glyph_support_level: GlyphSupportLevel::None, - }), - CapabilitySet::OffscreenBitmapCache(OffscreenBitmapCache { - is_supported: false, - cache_size: 0, - cache_entries: 0, - }), - CapabilitySet::VirtualChannel(VirtualChannel { - flags: VirtualChannelFlags::NO_COMPRESSION, - chunk_size: Some(0), // ignored - }), - CapabilitySet::Sound(Sound { - flags: SoundFlags::empty(), - }), - CapabilitySet::LargePointer(LargePointer { - // Setting `LargePointerSupportFlags::UP_TO_384X384_PIXELS` allows server to send - // `TS_FP_LARGEPOINTERATTRIBUTE` update messages, which are required for client-side - // rendering of pointers bigger than 96x96 pixels. - flags: LargePointerSupportFlags::UP_TO_384X384_PIXELS, - }), - CapabilitySet::SurfaceCommands(SurfaceCommands { - flags: CmdFlags::SET_SURFACE_BITS | CmdFlags::STREAM_SURFACE_BITS | CmdFlags::FRAME_MARKER, - }), - CapabilitySet::BitmapCodecs(BitmapCodecs(vec![Codec { - id: 0x03, // RemoteFX - property: CodecProperty::RemoteFx(RemoteFxContainer::ClientContainer(RfxClientCapsContainer { - capture_flags: CaptureFlags::empty(), - caps_data: RfxCaps(RfxCapset(vec![RfxICap { - flags: RfxICapFlags::empty(), - entropy_bits: EntropyBits::Rlgr3, - }])), - })), - }])), - CapabilitySet::FrameAcknowledge(FrameAcknowledge { - max_unacknowledged_frame_count: 2, - }), - ]); - - if !server_capability_sets - .iter() - .any(|c| matches!(&c, CapabilitySet::MultiFragmentUpdate(_))) - { - server_capability_sets.push(CapabilitySet::MultiFragmentUpdate(MultifragmentUpdate { - max_request_size: 1024, - })); - } - - ClientConfirmActive { - originator_id: SERVER_CHANNEL_ID, - pdu: DemandActive { - source_descriptor: "IRONRDP".to_owned(), - capability_sets: server_capability_sets, - }, - } -} diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs new file mode 100644 index 000000000..9332e3a68 --- /dev/null +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -0,0 +1,341 @@ +use std::mem; + +use ironrdp_pdu::rdp::{self, capability_sets::CapabilitySet}; + +use crate::{legacy, Config, ConnectionFinalizationSequence, ConnectorResult, DesktopSize, Sequence, State, Written}; + +/// Represents the Capability Exchange and Connection Finalization phases +/// of the connection sequence (section [1.3.1.1]). +/// +/// This is abstracted into its own struct to allow it to be used for the ordinary +/// RDP connection sequence [`ClientConnector`] that occurs for every RDP connection, +/// as well as the Deactivation-Reactivation Sequence ([1.3.1.3]) that occurs when +/// a [Server Deactivate All PDU] is received. +/// +/// [1.3.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/023f1e69-cfe8-4ee6-9ee0-7e759fb4e4ee +/// [1.3.1.3]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dfc234ce-481a-4674-9a5d-2a7bafb14432 +/// [`ClientConnector`]: crate::ClientConnector +/// [Server Deactivate All PDU]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/8a29971a-df3c-48da-add2-8ed9a05edc89 +#[derive(Debug, Clone)] +pub struct ConnectionActivationSequence { + pub state: ConnectionActivationState, + pub config: Config, +} + +impl ConnectionActivationSequence { + pub fn new(config: Config, io_channel_id: u16, user_channel_id: u16) -> Self { + Self { + state: ConnectionActivationState::CapabilitiesExchange { + io_channel_id, + user_channel_id, + }, + config, + } + } +} + +impl Sequence for ConnectionActivationSequence { + fn next_pdu_hint(&self) -> Option<&dyn ironrdp_pdu::PduHint> { + match &self.state { + ConnectionActivationState::Consumed => None, + ConnectionActivationState::Finalized { .. } => None, + ConnectionActivationState::CapabilitiesExchange { .. } => Some(&ironrdp_pdu::X224_HINT), + ConnectionActivationState::ConnectionFinalization { + connection_finalization, + .. + } => connection_finalization.next_pdu_hint(), + } + } + + fn state(&self) -> &dyn State { + &self.state + } + + fn step(&mut self, input: &[u8], output: &mut ironrdp_pdu::write_buf::WriteBuf) -> ConnectorResult { + let (written, next_state) = match mem::take(&mut self.state) { + // Invalid state + ConnectionActivationState::Consumed => { + return Err(general_err!("connector sequence state is consumed (this is a bug)")) + } + ConnectionActivationState::Finalized { .. } => { + return Err(general_err!("connector sequence state is finalized (this is a bug)")) + } + ConnectionActivationState::CapabilitiesExchange { + io_channel_id, + user_channel_id, + } => { + debug!("Capabilities Exchange"); + + let send_data_indication_ctx = legacy::decode_send_data_indication(input)?; + let share_control_ctx = legacy::decode_share_control(send_data_indication_ctx)?; + + debug!(message = ?share_control_ctx.pdu, "Received"); + + if share_control_ctx.channel_id != io_channel_id { + warn!( + io_channel_id, + share_control_ctx.channel_id, "Unexpected channel ID for received Share Control Pdu" + ); + } + + let capability_sets = if let rdp::headers::ShareControlPdu::ServerDemandActive(server_demand_active) = + share_control_ctx.pdu + { + server_demand_active.pdu.capability_sets + } else { + return Err(general_err!( + "unexpected Share Control Pdu (expected ServerDemandActive)", + )); + }; + + for c in &capability_sets { + if let rdp::capability_sets::CapabilitySet::General(g) = c { + if g.protocol_version != rdp::capability_sets::PROTOCOL_VER { + warn!(version = g.protocol_version, "Unexpected protocol version"); + } + break; + } + } + + let desktop_size = capability_sets + .iter() + .find_map(|c| match c { + rdp::capability_sets::CapabilitySet::Bitmap(b) => Some(DesktopSize { + width: b.desktop_width, + height: b.desktop_height, + }), + _ => None, + }) + .unwrap_or(DesktopSize { + width: self.config.desktop_size.width, + height: self.config.desktop_size.height, + }); + + let client_confirm_active = rdp::headers::ShareControlPdu::ClientConfirmActive( + create_client_confirm_active(&self.config, capability_sets), + ); + + debug!(message = ?client_confirm_active, "Send"); + + let written = legacy::encode_share_control( + user_channel_id, + io_channel_id, + share_control_ctx.share_id, + client_confirm_active, + output, + )?; + + ( + Written::from_size(written)?, + ConnectionActivationState::ConnectionFinalization { + io_channel_id, + user_channel_id, + desktop_size, + connection_finalization: ConnectionFinalizationSequence::new(io_channel_id, user_channel_id), + }, + ) + } + ConnectionActivationState::ConnectionFinalization { + io_channel_id, + user_channel_id, + desktop_size, + mut connection_finalization, + } => { + debug!("Connection Finalization"); + + let written = connection_finalization.step(input, output)?; + + let next_state = if !connection_finalization.state.is_terminal() { + ConnectionActivationState::ConnectionFinalization { + io_channel_id, + user_channel_id, + desktop_size, + connection_finalization, + } + } else { + ConnectionActivationState::Finalized { + io_channel_id, + user_channel_id, + desktop_size, + } + }; + + (written, next_state) + } + }; + + self.state = next_state; + + Ok(written) + } +} + +#[derive(Default, Debug, Clone)] +pub enum ConnectionActivationState { + #[default] + Consumed, + CapabilitiesExchange { + io_channel_id: u16, + user_channel_id: u16, + }, + ConnectionFinalization { + io_channel_id: u16, + user_channel_id: u16, + desktop_size: DesktopSize, + connection_finalization: ConnectionFinalizationSequence, + }, + Finalized { + io_channel_id: u16, + user_channel_id: u16, + desktop_size: DesktopSize, + }, +} + +impl State for ConnectionActivationState { + fn name(&self) -> &'static str { + match self { + ConnectionActivationState::Consumed => "Consumed", + ConnectionActivationState::CapabilitiesExchange { .. } => "CapabilitiesExchange", + ConnectionActivationState::ConnectionFinalization { .. } => "ConnectionFinalization", + ConnectionActivationState::Finalized { .. } => "Finalized", + } + } + + fn is_terminal(&self) -> bool { + matches!(self, ConnectionActivationState::Finalized { .. }) + } + + fn as_any(&self) -> &dyn core::any::Any { + self + } +} + +const DEFAULT_POINTER_CACHE_SIZE: u16 = 32; + +fn create_client_confirm_active( + config: &Config, + mut server_capability_sets: Vec, +) -> rdp::capability_sets::ClientConfirmActive { + use ironrdp_pdu::rdp::capability_sets::*; + + server_capability_sets.retain(|capability_set| matches!(capability_set, CapabilitySet::MultiFragmentUpdate(_))); + + let lossy_bitmap_compression = config + .bitmap + .as_ref() + .map(|bitmap| bitmap.lossy_compression) + .unwrap_or(false); + + let drawing_flags = if lossy_bitmap_compression { + BitmapDrawingFlags::ALLOW_SKIP_ALPHA + | BitmapDrawingFlags::ALLOW_DYNAMIC_COLOR_FIDELITY + | BitmapDrawingFlags::ALLOW_COLOR_SUBSAMPLING + } else { + BitmapDrawingFlags::ALLOW_SKIP_ALPHA + }; + + server_capability_sets.extend_from_slice(&[ + CapabilitySet::General(General { + major_platform_type: config.platform, + extra_flags: GeneralExtraFlags::FASTPATH_OUTPUT_SUPPORTED | GeneralExtraFlags::NO_BITMAP_COMPRESSION_HDR, + ..Default::default() + }), + CapabilitySet::Bitmap(Bitmap { + pref_bits_per_pix: 32, + desktop_width: config.desktop_size.width, + desktop_height: config.desktop_size.height, + desktop_resize_flag: false, + drawing_flags, + }), + CapabilitySet::Order(Order::new( + OrderFlags::NEGOTIATE_ORDER_SUPPORT | OrderFlags::ZERO_BOUNDS_DELTAS_SUPPORT, + OrderSupportExFlags::empty(), + 0, + 0, + )), + CapabilitySet::BitmapCache(BitmapCache { + caches: [CacheEntry { + entries: 0, + max_cell_size: 0, + }; BITMAP_CACHE_ENTRIES_NUM], + }), + CapabilitySet::Input(Input { + input_flags: InputFlags::all(), + keyboard_layout: 0, + keyboard_type: Some(config.keyboard_type), + keyboard_subtype: config.keyboard_subtype, + keyboard_function_key: config.keyboard_functional_keys_count, + keyboard_ime_filename: config.ime_file_name.clone(), + }), + CapabilitySet::Pointer(Pointer { + // Pointer cache should be set to non-zero value to enable client-side pointer rendering. + color_pointer_cache_size: DEFAULT_POINTER_CACHE_SIZE, + pointer_cache_size: DEFAULT_POINTER_CACHE_SIZE, + }), + CapabilitySet::Brush(Brush { + support_level: SupportLevel::Default, + }), + CapabilitySet::GlyphCache(GlyphCache { + glyph_cache: [CacheDefinition { + entries: 0, + max_cell_size: 0, + }; GLYPH_CACHE_NUM], + frag_cache: CacheDefinition { + entries: 0, + max_cell_size: 0, + }, + glyph_support_level: GlyphSupportLevel::None, + }), + CapabilitySet::OffscreenBitmapCache(OffscreenBitmapCache { + is_supported: false, + cache_size: 0, + cache_entries: 0, + }), + CapabilitySet::VirtualChannel(VirtualChannel { + flags: VirtualChannelFlags::NO_COMPRESSION, + chunk_size: Some(0), // ignored + }), + CapabilitySet::Sound(Sound { + flags: SoundFlags::empty(), + }), + CapabilitySet::LargePointer(LargePointer { + // Setting `LargePointerSupportFlags::UP_TO_384X384_PIXELS` allows server to send + // `TS_FP_LARGEPOINTERATTRIBUTE` update messages, which are required for client-side + // rendering of pointers bigger than 96x96 pixels. + flags: LargePointerSupportFlags::UP_TO_384X384_PIXELS, + }), + CapabilitySet::SurfaceCommands(SurfaceCommands { + flags: CmdFlags::SET_SURFACE_BITS | CmdFlags::STREAM_SURFACE_BITS | CmdFlags::FRAME_MARKER, + }), + CapabilitySet::BitmapCodecs(BitmapCodecs(vec![Codec { + id: 0x03, // RemoteFX + property: CodecProperty::RemoteFx(RemoteFxContainer::ClientContainer(RfxClientCapsContainer { + capture_flags: CaptureFlags::empty(), + caps_data: RfxCaps(RfxCapset(vec![RfxICap { + flags: RfxICapFlags::empty(), + entropy_bits: EntropyBits::Rlgr3, + }])), + })), + }])), + CapabilitySet::FrameAcknowledge(FrameAcknowledge { + max_unacknowledged_frame_count: 2, + }), + ]); + + if !server_capability_sets + .iter() + .any(|c| matches!(&c, CapabilitySet::MultiFragmentUpdate(_))) + { + server_capability_sets.push(CapabilitySet::MultiFragmentUpdate(MultifragmentUpdate { + max_request_size: 1024, + })); + } + + ClientConfirmActive { + originator_id: SERVER_CHANNEL_ID, + pdu: DemandActive { + source_descriptor: "IRONRDP".to_owned(), + capability_sets: server_capability_sets, + }, + } +} diff --git a/crates/ironrdp-connector/src/connection_finalization.rs b/crates/ironrdp-connector/src/connection_finalization.rs index 2136cb13b..2932bb8ba 100644 --- a/crates/ironrdp-connector/src/connection_finalization.rs +++ b/crates/ironrdp-connector/src/connection_finalization.rs @@ -8,7 +8,7 @@ use ironrdp_pdu::PduHint; use crate::{legacy, ConnectorResult, Sequence, State, Written}; -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] #[non_exhaustive] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum ConnectionFinalizationState { @@ -47,7 +47,7 @@ impl State for ConnectionFinalizationState { } } -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct ConnectionFinalizationSequence { pub state: ConnectionFinalizationState, diff --git a/crates/ironrdp-connector/src/legacy.rs b/crates/ironrdp-connector/src/legacy.rs index 6c41828af..1b0ff06ca 100644 --- a/crates/ironrdp-connector/src/legacy.rs +++ b/crates/ironrdp-connector/src/legacy.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; +use ironrdp_pdu::rdp::headers::ServerDeactivateAll; use ironrdp_pdu::write_buf::WriteBuf; use ironrdp_pdu::{decode, encode_vec, rdp, PduDecode, PduEncode}; @@ -147,7 +148,7 @@ pub fn decode_share_data(ctx: SendDataIndicationCtx<'_>) -> ConnectorResult) -> ConnectorResult) -> ConnectorResult { + let ctx = decode_share_control(ctx)?; + + match ctx.pdu { + rdp::headers::ShareControlPdu::ServerDeactivateAll(deactivate_all) => { + Ok(IoChannelPdu::DeactivateAll(deactivate_all)) + } + rdp::headers::ShareControlPdu::Data(share_data_header) => { + let share_data_ctx = ShareDataCtx { + initiator_id: ctx.initiator_id, + channel_id: ctx.channel_id, + share_id: ctx.share_id, + pdu_source: ctx.pdu_source, + pdu: share_data_header.share_data_pdu, + }; + + Ok(IoChannelPdu::Data(share_data_ctx)) + } + _ => Err(general_err!( + "received unexpected Share Control Pdu (expected Share Data Header or Server Deactivate All)" + )), + } +} + impl ironrdp_error::legacy::CatchAllKind for crate::ConnectorErrorKind { const CATCH_ALL_VALUE: Self = crate::ConnectorErrorKind::General; } diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index 98e1ad968..ee842e139 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -11,6 +11,7 @@ pub mod legacy; mod channel_connection; mod connection; +pub mod connection_activation; mod connection_finalization; pub mod credssp; mod license_exchange; diff --git a/crates/ironrdp-graphics/Cargo.toml b/crates/ironrdp-graphics/Cargo.toml index 6fe765262..21850254b 100644 --- a/crates/ironrdp-graphics/Cargo.toml +++ b/crates/ironrdp-graphics/Cargo.toml @@ -19,7 +19,7 @@ doctest = false bit_field = "0.10" bitflags.workspace = true bitvec = "1.0" -byteorder = "1.5" +byteorder.workspace = true ironrdp-error.workspace = true ironrdp-pdu = { workspace = true, features = ["std"] } lazy_static = "1.4" diff --git a/crates/ironrdp-pdu/Cargo.toml b/crates/ironrdp-pdu/Cargo.toml index 53b0b1c56..ab38f33b5 100644 --- a/crates/ironrdp-pdu/Cargo.toml +++ b/crates/ironrdp-pdu/Cargo.toml @@ -27,7 +27,7 @@ tap = "1" # TODO: get rid of these dependencies (related code should probably go into another crate) bit_field = "0.10" -byteorder = "1.5" +byteorder.workspace = true der-parser = "8.2" thiserror.workspace = true md5 = { package = "md-5", version = "0.10" } diff --git a/crates/ironrdp-pdu/src/rdp/capability_sets.rs b/crates/ironrdp-pdu/src/rdp/capability_sets.rs index 844c5aac1..699a302b4 100644 --- a/crates/ironrdp-pdu/src/rdp/capability_sets.rs +++ b/crates/ironrdp-pdu/src/rdp/capability_sets.rs @@ -59,6 +59,9 @@ const ORIGINATOR_ID_FIELD_SIZE: usize = 2; const NULL_TERMINATOR: &str = "\0"; +/// [2.2.1.13.1] Server Demand Active PDU +/// +/// [2.2.1.13.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/a07abad1-38bb-4a1a-96c9-253e3d5440df #[derive(Debug, Clone, PartialEq, Eq)] pub struct ServerDemandActive { pub pdu: DemandActive, @@ -100,6 +103,9 @@ impl<'de> PduDecode<'de> for ServerDemandActive { } } +/// [2.2.1.13.2] Client Confirm Active PDU +/// +/// [2.2.1.13.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/4c3c2710-0bf0-4c54-8e69-aff40ffcde66 #[derive(Debug, Clone, PartialEq, Eq)] pub struct ClientConfirmActive { /// According to [MSDN](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/4e9722c3-ad83-43f5-af5a-529f73d88b48), @@ -147,6 +153,9 @@ impl<'de> PduDecode<'de> for ClientConfirmActive { } } +/// 2.2.1.13.1.1 Demand Active PDU Data (TS_DEMAND_ACTIVE_PDU) +/// +/// [2.2.1.13.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/bd612af5-cb54-43a2-9646-438bc3ecf5db #[derive(Debug, Clone, PartialEq, Eq)] pub struct DemandActive { pub source_descriptor: String, diff --git a/crates/ironrdp-pdu/src/rdp/headers.rs b/crates/ironrdp-pdu/src/rdp/headers.rs index 166584085..3e266b69e 100644 --- a/crates/ironrdp-pdu/src/rdp/headers.rs +++ b/crates/ironrdp-pdu/src/rdp/headers.rs @@ -158,6 +158,7 @@ pub enum ShareControlPdu { ServerDemandActive(ServerDemandActive), ClientConfirmActive(ClientConfirmActive), Data(ShareDataHeader), + ServerDeactivateAll(ServerDeactivateAll), } impl ShareControlPdu { @@ -168,6 +169,7 @@ impl ShareControlPdu { ShareControlPdu::ServerDemandActive(_) => "Server Demand Active PDU", ShareControlPdu::ClientConfirmActive(_) => "Client Confirm Active PDU", ShareControlPdu::Data(_) => "Data PDU", + ShareControlPdu::ServerDeactivateAll(_) => "Server Deactivate All PDU", } } @@ -176,6 +178,7 @@ impl ShareControlPdu { ShareControlPdu::ServerDemandActive(_) => ShareControlPduType::DemandActivePdu, ShareControlPdu::ClientConfirmActive(_) => ShareControlPduType::ConfirmActivePdu, ShareControlPdu::Data(_) => ShareControlPduType::DataPdu, + ShareControlPdu::ServerDeactivateAll(_) => ShareControlPduType::DeactivateAllPdu, } } @@ -188,6 +191,9 @@ impl ShareControlPdu { Ok(ShareControlPdu::ClientConfirmActive(ClientConfirmActive::decode(src)?)) } ShareControlPduType::DataPdu => Ok(ShareControlPdu::Data(ShareDataHeader::decode(src)?)), + ShareControlPduType::DeactivateAllPdu => { + Ok(ShareControlPdu::ServerDeactivateAll(ServerDeactivateAll::decode(src)?)) + } _ => Err(invalid_message_err!("share_type", "unexpected share control PDU type")), } } @@ -199,6 +205,7 @@ impl PduEncode for ShareControlPdu { ShareControlPdu::ServerDemandActive(pdu) => pdu.encode(dst), ShareControlPdu::ClientConfirmActive(pdu) => pdu.encode(dst), ShareControlPdu::Data(share_data_header) => share_data_header.encode(dst), + ShareControlPdu::ServerDeactivateAll(deactivate_all) => deactivate_all.encode(dst), } } @@ -211,6 +218,7 @@ impl PduEncode for ShareControlPdu { ShareControlPdu::ServerDemandActive(pdu) => pdu.size(), ShareControlPdu::ClientConfirmActive(pdu) => pdu.size(), ShareControlPdu::Data(share_data_header) => share_data_header.size(), + ShareControlPdu::ServerDeactivateAll(deactivate_all) => deactivate_all.size(), } } } @@ -505,3 +513,35 @@ bitflags! { const FLUSHED = 0x80; } } + +/// 2.2.3.1 Server Deactivate All PDU +/// +/// [2.2.3.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/8a29971a-df3c-48da-add2-8ed9a05edc89 +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ServerDeactivateAll; + +impl PduDecode<'_> for ServerDeactivateAll { + fn decode(src: &mut ReadCursor<'_>) -> PduResult { + let length_source_descriptor = src.read_u16(); + let _ = src.read_slice(length_source_descriptor.into()); + Ok(Self) + } +} + +impl PduEncode for ServerDeactivateAll { + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + // A 16-bit, unsigned integer. The size in bytes of the sourceDescriptor field. + dst.write_u16(1); + // Variable number of bytes. The source descriptor. This field SHOULD be set to 0x00. + dst.write_u8(0); + Ok(()) + } + + fn name(&self) -> &'static str { + "Server Deactivate All" + } + + fn size(&self) -> usize { + 2 /* length_source_descriptor */ + 1 /* source_descriptor */ + } +} diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs index 8f9ddb15d..a0ce362a7 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs @@ -73,6 +73,9 @@ pub enum Orientation { PortraitFlipped = 270, } +/// [2.2.2.2.1] DISPLAYCONTROL_MONITOR_LAYOUT_PDU +/// +/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c #[derive(Debug, Clone, PartialEq, Eq)] pub struct Monitor { pub flags: MonitorFlags, @@ -154,6 +157,9 @@ impl<'de> PduDecode<'de> for Monitor { } } +/// [2.2.2.2] DISPLAYCONTROL_MONITOR_LAYOUT_PDU +/// +/// [2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/22741217-12a0-4fb8-b5a0-df43905aaf06 #[derive(Debug, Clone, PartialEq, Eq)] pub struct MonitorLayoutPdu { pub monitors: Vec, diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index b9b2e5d56..11da1fb5f 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -1,5 +1,6 @@ use std::rc::Rc; +use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::ConnectionResult; use ironrdp_graphics::pointer::DecodedPointer; use ironrdp_pdu::geometry::InclusiveRectangle; @@ -198,6 +199,7 @@ pub enum ActiveStageOutput { PointerPosition { x: u16, y: u16 }, PointerBitmap(Rc), Terminate(GracefulDisconnectReason), + DeactivateAll(ConnectionActivationSequence), } impl TryFrom for ActiveStageOutput { @@ -215,6 +217,7 @@ impl TryFrom for ActiveStageOutput { Ok(Self::Terminate(reason)) } + x224::ProcessorOutput::DeactivateAll(cas) => Ok(Self::DeactivateAll(cas)), } } } diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index e3e5f2c69..ac2709f53 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -4,6 +4,7 @@ mod gfx; use std::cmp; use std::collections::HashMap; +use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::legacy::SendDataIndicationCtx; use ironrdp_connector::GraphicsConfig; use ironrdp_pdu::dvc::FieldType; @@ -29,6 +30,8 @@ pub enum ProcessorOutput { ResponseFrame(Vec), /// A graceful disconnect notification. Client should close the connection upon receiving this. Disconnect(DisconnectReason), + /// Received a [`ironrdp_pdu::rdp::headers::ServerDeactivateAll`] PDU. + DeactivateAll(ConnectionActivationSequence), } pub struct Processor { @@ -123,58 +126,63 @@ impl Processor { fn process_io_channel(&self, data_ctx: SendDataIndicationCtx<'_>) -> SessionResult> { debug_assert_eq!(data_ctx.channel_id, self.io_channel_id); - let ctx = ironrdp_connector::legacy::decode_share_data(data_ctx).map_err(crate::legacy::map_error)?; - - match ctx.pdu { - ShareDataPdu::SaveSessionInfo(session_info) => { - debug!("Got Session Save Info PDU: {session_info:?}"); - 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 graceful_disconnect = error_info_to_graceful_disconnect_reason(&e); - - if let Some(reason) = graceful_disconnect { - debug!("Received server-side graceful disconnect request: {reason}"); - - Ok(vec![ProcessorOutput::Disconnect(reason)]) - } else { - Err(reason_err!("ServerSetErrorInfo", "{}", e.description())) + let io_channel = ironrdp_connector::legacy::decode_io_channel(data_ctx).map_err(crate::legacy::map_error)?; + + match io_channel { + ironrdp_connector::legacy::IoChannelPdu::Data(ctx) => { + match ctx.pdu { + ShareDataPdu::SaveSessionInfo(session_info) => { + debug!("Got Session Save Info PDU: {session_info:?}"); + 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 graceful_disconnect = error_info_to_graceful_disconnect_reason(&e); + + if let Some(reason) = graceful_disconnect { + debug!("Received server-side graceful disconnect request: {reason}"); + + Ok(vec![ProcessorOutput::Disconnect(reason)]) + } else { + Err(reason_err!("ServerSetErrorInfo", "{}", e.description())) + } + } + 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_pdu::encode_vec(&ultimatum).map_err(SessionError::pdu); + + Ok(vec![ + ProcessorOutput::ResponseFrame(encoded_pdu?), + ProcessorOutput::Disconnect(DisconnectReason::UserRequested), + ]) + } + _ => Err(reason_err!( + "IO channel", + "unexpected PDU: expected Session Save Info PDU, got: {:?}", + ctx.pdu.as_short_name() + )), } } - 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_pdu::encode_vec(&ultimatum).map_err(SessionError::pdu); - - Ok(vec![ - ProcessorOutput::ResponseFrame(encoded_pdu?), - ProcessorOutput::Disconnect(DisconnectReason::UserRequested), - ]) - } - _ => Err(reason_err!( - "IO channel", - "unexpected PDU: expected Session Save Info PDU, got: {:?}", - ctx.pdu.as_short_name() - )), + ironrdp_connector::legacy::IoChannelPdu::DeactivateAll(_) => todo!(), } } diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 93943d9a6..b230e2d02 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -603,6 +603,7 @@ impl Session { hotspot_y, })?; } + ActiveStageOutput::DeactivateAll(_) => todo!("DeactivateAll"), ActiveStageOutput::Terminate(reason) => break 'outer reason, } } From efaaf369ffdbb40caa0909e79e1f99e39906f48d Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 11 Mar 2024 12:39:44 -0700 Subject: [PATCH 26/84] Breaks single_connect_step into two separate methods: `single_connect_step_read` and `single_connect_step_write`. Also adds a `ConnectionActivationSequence::reset_clone` method to aid in reusing the sequence for Deactivate All PDU handling. Passes `ConnectionActivationSequence` to `x224::Processor` to hand back upon receiving a Deactivate All PDU. --- crates/ironrdp-async/src/connector.rs | 36 ++++++++++++---- crates/ironrdp-connector/src/connection.rs | 2 + .../src/connection_activation.rs | 42 ++++++++++++++++--- crates/ironrdp-session/src/active_stage.rs | 1 + crates/ironrdp-session/src/x224/mod.rs | 7 +++- 5 files changed, 74 insertions(+), 14 deletions(-) diff --git a/crates/ironrdp-async/src/connector.rs b/crates/ironrdp-async/src/connector.rs index bf5fc48eb..d8d0e8d2f 100644 --- a/crates/ironrdp-async/src/connector.rs +++ b/crates/ironrdp-async/src/connector.rs @@ -2,8 +2,8 @@ use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, Kerbe use ironrdp_connector::sspi::credssp::ClientState; use ironrdp_connector::sspi::generator::GeneratorState; use ironrdp_connector::{ - custom_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, - Sequence as _, ServerName, State as _, + custom_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, Sequence, + ServerName, State as _, Written, }; use ironrdp_pdu::write_buf::WriteBuf; @@ -187,10 +187,23 @@ where S: FramedWrite + FramedRead, { buf.clear(); + let written = single_connect_step_read(framed, connector, buf).await?; + single_connect_step_write(framed, buf, written).await +} + +pub async fn single_connect_step_read( + framed: &mut Framed, + connector: &mut dyn Sequence, + buf: &mut WriteBuf, +) -> ConnectorResult +where + S: FramedRead, +{ + buf.clear(); - let written = if let Some(next_pdu_hint) = connector.next_pdu_hint() { + if let Some(next_pdu_hint) = connector.next_pdu_hint() { debug!( - connector.state = connector.state.name(), + connector.state = connector.state().name(), hint = ?next_pdu_hint, "Wait for PDU" ); @@ -202,11 +215,20 @@ where trace!(length = pdu.len(), "PDU received"); - connector.step(&pdu, buf)? + connector.step(&pdu, buf) } else { - connector.step_no_input(buf)? - }; + connector.step_no_input(buf) + } +} +async fn single_connect_step_write( + framed: &mut Framed, + buf: &mut WriteBuf, + written: Written, +) -> ConnectorResult<()> +where + S: FramedWrite, +{ if let Some(response_len) = written.size() { debug_assert_eq!(buf.filled_len(), response_len); let response = buf.filled(); diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 46f53dd89..77596ee4a 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -25,6 +25,7 @@ pub struct ConnectionResult { pub graphics_config: Option, pub no_server_pointer: bool, pub pointer_software_rendering: bool, + pub connection_activation: ConnectionActivationSequence, } #[derive(Default, Debug)] @@ -560,6 +561,7 @@ impl Sequence for ClientConnector { graphics_config: self.config.graphics.clone(), no_server_pointer: self.config.no_server_pointer, pointer_software_rendering: self.config.pointer_software_rendering, + connection_activation, }, }, _ => return Err(general_err!("invalid state (this is a bug)")), diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs index 9332e3a68..67017f826 100644 --- a/crates/ironrdp-connector/src/connection_activation.rs +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -32,6 +32,38 @@ impl ConnectionActivationSequence { config, } } + + #[must_use] + pub fn reset_clone(&self) -> Self { + self.clone().reset() + } + + fn reset(mut self) -> Self { + match &self.state { + ConnectionActivationState::CapabilitiesExchange { + io_channel_id, + user_channel_id, + } + | ConnectionActivationState::ConnectionFinalization { + io_channel_id, + user_channel_id, + .. + } + | ConnectionActivationState::Finalized { + io_channel_id, + user_channel_id, + .. + } => { + self.state = ConnectionActivationState::CapabilitiesExchange { + io_channel_id: *io_channel_id, + user_channel_id: *user_channel_id, + }; + + self + } + ConnectionActivationState::Consumed => self, + } + } } impl Sequence for ConnectionActivationSequence { @@ -53,12 +85,10 @@ impl Sequence for ConnectionActivationSequence { fn step(&mut self, input: &[u8], output: &mut ironrdp_pdu::write_buf::WriteBuf) -> ConnectorResult { let (written, next_state) = match mem::take(&mut self.state) { - // Invalid state - ConnectionActivationState::Consumed => { - return Err(general_err!("connector sequence state is consumed (this is a bug)")) - } - ConnectionActivationState::Finalized { .. } => { - return Err(general_err!("connector sequence state is finalized (this is a bug)")) + ConnectionActivationState::Consumed | ConnectionActivationState::Finalized { .. } => { + return Err(general_err!( + "connector sequence state is finalized or consumed (this is a bug)" + )); } ConnectionActivationState::CapabilitiesExchange { io_channel_id, diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index 11da1fb5f..d04a579fc 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -29,6 +29,7 @@ impl ActiveStage { connection_result.io_channel_id, connection_result.graphics_config, graphics_handler, + connection_result.connection_activation, ); let fast_path_processor = fast_path::ProcessorBuilder { diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index ac2709f53..acd16d512 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -45,6 +45,7 @@ pub struct Processor { drdynvc_initialized: bool, graphics_config: Option, graphics_handler: Option>, + connection_activation: ConnectionActivationSequence, } impl Processor { @@ -54,6 +55,7 @@ impl Processor { io_channel_id: u16, graphics_config: Option, graphics_handler: Option>, + connection_activation: ConnectionActivationSequence, ) -> Self { let drdynvc_channel_id = static_channels.iter().find_map(|(type_id, channel)| { if channel.is_drdynvc() { @@ -73,6 +75,7 @@ impl Processor { drdynvc_initialized: false, graphics_config, graphics_handler, + connection_activation, } } @@ -182,7 +185,9 @@ impl Processor { )), } } - ironrdp_connector::legacy::IoChannelPdu::DeactivateAll(_) => todo!(), + ironrdp_connector::legacy::IoChannelPdu::DeactivateAll(_) => Ok(vec![ProcessorOutput::DeactivateAll( + self.connection_activation.reset_clone(), + )]), } } From c4afa98f2c231d2cf860d96b8575afb8bf5a43ce Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 11 Mar 2024 18:30:40 -0700 Subject: [PATCH 27/84] Sets desktop_resize_flag to true which is required for the Microsoft::Windows::RDS::DisplayControl DVC to work. --- crates/ironrdp-connector/src/connection_activation.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs index 67017f826..686d91b47 100644 --- a/crates/ironrdp-connector/src/connection_activation.rs +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -274,7 +274,8 @@ fn create_client_confirm_active( pref_bits_per_pix: 32, desktop_width: config.desktop_size.width, desktop_height: config.desktop_size.height, - desktop_resize_flag: false, + // This is required to be true in order for the Microsoft::Windows::RDS::DisplayControl DVC to work. + desktop_resize_flag: true, drawing_flags, }), CapabilitySet::Order(Order::new( From 4b7725d3fc83c0a1ecdd7351ebbfffd3a92ff85b Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 11 Mar 2024 21:45:00 -0700 Subject: [PATCH 28/84] Removes the explicit state machine from DecodingContext. State comes to us from the server via the BlockType in the BlockHeader, so it needn't be rigidly tracked explicitly in DecodingContext. This makes the RFX pipeline more flexible, which in turn allows us to seamlessly use it when we get another sync sequence mid-stream due to having fielded a Server Deactivate PDU. --- crates/ironrdp-pdu/src/codecs/rfx.rs | 16 ++++++- .../src/codecs/rfx/data_messages.rs | 16 ++++--- .../src/codecs/rfx/header_messages.rs | 16 ++++--- crates/ironrdp-session/src/rfx.rs | 44 +++++++++---------- crates/ironrdp-session/src/x224/mod.rs | 5 ++- 5 files changed, 62 insertions(+), 35 deletions(-) diff --git a/crates/ironrdp-pdu/src/codecs/rfx.rs b/crates/ironrdp-pdu/src/codecs/rfx.rs index 0d1982f69..634c7634f 100644 --- a/crates/ironrdp-pdu/src/codecs/rfx.rs +++ b/crates/ironrdp-pdu/src/codecs/rfx.rs @@ -76,6 +76,11 @@ pub struct BlockHeader { } impl BlockHeader { + pub fn from_buffer_consume(buffer: &mut &[u8]) -> Result { + let ty = BlockType::from_buffer(buffer)?; + Self::from_buffer_consume_with_type(buffer, ty) + } + fn from_buffer_consume_with_type(buffer: &mut &[u8], ty: BlockType) -> Result { let block_length = buffer.read_u32::()? as usize; @@ -98,8 +103,7 @@ impl BlockHeader { } fn from_buffer_consume_with_expected_type(buffer: &mut &[u8], expected_type: BlockType) -> Result { - let ty = buffer.read_u16::()?; - let ty = BlockType::from_u16(ty).ok_or(RfxError::InvalidBlockType(ty))?; + let ty = BlockType::from_buffer(buffer)?; if ty != expected_type { return Err(RfxError::UnexpectedBlockType { expected: expected_type, @@ -220,6 +224,14 @@ pub enum BlockType { Extension = 0xCCC7, } +impl BlockType { + fn from_buffer(buffer: &mut &[u8]) -> Result { + let ty = buffer.read_u16::()?; + let ty = BlockType::from_u16(ty).ok_or(RfxError::InvalidBlockType(ty))?; + Ok(ty) + } +} + #[derive(Debug, Error)] pub enum RfxError { #[error("IO error")] diff --git a/crates/ironrdp-pdu/src/codecs/rfx/data_messages.rs b/crates/ironrdp-pdu/src/codecs/rfx/data_messages.rs index 02d0f9900..5f378f7eb 100644 --- a/crates/ironrdp-pdu/src/codecs/rfx/data_messages.rs +++ b/crates/ironrdp-pdu/src/codecs/rfx/data_messages.rs @@ -120,11 +120,8 @@ pub struct FrameBeginPdu { pub number_of_regions: i16, } -impl PduBufferParsing<'_> for FrameBeginPdu { - type Error = RfxError; - - fn from_buffer_consume(buffer: &mut &[u8]) -> Result { - let header = BlockHeader::from_buffer_consume_with_expected_type(buffer, BlockType::FrameBegin)?; +impl FrameBeginPdu { + pub fn from_buffer_consume_with_header(buffer: &mut &[u8], header: BlockHeader) -> Result { CodecChannelHeader::from_buffer_consume_with_type(buffer, BlockType::FrameBegin)?; let mut buffer = buffer.split_to(header.data_length); @@ -136,6 +133,15 @@ impl PduBufferParsing<'_> for FrameBeginPdu { number_of_regions, }) } +} + +impl PduBufferParsing<'_> for FrameBeginPdu { + type Error = RfxError; + + fn from_buffer_consume(buffer: &mut &[u8]) -> Result { + let header = BlockHeader::from_buffer_consume_with_expected_type(buffer, BlockType::FrameBegin)?; + Self::from_buffer_consume_with_header(buffer, header) + } fn to_buffer_consume(&self, buffer: &mut &mut [u8]) -> Result<(), Self::Error> { let header = BlockHeader { diff --git a/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs b/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs index 07754c7da..67dc717af 100644 --- a/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs +++ b/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs @@ -16,11 +16,8 @@ const CHANNEL_SIZE: usize = 5; #[derive(Debug, Clone, PartialEq, Eq)] pub struct SyncPdu; -impl PduBufferParsing<'_> for SyncPdu { - type Error = RfxError; - - fn from_buffer_consume(buffer: &mut &[u8]) -> Result { - let header = BlockHeader::from_buffer_consume_with_expected_type(buffer, BlockType::Sync)?; +impl SyncPdu { + pub fn from_buffer_consume_with_header(buffer: &mut &[u8], header: BlockHeader) -> Result { let mut buffer = buffer.split_to(header.data_length); let magic = buffer.read_u32::()?; @@ -34,6 +31,15 @@ impl PduBufferParsing<'_> for SyncPdu { Ok(Self) } } +} + +impl PduBufferParsing<'_> for SyncPdu { + type Error = RfxError; + + fn from_buffer_consume(buffer: &mut &[u8]) -> Result { + let header = BlockHeader::from_buffer_consume_with_expected_type(buffer, BlockType::Sync)?; + Self::from_buffer_consume_with_header(buffer, header) + } fn to_buffer_consume(&self, buffer: &mut &mut [u8]) -> Result<(), Self::Error> { let header = BlockHeader { diff --git a/crates/ironrdp-session/src/rfx.rs b/crates/ironrdp-session/src/rfx.rs index 7ba8efc6a..8d2576bf6 100644 --- a/crates/ironrdp-session/src/rfx.rs +++ b/crates/ironrdp-session/src/rfx.rs @@ -15,7 +15,6 @@ const TILE_SIZE: u16 = 64; pub type FrameId = u32; pub struct DecodingContext { - state: SequenceState, context: rfx::ContextPdu, channels: rfx::ChannelsPdu, decoding_tiles: DecodingTileContext, @@ -24,7 +23,6 @@ pub struct DecodingContext { impl Default for DecodingContext { fn default() -> Self { Self { - state: SequenceState::HeaderMessages, context: rfx::ContextPdu { flags: rfx::OperatingMode::empty(), entropy_algorithm: rfx::EntropyAlgorithm::Rlgr1, @@ -47,20 +45,31 @@ impl DecodingContext { input: &mut &[u8], ) -> SessionResult<(FrameId, InclusiveRectangle)> { loop { - match self.state { - SequenceState::HeaderMessages => { - self.process_headers(input)?; + let block_header = rfx::BlockHeader::from_buffer_consume(input)?; + match block_header.ty { + rfx::BlockType::Sync => { + self.process_sync(input, block_header)?; } - SequenceState::DataMessages => { - return self.process_data_messages(image, destination, input); + rfx::BlockType::FrameBegin => { + return self.process_frame(input, block_header, image, destination); + } + _ => { + return Err(reason_err!( + "rfx::DecodingContext", + "unexpected RFX block type: {:?}", + block_header.ty + )); } } } } - fn process_headers(&mut self, input: &mut &[u8]) -> SessionResult<()> { - let _sync = rfx::SyncPdu::from_buffer_consume(input)?; + fn process_sync(&mut self, input: &mut &[u8], header: rfx::BlockHeader) -> SessionResult<()> { + let _sync = rfx::SyncPdu::from_buffer_consume_with_header(input, header)?; + self.process_headers(input) + } + fn process_headers(&mut self, input: &mut &[u8]) -> SessionResult<()> { let mut context = None; let mut channels = None; @@ -81,24 +90,24 @@ impl DecodingContext { self.context = context; self.channels = channels; - self.state = SequenceState::DataMessages; Ok(()) } #[instrument(skip_all)] - fn process_data_messages( + fn process_frame( &mut self, + input: &mut &[u8], + header: rfx::BlockHeader, image: &mut DecodedImage, destination: &InclusiveRectangle, - input: &mut &[u8], ) -> SessionResult<(FrameId, InclusiveRectangle)> { let channel = self.channels.0.first().unwrap(); let width = channel.width.as_u16(); let height = channel.height.as_u16(); let entropy_algorithm = self.context.entropy_algorithm; - let frame_begin = rfx::FrameBeginPdu::from_buffer_consume(input)?; + let frame_begin = rfx::FrameBeginPdu::from_buffer_consume_with_header(input, header)?; let mut region = rfx::RegionPdu::from_buffer_consume(input)?; let tile_set = rfx::TileSetPdu::from_buffer_consume(input)?; let _frame_end = rfx::FrameEndPdu::from_buffer_consume(input)?; @@ -145,10 +154,6 @@ impl DecodingContext { final_update_rectangle = final_update_rectangle.union(¤t_update_rectangle); } - if self.context.flags.contains(rfx::OperatingMode::IMAGE_MODE) { - self.state = SequenceState::HeaderMessages; - } - Ok((frame_begin.index, final_update_rectangle)) } } @@ -258,8 +263,3 @@ struct TileData<'a> { quants: [Quant; 3], data: [&'a [u8]; 3], } - -enum SequenceState { - HeaderMessages, - DataMessages, -} diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index acd16d512..f95bd0548 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -30,7 +30,10 @@ pub enum ProcessorOutput { ResponseFrame(Vec), /// A graceful disconnect notification. Client should close the connection upon receiving this. Disconnect(DisconnectReason), - /// Received a [`ironrdp_pdu::rdp::headers::ServerDeactivateAll`] PDU. + /// 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(ConnectionActivationSequence), } From 5f0840b8fb53301b4b884bcf9dec2235e754740e Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 16:23:36 -0700 Subject: [PATCH 29/84] Consolidates the display module into ironrdp-dvc crate. --- crates/ironrdp-dvc/src/display.rs | 238 ++++++++++--- crates/ironrdp-dvc/src/display/client.rs | 48 +++ crates/ironrdp-dvc/src/display/server.rs | 41 +++ crates/ironrdp-dvc/src/lib.rs | 4 +- crates/ironrdp-dvc/src/server.rs | 2 +- crates/ironrdp-pdu/src/rdp/headers.rs | 2 - crates/ironrdp-pdu/src/rdp/vc/dvc.rs | 1 - crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs | 357 ------------------- crates/ironrdp-server/src/server.rs | 40 +-- crates/ironrdp-session/src/active_stage.rs | 1 - 10 files changed, 287 insertions(+), 447 deletions(-) create mode 100644 crates/ironrdp-dvc/src/display/client.rs create mode 100644 crates/ironrdp-dvc/src/display/server.rs delete mode 100644 crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs diff --git a/crates/ironrdp-dvc/src/display.rs b/crates/ironrdp-dvc/src/display.rs index 30991d387..b27a7e4c9 100644 --- a/crates/ironrdp-dvc/src/display.rs +++ b/crates/ironrdp-dvc/src/display.rs @@ -14,68 +14,96 @@ use crate::SvcMessage; use crate::Vec; use bitflags::bitflags; use ironrdp_pdu::cast_length; +use ironrdp_pdu::cursor::ReadCursor; use ironrdp_pdu::cursor::WriteCursor; +use ironrdp_pdu::ensure_fixed_part_size; use ironrdp_pdu::ensure_size; +use ironrdp_pdu::invalid_message_err; use ironrdp_pdu::other_err; use ironrdp_pdu::write_buf::WriteBuf; +use ironrdp_pdu::PduDecode; use ironrdp_pdu::PduEncode; -use ironrdp_pdu::PduParsing; +use ironrdp_pdu::PduError; use ironrdp_svc::impl_as_any; -pub const CHANNEL_NAME: &str = "Microsoft::Windows::RDS::DisplayControl"; +pub mod client; +pub mod server; -/// A client for the Display Control Virtual Channel. -pub struct DisplayControlClient {} +pub const CHANNEL_NAME: &str = "Microsoft::Windows::RDS::DisplayControl"; -impl_as_any!(DisplayControlClient); +pub enum DisplayControlPdu { + MonitorLayout(MonitorLayoutPdu), + Caps(DisplayControlCapsPdu), +} -impl DvcProcessor for DisplayControlClient { - fn channel_name(&self) -> &str { - CHANNEL_NAME +impl PduEncode for DisplayControlPdu { + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + match self { + DisplayControlPdu::MonitorLayout(pdu) => pdu.encode(dst), + DisplayControlPdu::Caps(pdu) => pdu.encode(dst), + } } - fn start(&mut self, _channel_id: u32) -> PduResult { - Ok(Vec::new()) + fn name(&self) -> &'static str { + match self { + DisplayControlPdu::MonitorLayout(pdu) => pdu.name(), + DisplayControlPdu::Caps(pdu) => pdu.name(), + } } - fn process(&mut self, channel_id: u32, payload: &[u8]) -> PduResult { - // TODO: We can parse the payload here for completeness sake, - // in practice we don't need to do anything with the payload. - debug!("Got Display PDU of length: {}", payload.len()); - Ok(Vec::new()) + fn size(&self) -> usize { + match self { + DisplayControlPdu::MonitorLayout(pdu) => pdu.size(), + DisplayControlPdu::Caps(pdu) => pdu.size(), + } } } -impl DvcClientProcessor for DisplayControlClient {} +impl DvcPduEncode for DisplayControlPdu {} -impl DisplayControlClient { - pub fn new() -> Self { - Self {} +impl PduDecode<'_> for DisplayControlPdu { + fn decode(src: &mut ReadCursor<'_>) -> PduResult { + let header = Header::decode(src)?; + match header.pdu_type { + DisplayControlType::MonitorLayout => { + Ok(DisplayControlPdu::MonitorLayout(MonitorLayoutPdu::decode(header, src)?)) + } + DisplayControlType::Caps => Ok(DisplayControlPdu::Caps(DisplayControlCapsPdu::decode(header, src)?)), + } } +} - /// Fully encodes a [`MonitorLayoutPdu`] with the given monitors. - pub fn encode_monitors(&self, channel_id: u32, monitors: Vec) -> PduResult> { - let mut buf = WriteBuf::new(); - let pdu = MonitorLayoutPdu::new(monitors); - encode_dvc_messages(channel_id, vec![Box::new(pdu)], None) +impl From for DisplayControlPdu { + fn from(pdu: MonitorLayoutPdu) -> Self { + DisplayControlPdu::MonitorLayout(pdu) } } -impl Default for DisplayControlClient { - fn default() -> Self { - Self::new() +impl From for DisplayControlPdu { + fn from(pdu: DisplayControlCapsPdu) -> Self { + DisplayControlPdu::Caps(pdu) } } /// [2.2.1.1] DISPLAYCONTROL_HEADER /// /// [2.2.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/3dceb555-2faf-4596-9e74-62be820df8ba +#[derive(Debug)] pub struct Header { pdu_type: DisplayControlType, length: usize, } impl Header { + const FIXED_PART_SIZE: usize = 4 /* pdu_type */ + 4 /* length */; + + pub fn decode(src: &mut ReadCursor<'_>) -> PduResult { + ensure_fixed_part_size!(in: src); + let pdu_type = DisplayControlType::try_from(src.read_u32())?; + let length = cast_length!("Length", src.read_u32())?; + Ok(Self { pdu_type, length }) + } + pub fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { ensure_size!(in: dst, size: Self::size()); dst.write_u32(cast_length!("Type", self.pdu_type)?); @@ -84,19 +112,22 @@ impl Header { } pub fn size() -> usize { - 4 /* pdu_type */ + 4 /* length */ + Self::FIXED_PART_SIZE } } /// [2.2.2.2] DISPLAYCONTROL_MONITOR_LAYOUT_PDU /// /// [2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/22741217-12a0-4fb8-b5a0-df43905aaf06 +#[derive(Debug)] pub struct MonitorLayoutPdu { header: Header, pub monitors: Vec, } impl MonitorLayoutPdu { + const FIXED_PART_SIZE: usize = 4 /* MonitorLayoutSize */ + 4 /* NumMonitors */; + pub fn new(monitors: Vec) -> Self { Self { header: Header { @@ -106,9 +137,18 @@ impl MonitorLayoutPdu { monitors, } } -} -impl PduEncode for MonitorLayoutPdu { + fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { + ensure_fixed_part_size!(in: src); + let monitor_layout_size = src.read_u32(); + let num_monitors = src.read_u32(); + let mut monitors = Vec::new(); + for _ in 0..num_monitors { + monitors.push(Monitor::decode(src)?); + } + Ok(Self { header, monitors }) + } + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { ensure_size!(in: dst, size: self.size()); self.header.encode(dst)?; @@ -129,11 +169,10 @@ impl PduEncode for MonitorLayoutPdu { } } -impl DvcPduEncode for MonitorLayoutPdu {} - /// [2.2.2.2.1] DISPLAYCONTROL_MONITOR_LAYOUT_PDU /// /// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c +#[derive(Debug)] pub struct Monitor { pub flags: MonitorFlags, pub left: u32, @@ -148,8 +187,38 @@ pub struct Monitor { } impl Monitor { + const FIXED_PART_SIZE: usize = 40; + + fn decode(src: &mut ReadCursor<'_>) -> PduResult { + ensure_fixed_part_size!(in: src); + let flags = MonitorFlags::from_bits(src.read_u32()) + .ok_or_else(|| invalid_message_err!("MonitorFlags", "Invalid MonitorFlags"))?; + let left = src.read_u32(); + let top = src.read_u32(); + let width = src.read_u32(); + let height = src.read_u32(); + let physical_width = src.read_u32(); + let physical_height = src.read_u32(); + let orientation = cast_length!("Orientation", src.read_u32())?; + let desktop_scale_factor = src.read_u32(); + let device_scale_factor = src.read_u32(); + + Ok(Self { + flags, + left, + top, + width, + height, + physical_width, + physical_height, + orientation, + desktop_scale_factor, + device_scale_factor, + }) + } + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_size!(in: dst, size: Self::size()); + ensure_fixed_part_size!(in: dst); dst.write_u32(self.flags.bits()); dst.write_u32(self.left); dst.write_u32(self.top); @@ -157,13 +226,13 @@ impl Monitor { dst.write_u32(self.height); dst.write_u32(self.physical_width); dst.write_u32(self.physical_height); - dst.write_u32(cast_length!("Orientation", self.orientation)?); + dst.write_u32(self.orientation.into()); dst.write_u32(self.desktop_scale_factor); dst.write_u32(self.device_scale_factor); Ok(()) } fn size() -> usize { - 40 + Self::FIXED_PART_SIZE } } @@ -183,21 +252,90 @@ pub enum Orientation { PortraitFlipped = 270, } -impl TryFrom for u32 { - type Error = core::convert::Infallible; - - fn try_from(value: Orientation) -> Result { - Ok(match value { +impl From for u32 { + fn from(value: Orientation) -> u32 { + match value { Orientation::Landscape => 0, Orientation::Portrait => 90, Orientation::LandscapeFlipped => 180, Orientation::PortraitFlipped => 270, + } + } +} + +impl TryFrom for Orientation { + type Error = PduError; + + fn try_from(value: u32) -> Result { + Ok(match value { + 0 => Orientation::Landscape, + 90 => Orientation::Portrait, + 180 => Orientation::LandscapeFlipped, + 270 => Orientation::PortraitFlipped, + _ => return Err(invalid_message_err!("Orientation", "Invalid Orientation")), }) } } +/// 2.2.2.1 DISPLAYCONTROL_CAPS_PDU +/// +/// [2.2.2.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/8989a211-984e-4ecc-80f3-60694fc4b476 +#[derive(Debug)] +pub struct DisplayControlCapsPdu { + header: Header, + pub max_num_monitors: u32, + pub max_monitor_area_factora: u32, + pub max_monitor_area_factorb: u32, +} + +impl DisplayControlCapsPdu { + const FIXED_PART_SIZE: usize = 4 /* MaxNumMonitors */ + 4 /* MaxMonitorAreaFactorA */ + 4 /* MaxMonitorAreaFactorB */; + + pub fn new(max_num_monitors: u32, max_monitor_area_factora: u32, max_monitor_area_factorb: u32) -> Self { + Self { + header: Header { + pdu_type: DisplayControlType::Caps, + length: Header::size() + Self::FIXED_PART_SIZE, + }, + max_num_monitors, + max_monitor_area_factora, + max_monitor_area_factorb, + } + } + + pub fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: self.size()); + self.header.encode(dst)?; + dst.write_u32(self.max_num_monitors); + dst.write_u32(self.max_monitor_area_factora); + dst.write_u32(self.max_monitor_area_factorb); + Ok(()) + } + + pub fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { + ensure_fixed_part_size!(in: src); + let max_num_monitors = src.read_u32(); + let max_monitor_area_factora = src.read_u32(); + let max_monitor_area_factorb = src.read_u32(); + Ok(Self { + header, + max_num_monitors, + max_monitor_area_factora, + max_monitor_area_factorb, + }) + } + + pub fn size(&self) -> usize { + self.header.length + } + + pub fn name(&self) -> &'static str { + "DISPLAYCONTROL_CAPS_PDU" + } +} + #[repr(u32)] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Debug)] pub enum DisplayControlType { /// DISPLAYCONTROL_PDU_TYPE_CAPS Caps = 0x00000005, @@ -205,13 +343,23 @@ pub enum DisplayControlType { MonitorLayout = 0x00000002, } -impl TryFrom for u32 { - type Error = core::convert::Infallible; +impl TryFrom for DisplayControlType { + type Error = PduError; - fn try_from(value: DisplayControlType) -> Result { + fn try_from(value: u32) -> Result { Ok(match value { + 0x05 => DisplayControlType::Caps, + 0x02 => DisplayControlType::MonitorLayout, + _ => return Err(invalid_message_err!("DisplayControlType", "Invalid DisplayControlType")), + }) + } +} + +impl From for u32 { + fn from(value: DisplayControlType) -> u32 { + match value { DisplayControlType::Caps => 0x05, DisplayControlType::MonitorLayout => 0x02, - }) + } } } diff --git a/crates/ironrdp-dvc/src/display/client.rs b/crates/ironrdp-dvc/src/display/client.rs new file mode 100644 index 000000000..2e98046fc --- /dev/null +++ b/crates/ironrdp-dvc/src/display/client.rs @@ -0,0 +1,48 @@ +use super::{DisplayControlPdu, Monitor, MonitorLayoutPdu, CHANNEL_NAME}; +use crate::{encode_dvc_messages, vec, Box, DvcClientProcessor, Vec}; +use crate::{DvcMessages, DvcProcessor}; +use ironrdp_pdu::{write_buf::WriteBuf, PduResult}; +use ironrdp_svc::{impl_as_any, SvcMessage}; + +/// A client for the Display Control Virtual Channel. +pub struct DisplayControlClient {} + +impl_as_any!(DisplayControlClient); + +impl DvcProcessor for DisplayControlClient { + fn channel_name(&self) -> &str { + CHANNEL_NAME + } + + fn start(&mut self, _channel_id: u32) -> PduResult { + Ok(Vec::new()) + } + + fn process(&mut self, channel_id: u32, payload: &[u8]) -> PduResult { + // TODO: We can parse the payload here for completeness sake, + // in practice we don't need to do anything with the payload. + debug!("Got Display PDU of length: {}", payload.len()); + Ok(Vec::new()) + } +} + +impl DvcClientProcessor for DisplayControlClient {} + +impl DisplayControlClient { + pub fn new() -> Self { + Self {} + } + + /// Fully encodes a [`MonitorLayoutPdu`] with the given monitors. + pub fn encode_monitors(&self, channel_id: u32, monitors: Vec) -> PduResult> { + let mut buf = WriteBuf::new(); + let pdu: DisplayControlPdu = MonitorLayoutPdu::new(monitors).into(); + encode_dvc_messages(channel_id, vec![Box::new(pdu)], None) + } +} + +impl Default for DisplayControlClient { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/ironrdp-dvc/src/display/server.rs b/crates/ironrdp-dvc/src/display/server.rs new file mode 100644 index 000000000..fcf6bc8ee --- /dev/null +++ b/crates/ironrdp-dvc/src/display/server.rs @@ -0,0 +1,41 @@ +use crate::vec; +use crate::Box; +use crate::DvcServerProcessor; +use ironrdp_pdu::decode; +use ironrdp_pdu::PduResult; +use ironrdp_svc::impl_as_any; + +use crate::{DvcMessages, DvcProcessor}; + +use super::{DisplayControlCapsPdu, DisplayControlPdu, CHANNEL_NAME}; + +/// A server for the Display Control Virtual Channel. +pub struct DisplayControlServer {} + +impl_as_any!(DisplayControlServer); + +impl DvcProcessor for DisplayControlServer { + fn channel_name(&self) -> &str { + CHANNEL_NAME + } + + fn start(&mut self, _channel_id: u32) -> PduResult { + let pdu: DisplayControlPdu = DisplayControlCapsPdu::new(1, 3840, 2400).into(); + + Ok(vec![Box::new(pdu)]) + } + + fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult { + match decode(payload)? { + DisplayControlPdu::MonitorLayout(layout) => { + debug!(?layout); + } + DisplayControlPdu::Caps(caps) => { + debug!(?caps); + } + } + Ok(vec![]) + } +} + +impl DvcServerProcessor for DisplayControlServer {} diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index afdc2c72a..207f225e7 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -22,9 +22,7 @@ use alloc::vec::Vec; #[rustfmt::skip] // do not re-order this pub use pub use ironrdp_pdu; use ironrdp_pdu::write_buf::WriteBuf; -use ironrdp_pdu::{ - assert_obj_safe, cast_length, custom_err, encode_vec, ensure_size, other_err, PduEncode, PduParsing as _, PduResult, -}; +use ironrdp_pdu::{assert_obj_safe, cast_length, custom_err, encode_vec, ensure_size, other_err, PduEncode, PduResult}; use ironrdp_svc::{self, impl_as_any, AsAny, SvcMessage}; mod complete_data; diff --git a/crates/ironrdp-dvc/src/server.rs b/crates/ironrdp-dvc/src/server.rs index 8f3771e1e..b127974fd 100644 --- a/crates/ironrdp-dvc/src/server.rs +++ b/crates/ironrdp-dvc/src/server.rs @@ -18,7 +18,7 @@ use pdu::rdp::vc; use pdu::write_buf::WriteBuf; use pdu::PduDecode as _; use pdu::PduResult; -use pdu::{cast_length, custom_err, encode_vec, invalid_message_err, other_err, PduEncode, PduParsing}; +use pdu::{cast_length, custom_err, encode_vec, invalid_message_err, other_err, PduEncode}; use slab::Slab; pub trait DvcServerProcessor: DvcProcessor {} diff --git a/crates/ironrdp-pdu/src/rdp/headers.rs b/crates/ironrdp-pdu/src/rdp/headers.rs index 09644b86b..3e266b69e 100644 --- a/crates/ironrdp-pdu/src/rdp/headers.rs +++ b/crates/ironrdp-pdu/src/rdp/headers.rs @@ -14,8 +14,6 @@ use crate::rdp::session_info::SaveSessionInfoPdu; use crate::rdp::suppress_output::SuppressOutputPdu; use crate::{PduDecode, PduEncode, PduResult}; -use super::capability_sets::CapabilitySetsError; - pub const BASIC_SECURITY_HEADER_SIZE: usize = 4; pub const SHARE_DATA_HEADER_COMPRESSION_MASK: u8 = 0xF; const SHARE_CONTROL_HEADER_MASK: u16 = 0xF; diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs index 1fd69f4d6..4fcc11bbb 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs @@ -1,2 +1 @@ -pub mod display; pub mod gfx; diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs deleted file mode 100644 index a236533ff..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs +++ /dev/null @@ -1,357 +0,0 @@ -use bitflags::bitflags; -use num_derive::{FromPrimitive, ToPrimitive}; -use num_traits::{FromPrimitive as _, ToPrimitive as _}; - -use crate::cursor::{ReadCursor, WriteCursor}; -use crate::{PduDecode, PduEncode, PduResult}; - -pub const CHANNEL_NAME: &str = "Microsoft::Windows::RDS::DisplayControl"; - -const RDP_DISPLAY_HEADER_SIZE: usize = 8; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DisplayControlCapsPdu { - pub max_num_monitors: u32, - pub max_monitor_area_factora: u32, - pub max_monitor_area_factorb: u32, -} - -impl DisplayControlCapsPdu { - const NAME: &'static str = "DisplayControlCapsPdu"; - - const FIXED_PART_SIZE: usize = 4 /* MaxNumMonitors */ + 4 /* MaxFactorA */ + 4 /* MaxFactorB */; -} - -impl PduEncode for DisplayControlCapsPdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_fixed_part_size!(in: dst); - - dst.write_u32(self.max_num_monitors); - dst.write_u32(self.max_monitor_area_factora); - dst.write_u32(self.max_monitor_area_factorb); - - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - Self::FIXED_PART_SIZE - } -} - -impl<'de> PduDecode<'de> for DisplayControlCapsPdu { - fn decode(src: &mut ReadCursor<'de>) -> PduResult { - ensure_fixed_part_size!(in: src); - - let max_num_monitors = src.read_u32(); - let max_monitor_area_factora = src.read_u32(); - let max_monitor_area_factorb = src.read_u32(); - - Ok(Self { - max_num_monitors, - max_monitor_area_factora, - max_monitor_area_factorb, - }) - } -} - -bitflags! { - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub struct MonitorFlags: u32 { - const PRIMARY = 1; - } -} - -#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] -pub enum Orientation { - Landscape = 0, - Portrait = 90, - LandscapeFlipped = 180, - PortraitFlipped = 270, -} - -/// [2.2.2.2.1] DISPLAYCONTROL_MONITOR_LAYOUT_PDU -/// -/// Deprecated in favor of the struct by the same name in crates/ironrdp-dvc. -/// -/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Monitor { - pub flags: MonitorFlags, - pub left: u32, - pub top: u32, - pub width: u32, - pub height: u32, - pub physical_width: u32, - pub physical_height: u32, - pub orientation: Orientation, - pub desktop_scale_factor: u32, - pub device_scale_factor: u32, -} - -const MONITOR_SIZE: usize = 40; -const MONITOR_PDU_HEADER_SIZE: usize = 8; - -impl Monitor { - const NAME: &'static str = "DisplayMonitor"; - - const FIXED_PART_SIZE: usize = MONITOR_SIZE; -} - -impl PduEncode for Monitor { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_fixed_part_size!(in: dst); - - dst.write_u32(self.flags.bits()); - dst.write_u32(self.left); - dst.write_u32(self.top); - dst.write_u32(self.width); - dst.write_u32(self.height); - dst.write_u32(self.physical_width); - dst.write_u32(self.physical_height); - dst.write_u32(self.orientation.to_u32().unwrap()); - dst.write_u32(self.desktop_scale_factor); - dst.write_u32(self.device_scale_factor); - - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - Self::FIXED_PART_SIZE - } -} - -impl<'de> PduDecode<'de> for Monitor { - fn decode(src: &mut ReadCursor<'de>) -> PduResult { - ensure_fixed_part_size!(in: src); - - let flags = MonitorFlags::from_bits_retain(src.read_u32()); - let left = src.read_u32(); - let top = src.read_u32(); - let width = src.read_u32(); - let height = src.read_u32(); - let physical_width = src.read_u32(); - let physical_height = src.read_u32(); - let orientation = Orientation::from_u32(src.read_u32()) - .ok_or_else(|| invalid_message_err!("orientation", "invalid value"))?; - let desktop_scale_factor = src.read_u32(); - let device_scale_factor = src.read_u32(); - - Ok(Self { - flags, - left, - top, - width, - height, - physical_width, - physical_height, - orientation, - desktop_scale_factor, - device_scale_factor, - }) - } -} - -/// [2.2.2.2] DISPLAYCONTROL_MONITOR_LAYOUT_PDU -/// -/// Deprecated in favor of the struct by the same name in crates/ironrdp-dvc. -/// -/// [2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/22741217-12a0-4fb8-b5a0-df43905aaf06 -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MonitorLayoutPdu { - pub monitors: Vec, -} - -impl MonitorLayoutPdu { - const NAME: &'static str = "MonitorLayoutPdu"; - - const FIXED_PART_SIZE: usize = MONITOR_PDU_HEADER_SIZE; -} - -impl PduEncode for MonitorLayoutPdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_size!(in: dst, size: self.size()); - - dst.write_u32(cast_length!("size", MONITOR_SIZE)?); - dst.write_u32(cast_length!("len", self.monitors.len())?); - - for monitor in &self.monitors { - monitor.encode(dst)?; - } - - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - MONITOR_PDU_HEADER_SIZE + self.monitors.len() * MONITOR_SIZE - } -} - -impl<'de> PduDecode<'de> for MonitorLayoutPdu { - fn decode(src: &mut ReadCursor<'de>) -> PduResult { - ensure_fixed_part_size!(in: src); - - let _size = src.read_u32(); - let num_monitors = src.read_u32(); - let mut monitors = Vec::new(); - for _ in 0..num_monitors { - monitors.push(Monitor::decode(src)?); - } - Ok(Self { monitors }) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ServerPdu { - DisplayControlCaps(DisplayControlCapsPdu), -} - -impl ServerPdu { - const NAME: &'static str = "DisplayServerPdu"; - - const FIXED_PART_SIZE: usize = RDP_DISPLAY_HEADER_SIZE; -} - -impl PduEncode for ServerPdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - let size = self.size(); - - ensure_size!(in: dst, size: size); - - dst.write_u32(ServerPduType::from(self).to_u32().unwrap()); - dst.write_u32(cast_length!("len", size)?); - - match self { - ServerPdu::DisplayControlCaps(pdu) => pdu.encode(dst), - } - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - RDP_DISPLAY_HEADER_SIZE - + match self { - ServerPdu::DisplayControlCaps(pdu) => pdu.size(), - } - } -} - -impl<'de> PduDecode<'de> for ServerPdu { - fn decode(src: &mut ReadCursor<'de>) -> PduResult { - ensure_fixed_part_size!(in: src); - - let pdu_type = ServerPduType::from_u32(src.read_u32()) - .ok_or_else(|| invalid_message_err!("pduType", "invalid PDU type"))?; - let pdu_length = src.read_u32() as usize; - - let server_pdu = match pdu_type { - ServerPduType::DisplayControlCaps => ServerPdu::DisplayControlCaps(DisplayControlCapsPdu::decode(src)?), - }; - let actual_size = server_pdu.size(); - - if actual_size != pdu_length { - Err(not_enough_bytes_err!(actual_size, pdu_length)) - } else { - Ok(server_pdu) - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] -pub enum ServerPduType { - DisplayControlCaps = 0x05, -} - -impl From<&ServerPdu> for ServerPduType { - fn from(s: &ServerPdu) -> Self { - match s { - ServerPdu::DisplayControlCaps(_) => Self::DisplayControlCaps, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ClientPdu { - DisplayControlMonitorLayout(MonitorLayoutPdu), -} - -impl ClientPdu { - const NAME: &'static str = "DisplayClientPdu"; - - const FIXED_PART_SIZE: usize = RDP_DISPLAY_HEADER_SIZE; -} - -impl PduEncode for ClientPdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - let size = self.size(); - - ensure_size!(in: dst, size: size); - - dst.write_u32(ClientPduType::from(self).to_u32().unwrap()); - dst.write_u32(cast_length!("len", size)?); - - match self { - ClientPdu::DisplayControlMonitorLayout(pdu) => pdu.encode(dst), - } - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - RDP_DISPLAY_HEADER_SIZE - + match self { - ClientPdu::DisplayControlMonitorLayout(pdu) => pdu.size(), - } - } -} - -impl<'de> PduDecode<'de> for ClientPdu { - fn decode(src: &mut ReadCursor<'de>) -> PduResult { - ensure_fixed_part_size!(in: src); - - let pdu_type = ClientPduType::from_u32(src.read_u32()) - .ok_or_else(|| invalid_message_err!("pduType", "invalid PDU type"))?; - let pdu_length = src.read_u32() as usize; - - let client_pdu = match pdu_type { - ClientPduType::DisplayControlMonitorLayout => { - ClientPdu::DisplayControlMonitorLayout(MonitorLayoutPdu::decode(src)?) - } - }; - let actual_size = client_pdu.size(); - - if actual_size != pdu_length { - Err(not_enough_bytes_err!(actual_size, pdu_length)) - } else { - Ok(client_pdu) - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] -pub enum ClientPduType { - DisplayControlMonitorLayout = 0x02, -} - -impl From<&ClientPdu> for ClientPduType { - fn from(s: &ClientPdu) -> Self { - match s { - ClientPdu::DisplayControlMonitorLayout(_) => Self::DisplayControlMonitorLayout, - } - } -} diff --git a/crates/ironrdp-server/src/server.rs b/crates/ironrdp-server/src/server.rs index ff60ab5cb..cda9be915 100644 --- a/crates/ironrdp-server/src/server.rs +++ b/crates/ironrdp-server/src/server.rs @@ -2,6 +2,7 @@ use std::net::SocketAddr; use std::sync::{Arc, Mutex}; use anyhow::{bail, Result}; +use dvc::display::server::DisplayControlServer; use ironrdp_acceptor::{self, Acceptor, AcceptorResult, BeginResult}; use ironrdp_cliprdr::backend::CliprdrBackendFactory; use ironrdp_cliprdr::CliprdrServer; @@ -10,7 +11,7 @@ use ironrdp_pdu::input::fast_path::{FastPathInput, FastPathInputEvent}; use ironrdp_pdu::input::InputEventPdu; use ironrdp_pdu::mcs::SendDataRequest; use ironrdp_pdu::rdp::capability_sets::{CapabilitySet, CmdFlags, GeneralExtraFlags}; -use ironrdp_pdu::{self, custom_err, decode, mcs, nego, rdp, Action, PduParsing, PduResult}; +use ironrdp_pdu::{self, decode, mcs, nego, rdp, Action, PduResult}; use ironrdp_svc::{impl_as_any, server_encode_svc_messages, StaticChannelSet}; use ironrdp_tokio::{Framed, FramedRead, FramedWrite, TokioFramed}; use tokio::net::{TcpListener, TcpStream}; @@ -42,41 +43,6 @@ impl RdpServerSecurity { } } -struct DisplayControlHandler {} - -impl_as_any!(DisplayControlHandler); - -impl dvc::DvcProcessor for DisplayControlHandler { - fn channel_name(&self) -> &str { - ironrdp_pdu::dvc::display::CHANNEL_NAME - } - - fn start(&mut self, _channel_id: u32) -> PduResult { - use ironrdp_pdu::dvc::display::{DisplayControlCapsPdu, ServerPdu}; - - let pdu = ServerPdu::DisplayControlCaps(DisplayControlCapsPdu { - max_num_monitors: 1, - max_monitor_area_factora: 3840, - max_monitor_area_factorb: 2400, - }); - - Ok(vec![Box::new(pdu)]) - } - - fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult { - use ironrdp_pdu::dvc::display::ClientPdu; - - match decode(payload)? { - ClientPdu::DisplayControlMonitorLayout(layout) => { - debug!(?layout); - } - } - Ok(vec![]) - } -} - -impl dvc::DvcServerProcessor for DisplayControlHandler {} - struct AInputHandler { handler: Arc>>, } @@ -222,7 +188,7 @@ impl RdpServer { .with_dynamic_channel(AInputHandler { handler: Arc::clone(&self.handler), }) - .with_dynamic_channel(DisplayControlHandler {}); + .with_dynamic_channel(DisplayControlServer {}); acceptor.attach_static_channel(dvc); match ironrdp_acceptor::accept_begin(framed, &mut acceptor).await { diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index d5d5d743e..f6b705a5f 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -12,7 +12,6 @@ use ironrdp_svc::{SvcProcessor, SvcProcessorMessages}; use crate::fast_path::UpdateKind; use crate::image::DecodedImage; -use crate::x224::GfxHandler; use crate::{fast_path, x224, SessionError, SessionErrorExt, SessionResult}; pub struct ActiveStage { From bc57654c9679e3983b36c417c74d95015e4c0456 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 17:09:40 -0700 Subject: [PATCH 30/84] Consolidate all display control pdus into ironrdp-displaycontrol --- Cargo.lock | 4 + crates/ironrdp-client/src/rdp.rs | 2 +- crates/ironrdp-displaycontrol/Cargo.toml | 5 +- .../src}/client.rs | 18 +- crates/ironrdp-displaycontrol/src/lib.rs | 4 + crates/ironrdp-displaycontrol/src/pdu/mod.rs | 26 ++ .../src}/server.rs | 17 +- crates/ironrdp-dvc/src/complete_data.rs | 48 +-- crates/ironrdp-dvc/src/display.rs | 365 ------------------ crates/ironrdp-dvc/src/lib.rs | 3 +- .../rdp/capability_sets/bitmap_cache/tests.rs | 4 +- .../capability_sets/bitmap_codecs/tests.rs | 4 +- .../rdp/capability_sets/glyph_cache/tests.rs | 2 +- .../client_new_license_request/tests.rs | 5 +- crates/ironrdp-server/Cargo.toml | 1 + crates/ironrdp-server/src/server.rs | 2 +- 16 files changed, 90 insertions(+), 420 deletions(-) rename crates/{ironrdp-dvc/src/display => ironrdp-displaycontrol/src}/client.rs (68%) rename crates/{ironrdp-dvc/src/display => ironrdp-displaycontrol/src}/server.rs (71%) delete mode 100644 crates/ironrdp-dvc/src/display.rs diff --git a/Cargo.lock b/Cargo.lock index 97007ebad..b3b47d356 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1770,7 +1770,10 @@ dependencies = [ name = "ironrdp-displaycontrol" version = "0.1.0" dependencies = [ + "ironrdp-dvc", "ironrdp-pdu", + "ironrdp-svc", + "tracing", ] [[package]] @@ -1902,6 +1905,7 @@ dependencies = [ "ironrdp-acceptor", "ironrdp-ainput", "ironrdp-cliprdr", + "ironrdp-displaycontrol", "ironrdp-dvc", "ironrdp-graphics", "ironrdp-pdu", diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 8a683e46b..8b8c89feb 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -104,7 +104,7 @@ async fn connect( let mut connector = connector::ClientConnector::new(config.connector.clone()) .with_server_addr(server_addr) - .with_static_channel(ironrdp::dvc::DrdynvcClient::new()) // FIXME(#61): drdynvc is not working + .with_static_channel(ironrdp::dvc::DrdynvcClient::new()) .with_static_channel(rdpsnd::Rdpsnd::new()) .with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0)); diff --git a/crates/ironrdp-displaycontrol/Cargo.toml b/crates/ironrdp-displaycontrol/Cargo.toml index e77bea4ee..d3ee97ce5 100644 --- a/crates/ironrdp-displaycontrol/Cargo.toml +++ b/crates/ironrdp-displaycontrol/Cargo.toml @@ -12,4 +12,7 @@ keywords.workspace = true categories.workspace = true [dependencies] -ironrdp-pdu.workspace = true \ No newline at end of file +ironrdp-dvc.workspace = true +ironrdp-pdu.workspace = true +ironrdp-svc.workspace = true +tracing.workspace = true diff --git a/crates/ironrdp-dvc/src/display/client.rs b/crates/ironrdp-displaycontrol/src/client.rs similarity index 68% rename from crates/ironrdp-dvc/src/display/client.rs rename to crates/ironrdp-displaycontrol/src/client.rs index 2e98046fc..2ccb2c8b6 100644 --- a/crates/ironrdp-dvc/src/display/client.rs +++ b/crates/ironrdp-displaycontrol/src/client.rs @@ -1,8 +1,11 @@ -use super::{DisplayControlPdu, Monitor, MonitorLayoutPdu, CHANNEL_NAME}; -use crate::{encode_dvc_messages, vec, Box, DvcClientProcessor, Vec}; -use crate::{DvcMessages, DvcProcessor}; -use ironrdp_pdu::{write_buf::WriteBuf, PduResult}; +use crate::{ + pdu::{DisplayControlMonitorLayout, DisplayControlPdu, MonitorLayoutEntry}, + CHANNEL_NAME, +}; +use ironrdp_dvc::{encode_dvc_messages, DvcClientProcessor, DvcMessages, DvcProcessor}; +use ironrdp_pdu::PduResult; use ironrdp_svc::{impl_as_any, SvcMessage}; +use tracing::debug; /// A client for the Display Control Virtual Channel. pub struct DisplayControlClient {} @@ -18,7 +21,7 @@ impl DvcProcessor for DisplayControlClient { Ok(Vec::new()) } - fn process(&mut self, channel_id: u32, payload: &[u8]) -> PduResult { + fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult { // TODO: We can parse the payload here for completeness sake, // in practice we don't need to do anything with the payload. debug!("Got Display PDU of length: {}", payload.len()); @@ -34,9 +37,8 @@ impl DisplayControlClient { } /// Fully encodes a [`MonitorLayoutPdu`] with the given monitors. - pub fn encode_monitors(&self, channel_id: u32, monitors: Vec) -> PduResult> { - let mut buf = WriteBuf::new(); - let pdu: DisplayControlPdu = MonitorLayoutPdu::new(monitors).into(); + pub fn encode_monitors(&self, channel_id: u32, monitors: Vec) -> PduResult> { + let pdu: DisplayControlPdu = DisplayControlMonitorLayout::new(&monitors)?.into(); encode_dvc_messages(channel_id, vec![Box::new(pdu)], None) } } diff --git a/crates/ironrdp-displaycontrol/src/lib.rs b/crates/ironrdp-displaycontrol/src/lib.rs index 4323cbd12..4df434bcf 100644 --- a/crates/ironrdp-displaycontrol/src/lib.rs +++ b/crates/ironrdp-displaycontrol/src/lib.rs @@ -1,3 +1,7 @@ #![doc = include_str!("../README.md")] +pub const CHANNEL_NAME: &str = "Microsoft::Windows::RDS::DisplayControl"; + +pub mod client; pub mod pdu; +pub mod server; diff --git a/crates/ironrdp-displaycontrol/src/pdu/mod.rs b/crates/ironrdp-displaycontrol/src/pdu/mod.rs index 4a528b0f6..5c460c831 100644 --- a/crates/ironrdp-displaycontrol/src/pdu/mod.rs +++ b/crates/ironrdp-displaycontrol/src/pdu/mod.rs @@ -2,6 +2,7 @@ //! //! [1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/d2954508-f487-48bc-8731-39743e0854a9 +use ironrdp_dvc::DvcPduEncode; use ironrdp_pdu::cursor::{ReadCursor, WriteCursor}; use ironrdp_pdu::{ensure_fixed_part_size, invalid_message_err, PduDecode, PduEncode, PduResult}; @@ -73,6 +74,8 @@ impl PduEncode for DisplayControlPdu { } } +impl DvcPduEncode for DisplayControlPdu {} + impl<'de> PduDecode<'de> for DisplayControlPdu { fn decode(src: &mut ReadCursor<'de>) -> PduResult { ensure_fixed_part_size!(in: src); @@ -99,12 +102,28 @@ impl<'de> PduDecode<'de> for DisplayControlPdu { } } +impl From for DisplayControlPdu { + fn from(caps: DisplayControlCapabilities) -> Self { + Self::Caps(caps) + } +} + +impl From for DisplayControlPdu { + fn from(layout: DisplayControlMonitorLayout) -> Self { + Self::MonitorLayout(layout) + } +} + +/// 2.2.2.1 DISPLAYCONTROL_CAPS_PDU +/// /// Display control channel capabilities PDU. /// /// INVARIANTS: /// 0 <= max_num_monitors <= MAX_SUPPORTED_MONITORS /// 0 <= max_monitor_area_factor_a <= MAX_MONITOR_AREA_FACTOR /// 0 <= max_monitor_area_factor_b <= MAX_MONITOR_AREA_FACTOR +/// +/// [2.2.2.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/8989a211-984e-4ecc-80f3-60694fc4b476 #[derive(Debug, Clone, PartialEq, Eq)] pub struct DisplayControlCapabilities { max_num_monitors: u32, @@ -179,10 +198,14 @@ impl<'de> PduDecode<'de> for DisplayControlCapabilities { } } +/// [2.2.2.2] DISPLAYCONTROL_MONITOR_LAYOUT_PDU +/// /// Sent from client to server to notify about new monitor layout (e.g screen resize). /// /// INVARIANTS: /// 0 <= monitors.length() <= MAX_SUPPORTED_MONITORS +/// +/// [2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/22741217-12a0-4fb8-b5a0-df43905aaf06 #[derive(Debug, Clone, PartialEq, Eq)] pub struct DisplayControlMonitorLayout { monitors: Vec, @@ -280,6 +303,9 @@ impl<'de> PduDecode<'de> for DisplayControlMonitorLayout { } } +/// [2.2.2.2.1] DISPLAYCONTROL_MONITOR_LAYOUT_PDU +/// +/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c #[derive(Debug, Clone, PartialEq, Eq)] pub struct MonitorLayoutEntry { is_primary: bool, diff --git a/crates/ironrdp-dvc/src/display/server.rs b/crates/ironrdp-displaycontrol/src/server.rs similarity index 71% rename from crates/ironrdp-dvc/src/display/server.rs rename to crates/ironrdp-displaycontrol/src/server.rs index fcf6bc8ee..ef13c53dd 100644 --- a/crates/ironrdp-dvc/src/display/server.rs +++ b/crates/ironrdp-displaycontrol/src/server.rs @@ -1,13 +1,12 @@ -use crate::vec; -use crate::Box; -use crate::DvcServerProcessor; -use ironrdp_pdu::decode; -use ironrdp_pdu::PduResult; +use ironrdp_dvc::{DvcMessages, DvcProcessor, DvcServerProcessor}; +use ironrdp_pdu::{decode, PduResult}; use ironrdp_svc::impl_as_any; +use tracing::debug; -use crate::{DvcMessages, DvcProcessor}; - -use super::{DisplayControlCapsPdu, DisplayControlPdu, CHANNEL_NAME}; +use crate::{ + pdu::{DisplayControlCapabilities, DisplayControlPdu}, + CHANNEL_NAME, +}; /// A server for the Display Control Virtual Channel. pub struct DisplayControlServer {} @@ -20,7 +19,7 @@ impl DvcProcessor for DisplayControlServer { } fn start(&mut self, _channel_id: u32) -> PduResult { - let pdu: DisplayControlPdu = DisplayControlCapsPdu::new(1, 3840, 2400).into(); + let pdu: DisplayControlPdu = DisplayControlCapabilities::new(1, 3840, 2400)?.into(); Ok(vec![Box::new(pdu)]) } diff --git a/crates/ironrdp-dvc/src/complete_data.rs b/crates/ironrdp-dvc/src/complete_data.rs index 9b63a1f8f..b90e5f3f4 100644 --- a/crates/ironrdp-dvc/src/complete_data.rs +++ b/crates/ironrdp-dvc/src/complete_data.rs @@ -46,33 +46,33 @@ impl CompleteData { fn process_data_pdu(&mut self, mut data: DataPdu) -> PduResult>> { if self.total_size == 0 && self.data.is_empty() { // message is not fragmented - Ok(Some(data.data)) - } else { - // message is fragmented so need to reassemble it - match self.data.len().checked_add(data.data.len()) { - Some(actual_data_length) => { - match actual_data_length.cmp(&(self.total_size)) { - cmp::Ordering::Less => { - // this is one of the fragmented messages, just append it - self.data.append(&mut data.data); - Ok(None) - } - cmp::Ordering::Equal => { - // this is the last fragmented message, need to return the whole reassembled message - self.total_size = 0; - self.data.append(&mut data.data); - Ok(Some(self.data.drain(..).collect())) - } - cmp::Ordering::Greater => { - error!("Actual DVC message size is grater than expected total DVC message size"); - self.total_size = 0; - self.data.clear(); - Ok(None) - } + return Ok(Some(data.data)); + } + + // message is fragmented so need to reassemble it + match self.data.len().checked_add(data.data.len()) { + Some(actual_data_length) => { + match actual_data_length.cmp(&(self.total_size)) { + cmp::Ordering::Less => { + // this is one of the fragmented messages, just append it + self.data.append(&mut data.data); + Ok(None) + } + cmp::Ordering::Equal => { + // this is the last fragmented message, need to return the whole reassembled message + self.total_size = 0; + self.data.append(&mut data.data); + Ok(Some(self.data.drain(..).collect())) + } + cmp::Ordering::Greater => { + error!("Actual DVC message size is grater than expected total DVC message size"); + self.total_size = 0; + self.data.clear(); + Ok(None) } } - _ => Err(invalid_message_err!("DVC message", "data", "overflow occurred")), } + _ => Err(invalid_message_err!("DVC message", "data", "overflow occurred")), } } } diff --git a/crates/ironrdp-dvc/src/display.rs b/crates/ironrdp-dvc/src/display.rs deleted file mode 100644 index b27a7e4c9..000000000 --- a/crates/ironrdp-dvc/src/display.rs +++ /dev/null @@ -1,365 +0,0 @@ -//! Display Control Virtual Channel -//! [[MS-RDPEDISP]] -//! -//! [[MS-RDPEDISP]]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/d2954508-f487-48bc-8731-39743e0854a9 -use crate::encode_dvc_messages; -use crate::vec; -use crate::Box; -use crate::DvcClientProcessor; -use crate::DvcMessages; -use crate::DvcPduEncode; -use crate::DvcProcessor; -use crate::PduResult; -use crate::SvcMessage; -use crate::Vec; -use bitflags::bitflags; -use ironrdp_pdu::cast_length; -use ironrdp_pdu::cursor::ReadCursor; -use ironrdp_pdu::cursor::WriteCursor; -use ironrdp_pdu::ensure_fixed_part_size; -use ironrdp_pdu::ensure_size; -use ironrdp_pdu::invalid_message_err; -use ironrdp_pdu::other_err; -use ironrdp_pdu::write_buf::WriteBuf; -use ironrdp_pdu::PduDecode; -use ironrdp_pdu::PduEncode; -use ironrdp_pdu::PduError; -use ironrdp_svc::impl_as_any; - -pub mod client; -pub mod server; - -pub const CHANNEL_NAME: &str = "Microsoft::Windows::RDS::DisplayControl"; - -pub enum DisplayControlPdu { - MonitorLayout(MonitorLayoutPdu), - Caps(DisplayControlCapsPdu), -} - -impl PduEncode for DisplayControlPdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - match self { - DisplayControlPdu::MonitorLayout(pdu) => pdu.encode(dst), - DisplayControlPdu::Caps(pdu) => pdu.encode(dst), - } - } - - fn name(&self) -> &'static str { - match self { - DisplayControlPdu::MonitorLayout(pdu) => pdu.name(), - DisplayControlPdu::Caps(pdu) => pdu.name(), - } - } - - fn size(&self) -> usize { - match self { - DisplayControlPdu::MonitorLayout(pdu) => pdu.size(), - DisplayControlPdu::Caps(pdu) => pdu.size(), - } - } -} - -impl DvcPduEncode for DisplayControlPdu {} - -impl PduDecode<'_> for DisplayControlPdu { - fn decode(src: &mut ReadCursor<'_>) -> PduResult { - let header = Header::decode(src)?; - match header.pdu_type { - DisplayControlType::MonitorLayout => { - Ok(DisplayControlPdu::MonitorLayout(MonitorLayoutPdu::decode(header, src)?)) - } - DisplayControlType::Caps => Ok(DisplayControlPdu::Caps(DisplayControlCapsPdu::decode(header, src)?)), - } - } -} - -impl From for DisplayControlPdu { - fn from(pdu: MonitorLayoutPdu) -> Self { - DisplayControlPdu::MonitorLayout(pdu) - } -} - -impl From for DisplayControlPdu { - fn from(pdu: DisplayControlCapsPdu) -> Self { - DisplayControlPdu::Caps(pdu) - } -} - -/// [2.2.1.1] DISPLAYCONTROL_HEADER -/// -/// [2.2.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/3dceb555-2faf-4596-9e74-62be820df8ba -#[derive(Debug)] -pub struct Header { - pdu_type: DisplayControlType, - length: usize, -} - -impl Header { - const FIXED_PART_SIZE: usize = 4 /* pdu_type */ + 4 /* length */; - - pub fn decode(src: &mut ReadCursor<'_>) -> PduResult { - ensure_fixed_part_size!(in: src); - let pdu_type = DisplayControlType::try_from(src.read_u32())?; - let length = cast_length!("Length", src.read_u32())?; - Ok(Self { pdu_type, length }) - } - - pub fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_size!(in: dst, size: Self::size()); - dst.write_u32(cast_length!("Type", self.pdu_type)?); - dst.write_u32(cast_length!("Length", self.length)?); - Ok(()) - } - - pub fn size() -> usize { - Self::FIXED_PART_SIZE - } -} - -/// [2.2.2.2] DISPLAYCONTROL_MONITOR_LAYOUT_PDU -/// -/// [2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/22741217-12a0-4fb8-b5a0-df43905aaf06 -#[derive(Debug)] -pub struct MonitorLayoutPdu { - header: Header, - pub monitors: Vec, -} - -impl MonitorLayoutPdu { - const FIXED_PART_SIZE: usize = 4 /* MonitorLayoutSize */ + 4 /* NumMonitors */; - - pub fn new(monitors: Vec) -> Self { - Self { - header: Header { - pdu_type: DisplayControlType::MonitorLayout, - length: (Header::size() + 4 /* MonitorLayoutSize */ + 4 /* NumMonitors */ + (monitors.len() * Monitor::size())), - }, - monitors, - } - } - - fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { - ensure_fixed_part_size!(in: src); - let monitor_layout_size = src.read_u32(); - let num_monitors = src.read_u32(); - let mut monitors = Vec::new(); - for _ in 0..num_monitors { - monitors.push(Monitor::decode(src)?); - } - Ok(Self { header, monitors }) - } - - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_size!(in: dst, size: self.size()); - self.header.encode(dst)?; - dst.write_u32(cast_length!("MonitorLayoutSize", Monitor::size())?); - dst.write_u32(cast_length!("NumMonitors", self.monitors.len())?); - for monitor in &self.monitors { - monitor.encode(dst)?; - } - Ok(()) - } - - fn name(&self) -> &'static str { - "DISPLAYCONTROL_MONITOR_LAYOUT_PDU" - } - - fn size(&self) -> usize { - self.header.length - } -} - -/// [2.2.2.2.1] DISPLAYCONTROL_MONITOR_LAYOUT_PDU -/// -/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c -#[derive(Debug)] -pub struct Monitor { - pub flags: MonitorFlags, - pub left: u32, - pub top: u32, - pub width: u32, - pub height: u32, - pub physical_width: u32, - pub physical_height: u32, - pub orientation: Orientation, - pub desktop_scale_factor: u32, - pub device_scale_factor: u32, -} - -impl Monitor { - const FIXED_PART_SIZE: usize = 40; - - fn decode(src: &mut ReadCursor<'_>) -> PduResult { - ensure_fixed_part_size!(in: src); - let flags = MonitorFlags::from_bits(src.read_u32()) - .ok_or_else(|| invalid_message_err!("MonitorFlags", "Invalid MonitorFlags"))?; - let left = src.read_u32(); - let top = src.read_u32(); - let width = src.read_u32(); - let height = src.read_u32(); - let physical_width = src.read_u32(); - let physical_height = src.read_u32(); - let orientation = cast_length!("Orientation", src.read_u32())?; - let desktop_scale_factor = src.read_u32(); - let device_scale_factor = src.read_u32(); - - Ok(Self { - flags, - left, - top, - width, - height, - physical_width, - physical_height, - orientation, - desktop_scale_factor, - device_scale_factor, - }) - } - - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_fixed_part_size!(in: dst); - dst.write_u32(self.flags.bits()); - dst.write_u32(self.left); - dst.write_u32(self.top); - dst.write_u32(self.width); - dst.write_u32(self.height); - dst.write_u32(self.physical_width); - dst.write_u32(self.physical_height); - dst.write_u32(self.orientation.into()); - dst.write_u32(self.desktop_scale_factor); - dst.write_u32(self.device_scale_factor); - Ok(()) - } - fn size() -> usize { - Self::FIXED_PART_SIZE - } -} - -bitflags! { - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub struct MonitorFlags: u32 { - const PRIMARY = 1; - } -} - -#[repr(u32)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Orientation { - Landscape = 0, - Portrait = 90, - LandscapeFlipped = 180, - PortraitFlipped = 270, -} - -impl From for u32 { - fn from(value: Orientation) -> u32 { - match value { - Orientation::Landscape => 0, - Orientation::Portrait => 90, - Orientation::LandscapeFlipped => 180, - Orientation::PortraitFlipped => 270, - } - } -} - -impl TryFrom for Orientation { - type Error = PduError; - - fn try_from(value: u32) -> Result { - Ok(match value { - 0 => Orientation::Landscape, - 90 => Orientation::Portrait, - 180 => Orientation::LandscapeFlipped, - 270 => Orientation::PortraitFlipped, - _ => return Err(invalid_message_err!("Orientation", "Invalid Orientation")), - }) - } -} - -/// 2.2.2.1 DISPLAYCONTROL_CAPS_PDU -/// -/// [2.2.2.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/8989a211-984e-4ecc-80f3-60694fc4b476 -#[derive(Debug)] -pub struct DisplayControlCapsPdu { - header: Header, - pub max_num_monitors: u32, - pub max_monitor_area_factora: u32, - pub max_monitor_area_factorb: u32, -} - -impl DisplayControlCapsPdu { - const FIXED_PART_SIZE: usize = 4 /* MaxNumMonitors */ + 4 /* MaxMonitorAreaFactorA */ + 4 /* MaxMonitorAreaFactorB */; - - pub fn new(max_num_monitors: u32, max_monitor_area_factora: u32, max_monitor_area_factorb: u32) -> Self { - Self { - header: Header { - pdu_type: DisplayControlType::Caps, - length: Header::size() + Self::FIXED_PART_SIZE, - }, - max_num_monitors, - max_monitor_area_factora, - max_monitor_area_factorb, - } - } - - pub fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_size!(in: dst, size: self.size()); - self.header.encode(dst)?; - dst.write_u32(self.max_num_monitors); - dst.write_u32(self.max_monitor_area_factora); - dst.write_u32(self.max_monitor_area_factorb); - Ok(()) - } - - pub fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { - ensure_fixed_part_size!(in: src); - let max_num_monitors = src.read_u32(); - let max_monitor_area_factora = src.read_u32(); - let max_monitor_area_factorb = src.read_u32(); - Ok(Self { - header, - max_num_monitors, - max_monitor_area_factora, - max_monitor_area_factorb, - }) - } - - pub fn size(&self) -> usize { - self.header.length - } - - pub fn name(&self) -> &'static str { - "DISPLAYCONTROL_CAPS_PDU" - } -} - -#[repr(u32)] -#[derive(Clone, Copy, Debug)] -pub enum DisplayControlType { - /// DISPLAYCONTROL_PDU_TYPE_CAPS - Caps = 0x00000005, - /// DISPLAYCONTROL_PDU_TYPE_MONITOR_LAYOUT - MonitorLayout = 0x00000002, -} - -impl TryFrom for DisplayControlType { - type Error = PduError; - - fn try_from(value: u32) -> Result { - Ok(match value { - 0x05 => DisplayControlType::Caps, - 0x02 => DisplayControlType::MonitorLayout, - _ => return Err(invalid_message_err!("DisplayControlType", "Invalid DisplayControlType")), - }) - } -} - -impl From for u32 { - fn from(value: DisplayControlType) -> u32 { - match value { - DisplayControlType::Caps => 0x05, - DisplayControlType::MonitorLayout => 0x02, - } - } -} diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 207f225e7..b217782df 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -34,7 +34,6 @@ pub use client::*; mod server; pub use server::*; -pub mod display; pub mod pdu; /// Represents a message that, when encoded, forms a complete PDU for a given dynamic virtual channel. @@ -70,7 +69,7 @@ assert_obj_safe!(DvcProcessor); const DATA_MAX_SIZE: usize = 1590; -pub(crate) fn encode_dvc_messages( +pub fn encode_dvc_messages( channel_id: u32, messages: DvcMessages, flags: Option, diff --git a/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_cache/tests.rs b/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_cache/tests.rs index e62eea8e2..9f2891ea1 100644 --- a/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_cache/tests.rs +++ b/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_cache/tests.rs @@ -137,7 +137,7 @@ fn from_buffer_correctly_parses_cell_info() { #[test] fn to_buffer_correctly_serializes_cell_info() { - let cell_info = CELL_INFO.clone(); + let cell_info = *CELL_INFO; let buffer = encode_vec(&cell_info).unwrap(); @@ -156,7 +156,7 @@ fn from_buffer_correctly_parses_cache_entry() { #[test] fn to_buffer_correctly_serializes_cache_entry() { - let cache_entry = CACHE_ENTRY.clone(); + let cache_entry = *CACHE_ENTRY; let buffer = encode_vec(&cache_entry).unwrap(); diff --git a/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs/tests.rs b/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs/tests.rs index 940d6b91f..5a31b9cb6 100644 --- a/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs/tests.rs +++ b/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs/tests.rs @@ -508,7 +508,7 @@ fn ns_codec_with_too_high_color_loss_level_handled_correctly() { }), }; - assert_eq!(codec, decode(&mut codec_buffer.as_slice()).unwrap()); + assert_eq!(codec, decode(codec_buffer.as_slice()).unwrap()); } #[test] @@ -531,5 +531,5 @@ fn ns_codec_with_too_low_color_loss_level_handled_correctly() { }), }; - assert_eq!(codec, decode(&mut codec_buffer.as_slice()).unwrap()); + assert_eq!(codec, decode(codec_buffer.as_slice()).unwrap()); } diff --git a/crates/ironrdp-pdu/src/rdp/capability_sets/glyph_cache/tests.rs b/crates/ironrdp-pdu/src/rdp/capability_sets/glyph_cache/tests.rs index 1fe09ab2a..ceda66ab8 100644 --- a/crates/ironrdp-pdu/src/rdp/capability_sets/glyph_cache/tests.rs +++ b/crates/ironrdp-pdu/src/rdp/capability_sets/glyph_cache/tests.rs @@ -96,7 +96,7 @@ fn from_buffer_correctly_parses_cache_definition() { #[test] fn to_buffer_correctly_serializes_cache_definition() { - let cache_def = CACHE_DEFINITION.clone(); + let cache_def = *CACHE_DEFINITION; let buffer = encode_vec(&cache_def).unwrap(); diff --git a/crates/ironrdp-pdu/src/rdp/server_license/client_new_license_request/tests.rs b/crates/ironrdp-pdu/src/rdp/server_license/client_new_license_request/tests.rs index 3c243f821..f5c85e3da 100644 --- a/crates/ironrdp-pdu/src/rdp/server_license/client_new_license_request/tests.rs +++ b/crates/ironrdp-pdu/src/rdp/server_license/client_new_license_request/tests.rs @@ -264,10 +264,7 @@ lazy_static! { #[test] fn from_buffer_correctly_parses_client_new_license_request() { - assert_eq!( - *CLIENT_NEW_LICENSE_REQUEST, - decode(&mut REQUEST_BUFFER.as_slice()).unwrap() - ); + assert_eq!(*CLIENT_NEW_LICENSE_REQUEST, decode(REQUEST_BUFFER.as_slice()).unwrap()); } #[test] diff --git a/crates/ironrdp-server/Cargo.toml b/crates/ironrdp-server/Cargo.toml index 35f085ece..4703e8bd3 100644 --- a/crates/ironrdp-server/Cargo.toml +++ b/crates/ironrdp-server/Cargo.toml @@ -24,6 +24,7 @@ ironrdp-ainput.workspace = true ironrdp-pdu.workspace = true ironrdp-svc.workspace = true ironrdp-cliprdr.workspace = true +ironrdp-displaycontrol.workspace = true ironrdp-dvc.workspace = true ironrdp-tokio.workspace = true ironrdp-acceptor.workspace = true diff --git a/crates/ironrdp-server/src/server.rs b/crates/ironrdp-server/src/server.rs index cda9be915..8d373a931 100644 --- a/crates/ironrdp-server/src/server.rs +++ b/crates/ironrdp-server/src/server.rs @@ -2,10 +2,10 @@ use std::net::SocketAddr; use std::sync::{Arc, Mutex}; use anyhow::{bail, Result}; -use dvc::display::server::DisplayControlServer; use ironrdp_acceptor::{self, Acceptor, AcceptorResult, BeginResult}; use ironrdp_cliprdr::backend::CliprdrBackendFactory; use ironrdp_cliprdr::CliprdrServer; +use ironrdp_displaycontrol::server::DisplayControlServer; use ironrdp_dvc as dvc; use ironrdp_pdu::input::fast_path::{FastPathInput, FastPathInputEvent}; use ironrdp_pdu::input::InputEventPdu; From 205eacf3553536f1d41b98e85f5767ba4d9c1f25 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 17:20:58 -0700 Subject: [PATCH 31/84] cleaning up --- crates/ironrdp-displaycontrol/src/client.rs | 6 ++-- crates/ironrdp-displaycontrol/src/server.rs | 6 ++-- crates/ironrdp-dvc/src/client.rs | 20 +++++-------- crates/ironrdp-dvc/src/lib.rs | 31 ++++++--------------- crates/ironrdp-dvc/src/pdu.rs | 8 +++--- crates/ironrdp-dvc/src/server.rs | 15 +++------- crates/ironrdp-server/src/server.rs | 4 +-- 7 files changed, 31 insertions(+), 59 deletions(-) diff --git a/crates/ironrdp-displaycontrol/src/client.rs b/crates/ironrdp-displaycontrol/src/client.rs index 2ccb2c8b6..c82f9fb0a 100644 --- a/crates/ironrdp-displaycontrol/src/client.rs +++ b/crates/ironrdp-displaycontrol/src/client.rs @@ -2,7 +2,7 @@ use crate::{ pdu::{DisplayControlMonitorLayout, DisplayControlPdu, MonitorLayoutEntry}, CHANNEL_NAME, }; -use ironrdp_dvc::{encode_dvc_messages, DvcClientProcessor, DvcMessages, DvcProcessor}; +use ironrdp_dvc::{encode_dvc_messages, DvcClientProcessor, DvcMessage, DvcProcessor}; use ironrdp_pdu::PduResult; use ironrdp_svc::{impl_as_any, SvcMessage}; use tracing::debug; @@ -17,11 +17,11 @@ impl DvcProcessor for DisplayControlClient { CHANNEL_NAME } - fn start(&mut self, _channel_id: u32) -> PduResult { + fn start(&mut self, _channel_id: u32) -> PduResult> { Ok(Vec::new()) } - fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult { + fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult> { // TODO: We can parse the payload here for completeness sake, // in practice we don't need to do anything with the payload. debug!("Got Display PDU of length: {}", payload.len()); diff --git a/crates/ironrdp-displaycontrol/src/server.rs b/crates/ironrdp-displaycontrol/src/server.rs index ef13c53dd..69ace518d 100644 --- a/crates/ironrdp-displaycontrol/src/server.rs +++ b/crates/ironrdp-displaycontrol/src/server.rs @@ -1,4 +1,4 @@ -use ironrdp_dvc::{DvcMessages, DvcProcessor, DvcServerProcessor}; +use ironrdp_dvc::{DvcMessage, DvcProcessor, DvcServerProcessor}; use ironrdp_pdu::{decode, PduResult}; use ironrdp_svc::impl_as_any; use tracing::debug; @@ -18,13 +18,13 @@ impl DvcProcessor for DisplayControlServer { CHANNEL_NAME } - fn start(&mut self, _channel_id: u32) -> PduResult { + fn start(&mut self, _channel_id: u32) -> PduResult> { let pdu: DisplayControlPdu = DisplayControlCapabilities::new(1, 3840, 2400)?.into(); Ok(vec![Box::new(pdu)]) } - fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult { + fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult> { match decode(payload)? { DisplayControlPdu::MonitorLayout(layout) => { debug!(?layout); diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 8d812b8af..0112a478f 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -1,25 +1,19 @@ -use crate::complete_data::CompleteData; use crate::pdu::{ CapabilitiesResponsePdu, CapsVersion, ClosePdu, CreateResponsePdu, CreationStatus, DrdynvcClientPdu, - DrdynvcDataPdu, DrdynvcServerPdu, + DrdynvcServerPdu, }; -use crate::{encode_dvc_messages, DvcMessages, DvcProcessor, DynamicChannelId, DynamicChannelSet}; -use alloc::borrow::ToOwned; -use alloc::boxed::Box; -use alloc::collections::BTreeMap; -use alloc::string::String; +use crate::{encode_dvc_messages, DvcProcessor, DynamicChannelId, DynamicChannelSet}; use alloc::vec; use alloc::vec::Vec; -use core::any::{Any, TypeId}; -use core::{cmp, fmt}; +use core::any::TypeId; +use core::fmt; use ironrdp_pdu as pdu; -use ironrdp_svc::{impl_as_any, CompressionCondition, SvcClientProcessor, SvcMessage, SvcPduEncode, SvcProcessor}; -use pdu::cursor::{ReadCursor, WriteCursor}; +use ironrdp_svc::{impl_as_any, CompressionCondition, SvcClientProcessor, SvcMessage, SvcProcessor}; +use pdu::cursor::ReadCursor; use pdu::gcc::ChannelName; -use pdu::rdp::vc; +use pdu::other_err; use pdu::PduDecode as _; use pdu::PduResult; -use pdu::{other_err, PduEncode}; pub trait DvcClientProcessor: DvcProcessor {} diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index b217782df..13d24c12b 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -1,7 +1,4 @@ #![cfg_attr(not(feature = "std"), no_std)] -#![allow(unused)] // FIXME(#61): remove this annotation - -// TODO: this crate is WIP use crate::alloc::borrow::ToOwned; #[macro_use] @@ -17,13 +14,11 @@ use alloc::boxed::Box; use alloc::collections::BTreeMap; use alloc::vec; use alloc::vec::Vec; - // Re-export ironrdp_pdu crate for convenience #[rustfmt::skip] // do not re-order this pub use pub use ironrdp_pdu; -use ironrdp_pdu::write_buf::WriteBuf; -use ironrdp_pdu::{assert_obj_safe, cast_length, custom_err, encode_vec, ensure_size, other_err, PduEncode, PduResult}; -use ironrdp_svc::{self, impl_as_any, AsAny, SvcMessage}; +use ironrdp_pdu::{assert_obj_safe, cast_length, encode_vec, other_err, PduEncode, PduResult}; +use ironrdp_svc::{self, AsAny, SvcMessage}; mod complete_data; use complete_data::CompleteData; @@ -41,7 +36,6 @@ pub mod pdu; /// (being split into multiple of such PDUs if necessary). pub trait DvcPduEncode: PduEncode + Send {} pub type DvcMessage = Box; -pub type DvcMessages = Vec; /// We implement `DvcPduEncode` for `Vec` for legacy reasons. impl DvcPduEncode for Vec {} @@ -58,11 +52,11 @@ pub trait DvcProcessor: AsAny + Send + Sync { /// Returns any messages that should be sent immediately /// upon the channel being created. - fn start(&mut self, channel_id: u32) -> PduResult; + fn start(&mut self, channel_id: u32) -> PduResult>; - fn process(&mut self, channel_id: u32, payload: &[u8]) -> PduResult; + fn process(&mut self, channel_id: u32, payload: &[u8]) -> PduResult>; - fn close(&mut self, channel_id: u32) {} + fn close(&mut self, _channel_id: u32) {} } assert_obj_safe!(DvcProcessor); @@ -71,7 +65,7 @@ const DATA_MAX_SIZE: usize = 1590; pub fn encode_dvc_messages( channel_id: u32, - messages: DvcMessages, + messages: Vec, flags: Option, ) -> PduResult> { let mut res = Vec::new(); @@ -125,11 +119,11 @@ impl DynamicVirtualChannel { } } - fn start(&mut self, channel_id: DynamicChannelId) -> PduResult { + fn start(&mut self, channel_id: DynamicChannelId) -> PduResult> { self.channel_processor.start(channel_id) } - fn process(&mut self, pdu: DrdynvcDataPdu) -> PduResult { + fn process(&mut self, pdu: DrdynvcDataPdu) -> PduResult> { let channel_id = pdu.channel_id(); let complete_data = self.complete_data.process_data(pdu)?; if let Some(complete_data) = complete_data { @@ -146,10 +140,6 @@ impl DynamicVirtualChannel { fn channel_processor_downcast_ref(&self) -> Option<&T> { self.channel_processor.as_any().downcast_ref() } - - fn channel_processor_downcast_mut(&mut self) -> Option<&mut T> { - self.channel_processor.as_any_mut().downcast_mut() - } } struct DynamicChannelSet { @@ -177,7 +167,6 @@ impl DynamicChannelSet { } pub fn attach_channel_id(&mut self, name: DynamicChannelName, id: DynamicChannelId) -> Option { - let channel = self.get_by_channel_name_mut(&name)?; self.channel_id_to_name.insert(id, name.clone()); self.name_to_channel_id.insert(name, id) } @@ -198,10 +187,6 @@ impl DynamicChannelSet { self.channels.get_mut(name) } - pub fn get_by_channel_id(&self, id: &DynamicChannelId) -> Option<&DynamicVirtualChannel> { - self.channel_id_to_name.get(id).and_then(|name| self.channels.get(name)) - } - pub fn get_by_channel_id_mut(&mut self, id: &DynamicChannelId) -> Option<&mut DynamicVirtualChannel> { self.channel_id_to_name .get(id) diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index 1ec60a0a2..9a318db22 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -6,7 +6,7 @@ use alloc::format; use ironrdp_pdu::{ cast_length, cursor::{ReadCursor, WriteCursor}, - ensure_fixed_part_size, ensure_size, invalid_message_err, unexpected_message_type_err, unsupported_pdu_err, + ensure_size, invalid_message_err, unsupported_pdu_err, utils::{encoded_str_len, read_string_from_cursor, write_string_to_cursor, CharacterSet}, PduDecode, PduEncode, PduError, PduResult, }; @@ -685,7 +685,7 @@ impl CapabilitiesRequestPdu { match self { CapabilitiesRequestPdu::V1 { header } | CapabilitiesRequestPdu::V2 { header, .. } - | CapabilitiesRequestPdu::V3 { header, .. } => header.encode(dst), + | CapabilitiesRequestPdu::V3 { header, .. } => header.encode(dst)?, }; dst.write_u8(0x00); // Pad, MUST be 0x00 match self { @@ -706,7 +706,7 @@ impl CapabilitiesRequestPdu { fn size(&self) -> usize { match self { - Self::V1 { header } => Self::FIXED_PART_SIZE, + Self::V1 { .. } => Self::FIXED_PART_SIZE, _ => Self::FIXED_PART_SIZE + Self::PRIORITY_CHARGES_SIZE, } } @@ -754,7 +754,7 @@ impl CreateRequestPdu { ensure_size!(in: dst, size: self.size()); self.header.encode(dst)?; self.header.cb_id.encode_val(self.channel_id, dst)?; - write_string_to_cursor(dst, &self.channel_name, CharacterSet::Ansi, true); + write_string_to_cursor(dst, &self.channel_name, CharacterSet::Ansi, true)?; Ok(()) } diff --git a/crates/ironrdp-dvc/src/server.rs b/crates/ironrdp-dvc/src/server.rs index b127974fd..c731cdd77 100644 --- a/crates/ironrdp-dvc/src/server.rs +++ b/crates/ironrdp-dvc/src/server.rs @@ -1,24 +1,17 @@ use crate::pdu::{ - CapabilitiesRequestPdu, CapsVersion, CreateRequestPdu, CreationStatus, DrdynvcClientPdu, DrdynvcDataPdu, - DrdynvcServerPdu, + CapabilitiesRequestPdu, CapsVersion, CreateRequestPdu, CreationStatus, DrdynvcClientPdu, DrdynvcServerPdu, }; -use crate::{encode_dvc_messages, CompleteData, DvcMessages, DvcProcessor}; -use alloc::borrow::ToOwned; +use crate::{encode_dvc_messages, CompleteData, DvcProcessor}; use alloc::boxed::Box; -use alloc::collections::BTreeMap; -use alloc::string::String; use alloc::vec::Vec; -use core::any::Any; use core::fmt; use ironrdp_pdu as pdu; use ironrdp_svc::{impl_as_any, ChannelFlags, CompressionCondition, SvcMessage, SvcProcessor, SvcServerProcessor}; -use pdu::cursor::{ReadCursor, WriteCursor}; +use pdu::cursor::ReadCursor; use pdu::gcc::ChannelName; -use pdu::rdp::vc; -use pdu::write_buf::WriteBuf; use pdu::PduDecode as _; use pdu::PduResult; -use pdu::{cast_length, custom_err, encode_vec, invalid_message_err, other_err, PduEncode}; +use pdu::{cast_length, custom_err, invalid_message_err}; use slab::Slab; pub trait DvcServerProcessor: DvcProcessor {} diff --git a/crates/ironrdp-server/src/server.rs b/crates/ironrdp-server/src/server.rs index 8d373a931..bcf2451d1 100644 --- a/crates/ironrdp-server/src/server.rs +++ b/crates/ironrdp-server/src/server.rs @@ -54,7 +54,7 @@ impl dvc::DvcProcessor for AInputHandler { ironrdp_ainput::CHANNEL_NAME } - fn start(&mut self, _channel_id: u32) -> PduResult { + fn start(&mut self, _channel_id: u32) -> PduResult> { use ironrdp_ainput::{ServerPdu, VersionPdu}; let pdu = ServerPdu::Version(VersionPdu::default()); @@ -64,7 +64,7 @@ impl dvc::DvcProcessor for AInputHandler { fn close(&mut self, _channel_id: u32) {} - fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult { + fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult> { use ironrdp_ainput::ClientPdu; match decode(payload)? { From dd4906a5c2e11bfc86073b717e876ff3e99e0af1 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 17:24:20 -0700 Subject: [PATCH 32/84] empty flags in lieu of Option --- crates/ironrdp-displaycontrol/src/client.rs | 4 ++-- crates/ironrdp-dvc/src/client.rs | 6 +++--- crates/ironrdp-dvc/src/lib.rs | 8 +++----- crates/ironrdp-dvc/src/server.rs | 8 ++------ 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/crates/ironrdp-displaycontrol/src/client.rs b/crates/ironrdp-displaycontrol/src/client.rs index c82f9fb0a..54a1d6480 100644 --- a/crates/ironrdp-displaycontrol/src/client.rs +++ b/crates/ironrdp-displaycontrol/src/client.rs @@ -4,7 +4,7 @@ use crate::{ }; use ironrdp_dvc::{encode_dvc_messages, DvcClientProcessor, DvcMessage, DvcProcessor}; use ironrdp_pdu::PduResult; -use ironrdp_svc::{impl_as_any, SvcMessage}; +use ironrdp_svc::{impl_as_any, ChannelFlags, SvcMessage}; use tracing::debug; /// A client for the Display Control Virtual Channel. @@ -39,7 +39,7 @@ impl DisplayControlClient { /// Fully encodes a [`MonitorLayoutPdu`] with the given monitors. pub fn encode_monitors(&self, channel_id: u32, monitors: Vec) -> PduResult> { let pdu: DisplayControlPdu = DisplayControlMonitorLayout::new(&monitors)?.into(); - encode_dvc_messages(channel_id, vec![Box::new(pdu)], None) + encode_dvc_messages(channel_id, vec![Box::new(pdu)], ChannelFlags::empty()) } } diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 0112a478f..a5410b7a2 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -8,7 +8,7 @@ use alloc::vec::Vec; use core::any::TypeId; use core::fmt; use ironrdp_pdu as pdu; -use ironrdp_svc::{impl_as_any, CompressionCondition, SvcClientProcessor, SvcMessage, SvcProcessor}; +use ironrdp_svc::{impl_as_any, ChannelFlags, CompressionCondition, SvcClientProcessor, SvcMessage, SvcProcessor}; use pdu::cursor::ReadCursor; use pdu::gcc::ChannelName; use pdu::other_err; @@ -140,7 +140,7 @@ impl SvcProcessor for DrdynvcClient { // If this DVC has start messages, send them. if !start_messages.is_empty() { - responses.extend(encode_dvc_messages(channel_id, start_messages, None)?); + responses.extend(encode_dvc_messages(channel_id, start_messages, ChannelFlags::empty())?); } } DrdynvcServerPdu::Close(close_request) => { @@ -161,7 +161,7 @@ impl SvcProcessor for DrdynvcClient { .ok_or_else(|| other_err!("DVC", "access to non existing channel"))? .process(data)?; - responses.extend(encode_dvc_messages(channel_id, messages, None)?); + responses.extend(encode_dvc_messages(channel_id, messages, ChannelFlags::empty())?); } } diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 13d24c12b..2f92eeb2d 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -66,7 +66,7 @@ const DATA_MAX_SIZE: usize = 1590; pub fn encode_dvc_messages( channel_id: u32, messages: Vec, - flags: Option, + flags: ironrdp_svc::ChannelFlags, ) -> PduResult> { let mut res = Vec::new(); for msg in messages { @@ -94,10 +94,8 @@ pub fn encode_dvc_messages( pdu::DrdynvcDataPdu::Data(pdu::DataPdu::new(channel_id, msg[off..end].to_vec())) }; - let mut svc = SvcMessage::from(pdu); - if let Some(flags) = flags { - svc = svc.with_flags(flags); - } + let svc = SvcMessage::from(pdu).with_flags(flags); + res.push(svc); off = end; } diff --git a/crates/ironrdp-dvc/src/server.rs b/crates/ironrdp-dvc/src/server.rs index c731cdd77..70d4cfe91 100644 --- a/crates/ironrdp-dvc/src/server.rs +++ b/crates/ironrdp-dvc/src/server.rs @@ -148,11 +148,7 @@ impl SvcProcessor for DrdynvcServer { } c.state = ChannelState::Opened; let msg = c.processor.start(create_resp.channel_id)?; - resp.extend(encode_dvc_messages( - id, - msg, - Some(ironrdp_svc::ChannelFlags::SHOW_PROTOCOL), - )?); + resp.extend(encode_dvc_messages(id, msg, ironrdp_svc::ChannelFlags::SHOW_PROTOCOL)?); } DrdynvcClientPdu::Close(close_resp) => { debug!("Got DVC Close Response PDU: {close_resp:?}"); @@ -173,7 +169,7 @@ impl SvcProcessor for DrdynvcServer { resp.extend(encode_dvc_messages( channel_id, msg, - Some(ironrdp_svc::ChannelFlags::SHOW_PROTOCOL), + ironrdp_svc::ChannelFlags::SHOW_PROTOCOL, )?); } } From 6e45da9cb15be67abc8d178038db2b01e621f17b Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 17:30:11 -0700 Subject: [PATCH 33/84] Changes DATA_MAX_SIZE to be part of DrdynvcDataPdu --- crates/ironrdp-dvc/src/lib.rs | 6 ++---- crates/ironrdp-dvc/src/pdu.rs | 3 +++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 2f92eeb2d..4e301eb5f 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -61,8 +61,6 @@ pub trait DvcProcessor: AsAny + Send + Sync { assert_obj_safe!(DvcProcessor); -const DATA_MAX_SIZE: usize = 1590; - pub fn encode_dvc_messages( channel_id: u32, messages: Vec, @@ -71,7 +69,7 @@ pub fn encode_dvc_messages( let mut res = Vec::new(); for msg in messages { let total_length = msg.size(); - let needs_splitting = total_length >= DATA_MAX_SIZE; + let needs_splitting = total_length >= DrdynvcDataPdu::MAX_DATA_SIZE; let msg = encode_vec(msg.as_ref())?; let mut off = 0; @@ -79,7 +77,7 @@ pub fn encode_dvc_messages( while off < total_length { let first = off == 0; let rem = total_length.checked_sub(off).unwrap(); - let size = core::cmp::min(rem, DATA_MAX_SIZE); + let size = core::cmp::min(rem, DrdynvcDataPdu::MAX_DATA_SIZE); let end = off .checked_add(size) .ok_or_else(|| other_err!("encode_dvc_messages", "overflow occurred"))?; diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index 9a318db22..525ec6faf 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -20,6 +20,9 @@ pub enum DrdynvcDataPdu { } impl DrdynvcDataPdu { + /// Maximum size of the `data` field in `DrdynvcDataPdu`. + pub const MAX_DATA_SIZE: usize = 1590; + pub fn channel_id(&self) -> DynamicChannelId { match self { DrdynvcDataPdu::DataFirst(pdu) => pdu.channel_id, From 3c493d2f6eb17bbf503521dec21c5a3c263b7ad1 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 17:34:31 -0700 Subject: [PATCH 34/84] removes all vec![] --- crates/ironrdp-cliprdr/src/lib.rs | 4 ++-- crates/ironrdp-displaycontrol/src/server.rs | 2 +- crates/ironrdp-dvc/src/client.rs | 3 +-- crates/ironrdp-dvc/src/lib.rs | 3 +-- crates/ironrdp-dvc/src/pdu/tests/capabilities.rs | 5 ++--- crates/ironrdp-dvc/src/pdu/tests/close.rs | 2 +- crates/ironrdp-dvc/src/pdu/tests/create.rs | 2 +- crates/ironrdp-dvc/src/pdu/tests/data.rs | 2 +- crates/ironrdp-dvc/src/pdu/tests/data_first.rs | 4 ++-- crates/ironrdp-server/src/server.rs | 2 +- crates/ironrdp-svc/src/lib.rs | 2 +- 11 files changed, 14 insertions(+), 17 deletions(-) diff --git a/crates/ironrdp-cliprdr/src/lib.rs b/crates/ironrdp-cliprdr/src/lib.rs index 3eb875e02..f5012ca39 100644 --- a/crates/ironrdp-cliprdr/src/lib.rs +++ b/crates/ironrdp-cliprdr/src/lib.rs @@ -222,7 +222,7 @@ impl Cliprdr { /// implementation when user performs OS-specific copy command (e.g. `Ctrl+C` shortcut on /// keyboard) pub fn initiate_copy(&self, available_formats: &[ClipboardFormat]) -> PduResult> { - let mut pdus = vec![]; + let mut pdus = Vec::new(); match (self.state, R::is_server()) { // When user initiates copy, we should send format list to server. @@ -275,7 +275,7 @@ impl SvcProcessor for Cliprdr { if R::is_server() { Ok(vec![self.capabilities()?, self.monitor_ready()?]) } else { - Ok(vec![]) + Ok(Vec::new()) } } diff --git a/crates/ironrdp-displaycontrol/src/server.rs b/crates/ironrdp-displaycontrol/src/server.rs index 69ace518d..dc1796caf 100644 --- a/crates/ironrdp-displaycontrol/src/server.rs +++ b/crates/ironrdp-displaycontrol/src/server.rs @@ -33,7 +33,7 @@ impl DvcProcessor for DisplayControlServer { debug!(?caps); } } - Ok(vec![]) + Ok(Vec::new()) } } diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index a5410b7a2..414484c6b 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -3,7 +3,6 @@ use crate::pdu::{ DrdynvcServerPdu, }; use crate::{encode_dvc_messages, DvcProcessor, DynamicChannelId, DynamicChannelSet}; -use alloc::vec; use alloc::vec::Vec; use core::any::TypeId; use core::fmt; @@ -131,7 +130,7 @@ impl SvcProcessor for DrdynvcClient { let dynamic_channel = self.dynamic_channels.get_by_channel_name_mut(&channel_name).unwrap(); (CreationStatus::OK, dynamic_channel.start(channel_id)?) } else { - (CreationStatus::NO_LISTENER, vec![]) + (CreationStatus::NO_LISTENER, Vec::new()) }; let create_response = DrdynvcClientPdu::Create(CreateResponsePdu::new(channel_id, creation_status)); diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 4e301eb5f..062641078 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -12,7 +12,6 @@ use pdu::DrdynvcDataPdu; use alloc::boxed::Box; use alloc::collections::BTreeMap; -use alloc::vec; use alloc::vec::Vec; // Re-export ironrdp_pdu crate for convenience #[rustfmt::skip] // do not re-order this pub use @@ -125,7 +124,7 @@ impl DynamicVirtualChannel { if let Some(complete_data) = complete_data { self.channel_processor.process(channel_id, &complete_data) } else { - Ok(vec![]) + Ok(Vec::new()) } } diff --git a/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs b/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs index b511f31ff..e17cd4829 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs @@ -1,7 +1,6 @@ -use crate::vec; -use lazy_static::lazy_static; - use super::*; +use alloc::vec; +use lazy_static::lazy_static; const REQ_V1_ENCODED: [u8; 4] = [0x50, 0x00, 0x01, 0x00]; const REQ_V2_ENCODED: [u8; 12] = [0x50, 0x00, 0x02, 0x00, 0x33, 0x33, 0x11, 0x11, 0x3d, 0x0a, 0xa7, 0x04]; diff --git a/crates/ironrdp-dvc/src/pdu/tests/close.rs b/crates/ironrdp-dvc/src/pdu/tests/close.rs index 4ae5efc9d..413c31d84 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/close.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/close.rs @@ -1,4 +1,4 @@ -use crate::vec; +use alloc::vec; use lazy_static::lazy_static; use super::*; diff --git a/crates/ironrdp-dvc/src/pdu/tests/create.rs b/crates/ironrdp-dvc/src/pdu/tests/create.rs index 6a7313ca0..d6bea291b 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/create.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/create.rs @@ -1,5 +1,5 @@ use super::*; -use crate::vec; +use alloc::vec; use lazy_static::lazy_static; const CHANNEL_ID: u32 = 0x0000_0003; diff --git a/crates/ironrdp-dvc/src/pdu/tests/data.rs b/crates/ironrdp-dvc/src/pdu/tests/data.rs index 95fc9461b..82d5c20e2 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/data.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/data.rs @@ -1,5 +1,5 @@ use super::*; -use crate::vec; +use alloc::vec; use lazy_static::lazy_static; const CHANNEL_ID: u32 = 0x03; diff --git a/crates/ironrdp-dvc/src/pdu/tests/data_first.rs b/crates/ironrdp-dvc/src/pdu/tests/data_first.rs index 29fc09ffb..64e7964e3 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/data_first.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/data_first.rs @@ -1,6 +1,6 @@ use super::*; - -use crate::{vec, Vec}; +use crate::Vec; +use alloc::vec; use ironrdp_pdu::cursor::{ReadCursor, WriteCursor}; use ironrdp_pdu::PduDecode; use lazy_static::lazy_static; diff --git a/crates/ironrdp-server/src/server.rs b/crates/ironrdp-server/src/server.rs index bcf2451d1..ccc4b784d 100644 --- a/crates/ironrdp-server/src/server.rs +++ b/crates/ironrdp-server/src/server.rs @@ -74,7 +74,7 @@ impl dvc::DvcProcessor for AInputHandler { } } - Ok(vec![]) + Ok(Vec::new()) } } diff --git a/crates/ironrdp-svc/src/lib.rs b/crates/ironrdp-svc/src/lib.rs index c3442ce58..ea14a7123 100644 --- a/crates/ironrdp-svc/src/lib.rs +++ b/crates/ironrdp-svc/src/lib.rs @@ -237,7 +237,7 @@ pub trait SvcProcessor: AsAny + fmt::Debug + Send { /// /// Returns a list of PDUs to be sent back. fn start(&mut self) -> PduResult> { - Ok(vec![]) + Ok(Vec::new()) } /// Processes a payload received on the virtual channel. The `payload` is expected From 1e2dd93f0f804e30e1e46d927fe816e43bd6c05e Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 17:39:06 -0700 Subject: [PATCH 35/84] impl fmt::Display for Cmd --- crates/ironrdp-dvc/src/pdu.rs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index 525ec6faf..ee1640bc6 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -1,12 +1,14 @@ #[cfg(test)] mod tests; -use crate::{DynamicChannelId, String, Vec}; use alloc::format; +use core::fmt; + +use crate::{DynamicChannelId, String, Vec}; use ironrdp_pdu::{ cast_length, cursor::{ReadCursor, WriteCursor}, - ensure_size, invalid_message_err, unsupported_pdu_err, + ensure_fixed_part_size, ensure_size, invalid_message_err, unsupported_pdu_err, utils::{encoded_str_len, read_string_from_cursor, write_string_to_cursor, CharacterSet}, PduDecode, PduEncode, PduError, PduResult, }; @@ -191,7 +193,7 @@ impl Header { } fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_size!(in: dst, size: Self::size()); + ensure_fixed_part_size!(in: dst); dst.write_u8((self.cmd as u8) << 4 | Into::::into(self.sp) << 2 | Into::::into(self.cb_id)); Ok(()) } @@ -246,9 +248,25 @@ impl TryFrom for Cmd { } } +impl fmt::Display for Cmd { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Cmd::Create => "Create", + Cmd::DataFirst => "DataFirst", + Cmd::Data => "Data", + Cmd::Close => "Close", + Cmd::Capability => "Capability", + Cmd::DataFirstCompressed => "DataFirstCompressed", + Cmd::DataCompressed => "DataCompressed", + Cmd::SoftSyncRequest => "SoftSyncRequest", + Cmd::SoftSyncResponse => "SoftSyncResponse", + }) + } +} + impl From for String { - fn from(val: Cmd) -> Self { - format!("{:?}", val) + fn from(cmd: Cmd) -> Self { + format!("{:?}", cmd) } } From 0a16e2bf21d383adff75be7a14e08d9458e1cb3e Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 17:41:04 -0700 Subject: [PATCH 36/84] add headerless_size --- crates/ironrdp-dvc/src/pdu.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index ee1640bc6..3fb1069f0 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -581,7 +581,7 @@ impl CapabilitiesResponsePdu { } fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { - ensure_size!(in: src, size: 1 /* Pad */ + CapsVersion::size()); + ensure_size!(in: src, size: Self::headerless_size()); let _pad = src.read_u8(); let version = CapsVersion::try_from(src.read_u16())?; Ok(Self { header, version }) @@ -599,8 +599,12 @@ impl CapabilitiesResponsePdu { "DYNVC_CAPS_RSP" } + fn headerless_size() -> usize { + 1 /* Pad */ + CapsVersion::size() + } + fn size(&self) -> usize { - Header::size() + 1 /* Pad */ + CapsVersion::size() + Header::size() + Self::headerless_size() } } From ceb44b8732ae2459eb5eeb3b7462ef483d77b2c9 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 17:47:18 -0700 Subject: [PATCH 37/84] removes leftover commented out code --- crates/ironrdp-dvc/src/pdu/tests/create.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/crates/ironrdp-dvc/src/pdu/tests/create.rs b/crates/ironrdp-dvc/src/pdu/tests/create.rs index d6bea291b..4458379be 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/create.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/create.rs @@ -46,23 +46,3 @@ fn encodes_create_response() { data.encode(&mut cursor).unwrap(); assert_eq!(RESP_ENCODED.as_slice(), buffer.as_slice()); } - -// #[test] -// fn to_buffer_correct_serializes_dvc_create_response_pdu() { -// let create_response = DVC_CREATE_RESPONSE.clone(); - -// let mut buffer = Vec::new(); -// create_response.to_buffer(&mut buffer).unwrap(); - -// assert_eq!(RESP_ENCODED.as_ref(), buffer.as_slice()); -// } - -// #[test] -// fn buffer_length_is_correct_for_dvc_create_response_pdu() { -// let create_response = DVC_CREATE_RESPONSE.clone(); -// let expected_buf_len = RESP_ENCODED.len(); - -// let len = create_response.buffer_length(); - -// assert_eq!(expected_buf_len, len); -// } From 28b5dc338bee500beb1fc02ac71787086b1308d8 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 18:59:44 -0700 Subject: [PATCH 38/84] Fixes linter errors, adding checked_sum and checked_sum_or_panic util functions in the process --- Cargo.lock | 2 - .../src/connection_activation.rs | 1 + crates/ironrdp-dvc/Cargo.toml | 1 - crates/ironrdp-dvc/src/client.rs | 2 +- crates/ironrdp-dvc/src/lib.rs | 14 +-- crates/ironrdp-dvc/src/pdu.rs | 115 +++++++++++------- .../ironrdp-dvc/src/pdu/tests/capabilities.rs | 2 +- crates/ironrdp-pdu/src/lib.rs | 1 + crates/ironrdp-pdu/src/utils.rs | 43 +++++++ crates/ironrdp-session/Cargo.toml | 1 - 10 files changed, 124 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3b47d356..76fc4a8c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1780,7 +1780,6 @@ dependencies = [ name = "ironrdp-dvc" version = "0.1.0" dependencies = [ - "bitflags 2.4.2", "ironrdp-pdu", "ironrdp-svc", "lazy_static", @@ -1920,7 +1919,6 @@ dependencies = [ name = "ironrdp-session" version = "0.1.0" dependencies = [ - "bitflags 2.4.2", "ironrdp-connector", "ironrdp-dvc", "ironrdp-error", diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs index bce969062..686d91b47 100644 --- a/crates/ironrdp-connector/src/connection_activation.rs +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -33,6 +33,7 @@ impl ConnectionActivationSequence { } } + #[must_use] pub fn reset_clone(&self) -> Self { self.clone().reset() } diff --git a/crates/ironrdp-dvc/Cargo.toml b/crates/ironrdp-dvc/Cargo.toml index 549dbbaaa..bdfe660f9 100644 --- a/crates/ironrdp-dvc/Cargo.toml +++ b/crates/ironrdp-dvc/Cargo.toml @@ -19,7 +19,6 @@ default = [] std = [] [dependencies] -bitflags.workspace = true ironrdp-svc.workspace = true ironrdp-pdu = { workspace = true, features = ["alloc"] } tracing.workspace = true diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 414484c6b..096a4df1f 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -110,7 +110,7 @@ impl SvcProcessor for DrdynvcClient { } DrdynvcServerPdu::Create(create_request) => { debug!("Got DVC Create Request PDU: {create_request:?}"); - let channel_name = create_request.channel_name.clone(); + let channel_name = create_request.channel_name; let channel_id = create_request.channel_id; if !self.cap_handshake_done { diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 062641078..3c75fda9c 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -161,12 +161,12 @@ impl DynamicChannelSet { self.channels.insert(name, DynamicVirtualChannel::new(channel)) } - pub fn attach_channel_id(&mut self, name: DynamicChannelName, id: DynamicChannelId) -> Option { + fn attach_channel_id(&mut self, name: DynamicChannelName, id: DynamicChannelId) -> Option { self.channel_id_to_name.insert(id, name.clone()); self.name_to_channel_id.insert(name, id) } - pub fn get_by_type_id(&self, type_id: TypeId) -> Option<(&DynamicVirtualChannel, Option)> { + fn get_by_type_id(&self, type_id: TypeId) -> Option<(&DynamicVirtualChannel, Option)> { self.type_id_to_name.get(&type_id).and_then(|name| { self.channels .get(name) @@ -174,21 +174,21 @@ impl DynamicChannelSet { }) } - pub fn get_by_channel_name(&self, name: &DynamicChannelName) -> Option<&DynamicVirtualChannel> { + fn get_by_channel_name(&self, name: &DynamicChannelName) -> Option<&DynamicVirtualChannel> { self.channels.get(name) } - pub fn get_by_channel_name_mut(&mut self, name: &DynamicChannelName) -> Option<&mut DynamicVirtualChannel> { + fn get_by_channel_name_mut(&mut self, name: &DynamicChannelName) -> Option<&mut DynamicVirtualChannel> { self.channels.get_mut(name) } - pub fn get_by_channel_id_mut(&mut self, id: &DynamicChannelId) -> Option<&mut DynamicVirtualChannel> { + fn get_by_channel_id_mut(&mut self, id: &DynamicChannelId) -> Option<&mut DynamicVirtualChannel> { self.channel_id_to_name .get(id) .and_then(|name| self.channels.get_mut(name)) } - pub fn remove_by_channel_id(&mut self, id: &DynamicChannelId) -> Option { + fn remove_by_channel_id(&mut self, id: &DynamicChannelId) -> Option { if let Some(name) = self.channel_id_to_name.remove(id) { return self.name_to_channel_id.remove(&name); // Channels are retained in the `self.channels` and `self.type_id_to_name` map to allow potential @@ -198,7 +198,7 @@ impl DynamicChannelSet { } #[inline] - pub fn values(&self) -> impl Iterator { + fn values(&self) -> impl Iterator { self.channels.values() } } diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index 3fb1069f0..a4d0460ba 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -9,7 +9,10 @@ use ironrdp_pdu::{ cast_length, cursor::{ReadCursor, WriteCursor}, ensure_fixed_part_size, ensure_size, invalid_message_err, unsupported_pdu_err, - utils::{encoded_str_len, read_string_from_cursor, write_string_to_cursor, CharacterSet}, + utils::{ + checked_sum, checked_sum_or_panic, encoded_str_len, read_string_from_cursor, write_string_to_cursor, + CharacterSet, + }, PduDecode, PduEncode, PduError, PduResult, }; use ironrdp_svc::SvcPduEncode; @@ -43,8 +46,8 @@ impl PduEncode for DrdynvcDataPdu { fn name(&self) -> &'static str { match self { - DrdynvcDataPdu::DataFirst(pdu) => pdu.name(), - DrdynvcDataPdu::Data(pdu) => pdu.name(), + DrdynvcDataPdu::DataFirst(_) => DataFirstPdu::name(), + DrdynvcDataPdu::Data(_) => DataPdu::name(), } } @@ -77,16 +80,16 @@ impl PduEncode for DrdynvcClientPdu { fn name(&self) -> &'static str { match self { - DrdynvcClientPdu::Capabilities(pdu) => pdu.name(), - DrdynvcClientPdu::Create(pdu) => pdu.name(), + DrdynvcClientPdu::Capabilities(_) => CapabilitiesResponsePdu::name(), + DrdynvcClientPdu::Create(_) => CreateResponsePdu::name(), DrdynvcClientPdu::Data(pdu) => pdu.name(), - DrdynvcClientPdu::Close(pdu) => pdu.name(), + DrdynvcClientPdu::Close(_) => ClosePdu::name(), } } fn size(&self) -> usize { match self { - DrdynvcClientPdu::Capabilities(pdu) => pdu.size(), + DrdynvcClientPdu::Capabilities(_) => CapabilitiesResponsePdu::size(), DrdynvcClientPdu::Create(pdu) => pdu.size(), DrdynvcClientPdu::Data(pdu) => pdu.size(), DrdynvcClientPdu::Close(pdu) => pdu.size(), @@ -133,8 +136,8 @@ impl PduEncode for DrdynvcServerPdu { match self { DrdynvcServerPdu::Data(pdu) => pdu.name(), DrdynvcServerPdu::Capabilities(pdu) => pdu.name(), - DrdynvcServerPdu::Create(pdu) => pdu.name(), - DrdynvcServerPdu::Close(pdu) => pdu.name(), + DrdynvcServerPdu::Create(_) => CreateRequestPdu::name(), + DrdynvcServerPdu::Close(_) => ClosePdu::name(), } } @@ -301,7 +304,8 @@ impl DataFirstPdu { } fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { - ensure_size!(in: src, size: header.cb_id.size_of_val() + header.sp.size_of_val()); + let fixed_part_size = checked_sum(&[header.cb_id.size_of_val(), header.sp.size_of_val()])?; + ensure_size!(in: src, size: fixed_part_size); let channel_id = header.cb_id.decode_val(src)?; let length = header.sp.decode_val(src)?; let data = src.read_remaining().to_vec(); @@ -324,15 +328,17 @@ impl DataFirstPdu { Ok(()) } - fn name(&self) -> &'static str { + fn name() -> &'static str { "DYNVC_DATA_FIRST" } fn size(&self) -> usize { - Header::size() + - self.header.cb_id.size_of_val() + // ChannelId - self.header.sp.size_of_val() + // Length - self.data.len() // Data + checked_sum_or_panic(&[ + Header::size(), + self.header.cb_id.size_of_val(), + self.header.sp.size_of_val(), + self.data.len(), + ]) } } @@ -358,8 +364,8 @@ impl FieldType { fn decode_val(&self, src: &mut ReadCursor<'_>) -> PduResult { ensure_size!(in: src, size: self.size_of_val()); match *self { - FieldType::U8 => Ok(src.read_u8() as u32), - FieldType::U16 => Ok(src.read_u16() as u32), + FieldType::U8 => Ok(u32::from(src.read_u8())), + FieldType::U16 => Ok(u32::from(src.read_u16())), FieldType::U32 => Ok(src.read_u32()), _ => Err(invalid_message_err!("FieldType", "invalid field type")), } @@ -376,9 +382,9 @@ impl FieldType { } fn for_val(value: u32) -> Self { - if value <= u8::MAX as u32 { + if u8::try_from(value).is_ok() { FieldType::U8 - } else if value <= u16::MAX as u32 { + } else if u16::try_from(value).is_ok() { FieldType::U16 } else { FieldType::U32 @@ -441,14 +447,16 @@ impl DataPdu { Ok(()) } - fn name(&self) -> &'static str { + fn name() -> &'static str { "DYNVC_DATA" } fn size(&self) -> usize { - Header::size() + - self.header.cb_id.size_of_val() + // ChannelId - self.data.len() // Data + checked_sum_or_panic(&[ + Header::size(), + self.header.cb_id.size_of_val(), // ChannelId + self.data.len(), // Data + ]) } } @@ -471,12 +479,12 @@ impl CreateResponsePdu { } } - fn name(&self) -> &'static str { + fn name() -> &'static str { "DYNVC_CREATE_RSP" } fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { - ensure_size!(in: src, size: header.cb_id.size_of_val() + CreationStatus::size()); + ensure_size!(in: src, size: Self::headerless_size(&header)); let channel_id = header.cb_id.decode_val(src)?; let creation_status = CreationStatus(src.read_u32()); Ok(Self { @@ -494,10 +502,15 @@ impl CreateResponsePdu { Ok(()) } + fn headerless_size(header: &Header) -> usize { + checked_sum_or_panic(&[ + header.cb_id.size_of_val(), // ChannelId + CreationStatus::size(), // CreationStatus + ]) + } + fn size(&self) -> usize { - Header::size() + - self.header.cb_id.size_of_val() + // ChannelId - CreationStatus::size() // CreationStatus + checked_sum_or_panic(&[Header::size(), Self::headerless_size(&self.header)]) } } @@ -543,6 +556,7 @@ impl ClosePdu { } fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { + ensure_size!(in: src, size: Self::headerless_size(&header)); let channel_id = header.cb_id.decode_val(src)?; Ok(Self { header, channel_id }) } @@ -554,12 +568,16 @@ impl ClosePdu { Ok(()) } - fn name(&self) -> &'static str { + fn name() -> &'static str { "DYNVC_CLOSE" } + fn headerless_size(header: &Header) -> usize { + header.cb_id.size_of_val() + } + fn size(&self) -> usize { - Header::size() + self.header.cb_id.size_of_val() + checked_sum_or_panic(&[Header::size(), Self::headerless_size(&self.header)]) } } @@ -573,6 +591,9 @@ pub struct CapabilitiesResponsePdu { } impl CapabilitiesResponsePdu { + const HEADERLESS_FIXED_PART_SIZE: usize = 1 /* Pad */ + CapsVersion::FIXED_PART_SIZE /* Version */; + const FIXED_PART_SIZE: usize = Header::FIXED_PART_SIZE + Self::HEADERLESS_FIXED_PART_SIZE; + pub fn new(version: CapsVersion) -> Self { Self { header: Header::new(0, 0, Cmd::Capability), @@ -581,30 +602,26 @@ impl CapabilitiesResponsePdu { } fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { - ensure_size!(in: src, size: Self::headerless_size()); + ensure_size!(in: src, size: Self::HEADERLESS_FIXED_PART_SIZE); let _pad = src.read_u8(); let version = CapsVersion::try_from(src.read_u16())?; Ok(Self { header, version }) } fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_size!(in: dst, size: self.size()); + ensure_size!(in: dst, size: Self::size()); self.header.encode(dst)?; dst.write_u8(0x00); // Pad, MUST be 0x00 self.version.encode(dst)?; Ok(()) } - fn name(&self) -> &'static str { + fn name() -> &'static str { "DYNVC_CAPS_RSP" } - fn headerless_size() -> usize { - 1 /* Pad */ + CapsVersion::size() - } - - fn size(&self) -> usize { - Header::size() + Self::headerless_size() + fn size() -> usize { + Self::FIXED_PART_SIZE } } @@ -617,6 +634,8 @@ pub enum CapsVersion { } impl CapsVersion { + const FIXED_PART_SIZE: usize = 2; + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { ensure_size!(in: dst, size: Self::size()); dst.write_u16(*self as u16); @@ -624,7 +643,7 @@ impl CapsVersion { } fn size() -> usize { - 2 + Self::FIXED_PART_SIZE } } @@ -765,7 +784,7 @@ impl CreateRequestPdu { } fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { - ensure_size!(in: src, size: header.cb_id.size_of_val()); + ensure_size!(in: src, size: Self::headerless_fixed_part_size(&header)); let channel_id = header.cb_id.decode_val(src)?; let channel_name = read_string_from_cursor(src, CharacterSet::Ansi, true)?; Ok(Self { @@ -783,13 +802,19 @@ impl CreateRequestPdu { Ok(()) } - fn name(&self) -> &'static str { + fn name() -> &'static str { "DYNVC_CREATE_REQ" } + fn headerless_fixed_part_size(header: &Header) -> usize { + header.cb_id.size_of_val() // ChannelId + } + fn size(&self) -> usize { - Header::size() + - self.header.cb_id.size_of_val() + // ChannelId - encoded_str_len(&self.channel_name, CharacterSet::Ansi, true) // ChannelName + Null terminator + checked_sum_or_panic(&[ + Header::size(), + Self::headerless_fixed_part_size(&self.header), // ChannelId + encoded_str_len(&self.channel_name, CharacterSet::Ansi, true), // ChannelName + Null terminator + ]) } } diff --git a/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs b/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs index e17cd4829..7f9ac3d87 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs +++ b/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs @@ -61,7 +61,7 @@ fn decodes_response_v1() { #[test] fn encodes_response_v1() { let data = &*RESP_V1_DECODED; - let mut buffer = vec![0x00; data.size()]; + let mut buffer = vec![0x00; CapabilitiesResponsePdu::size()]; let mut cursor = WriteCursor::new(&mut buffer); data.encode(&mut cursor).unwrap(); assert_eq!(RESP_V1_ENCODED.as_ref(), buffer.as_slice()); diff --git a/crates/ironrdp-pdu/src/lib.rs b/crates/ironrdp-pdu/src/lib.rs index 02e85956a..825fdb056 100644 --- a/crates/ironrdp-pdu/src/lib.rs +++ b/crates/ironrdp-pdu/src/lib.rs @@ -428,6 +428,7 @@ mod legacy { InvalidActionCode(u8), } + #[cfg(feature = "std")] impl ironrdp_error::legacy::CatchAllKind for crate::PduErrorKind { const CATCH_ALL_VALUE: Self = crate::PduErrorKind::Custom; } diff --git a/crates/ironrdp-pdu/src/utils.rs b/crates/ironrdp-pdu/src/utils.rs index f7b8d7e19..45868bc1f 100644 --- a/crates/ironrdp-pdu/src/utils.rs +++ b/crates/ironrdp-pdu/src/utils.rs @@ -1,5 +1,7 @@ use byteorder::{LittleEndian, ReadBytesExt as _}; use num_derive::{FromPrimitive, ToPrimitive}; +use std::fmt::Debug; +use std::ops::Add; use crate::cursor::{ReadCursor, WriteCursor}; use crate::PduResult; @@ -248,3 +250,44 @@ impl SplitTo for &mut [T] { a } } + +pub trait CheckedAdd: Sized + Add { + fn checked_add(self, rhs: Self) -> Option; +} + +// Implement the trait for usize and u32 +impl CheckedAdd for usize { + fn checked_add(self, rhs: Self) -> Option { + usize::checked_add(self, rhs) + } +} + +impl CheckedAdd for u32 { + fn checked_add(self, rhs: Self) -> Option { + u32::checked_add(self, rhs) + } +} + +// Utility function for checked addition that returns a PduResult +pub fn checked_sum(values: &[T]) -> PduResult +where + T: CheckedAdd + Copy + Debug, +{ + values.split_first().map_or_else( + || Err(other_err!("Empty array provided to checked_sum")), + |(&first, rest)| { + rest.iter().try_fold(first, |acc, &val| { + acc.checked_add(val) + .ok_or_else(|| other_err!("Overflow detected during addition")) + }) + }, + ) +} + +// Utility function that panics on overflow +pub fn checked_sum_or_panic(values: &[T]) -> T +where + T: CheckedAdd + Copy + Debug, +{ + checked_sum::(values).expect("Overflow detected during addition") +} diff --git a/crates/ironrdp-session/Cargo.toml b/crates/ironrdp-session/Cargo.toml index 0f77336bf..11995b539 100644 --- a/crates/ironrdp-session/Cargo.toml +++ b/crates/ironrdp-session/Cargo.toml @@ -16,7 +16,6 @@ doctest = false test = false [dependencies] -bitflags.workspace = true # TODO: investigate usage in this crate ironrdp-connector.workspace = true # TODO: at some point, this dependency could be removed (good for compilation speed) ironrdp-svc.workspace = true ironrdp-dvc.workspace = true From b9669f33056dc7a5e3648bf6a0f80923edfa7bd6 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 19:00:52 -0700 Subject: [PATCH 39/84] removes pub from FieldType --- crates/ironrdp-dvc/src/pdu.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index a4d0460ba..d9654303f 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -343,7 +343,7 @@ impl DataFirstPdu { } #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct FieldType(u8); +struct FieldType(u8); impl FieldType { pub const U8: Self = Self(0x00); From fb894074786e524525d6df14a3afc0fad4104078 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 19:03:39 -0700 Subject: [PATCH 40/84] Removes impl DvcPduEncode for Vec {} which was no longer necessary --- crates/ironrdp-dvc/src/lib.rs | 3 --- crates/ironrdp-dvc/src/pdu.rs | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 3c75fda9c..4e52bdc30 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -36,9 +36,6 @@ pub mod pdu; pub trait DvcPduEncode: PduEncode + Send {} pub type DvcMessage = Box; -/// We implement `DvcPduEncode` for `Vec` for legacy reasons. -impl DvcPduEncode for Vec {} - /// A type that is a Dynamic Virtual Channel (DVC) /// /// Dynamic virtual channels may be created at any point during the RDP session. diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index d9654303f..821bc78c4 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -346,9 +346,9 @@ impl DataFirstPdu { struct FieldType(u8); impl FieldType { - pub const U8: Self = Self(0x00); - pub const U16: Self = Self(0x01); - pub const U32: Self = Self(0x02); + const U8: Self = Self(0x00); + const U16: Self = Self(0x01); + const U32: Self = Self(0x02); fn encode_val(&self, value: u32, dst: &mut WriteCursor<'_>) -> PduResult<()> { ensure_size!(in: dst, size: self.size_of_val()); From bc26ef08e3a73e2cc56dbe73e8b67103abc9f0e5 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 20:48:49 -0700 Subject: [PATCH 41/84] Moves tests to ironrdp-testsuite-core --- Cargo.lock | 1 + crates/ironrdp-dvc/Cargo.toml | 1 + crates/ironrdp-dvc/src/pdu.rs | 49 +++++++++--- crates/ironrdp-dvc/src/pdu/tests.rs | 12 --- .../ironrdp-dvc/src/pdu/tests/capabilities.rs | 68 ---------------- crates/ironrdp-dvc/src/pdu/tests/close.rs | 39 ---------- crates/ironrdp-dvc/src/pdu/tests/create.rs | 48 ------------ crates/ironrdp-dvc/src/pdu/tests/data.rs | 40 ---------- crates/ironrdp-testsuite-core/Cargo.toml | 1 + .../tests/dvc/capabilities.rs | 46 +++++++++++ .../ironrdp-testsuite-core/tests/dvc/close.rs | 23 ++++++ .../tests/dvc/create.rs | 32 ++++++++ .../ironrdp-testsuite-core/tests/dvc/data.rs | 29 +++++++ .../tests/dvc}/data_first.rs | 77 +++++++------------ .../ironrdp-testsuite-core/tests/dvc/mod.rs | 32 ++++++++ crates/ironrdp-testsuite-core/tests/main.rs | 1 + 16 files changed, 233 insertions(+), 266 deletions(-) delete mode 100644 crates/ironrdp-dvc/src/pdu/tests.rs delete mode 100644 crates/ironrdp-dvc/src/pdu/tests/capabilities.rs delete mode 100644 crates/ironrdp-dvc/src/pdu/tests/close.rs delete mode 100644 crates/ironrdp-dvc/src/pdu/tests/create.rs delete mode 100644 crates/ironrdp-dvc/src/pdu/tests/data.rs create mode 100644 crates/ironrdp-testsuite-core/tests/dvc/capabilities.rs create mode 100644 crates/ironrdp-testsuite-core/tests/dvc/close.rs create mode 100644 crates/ironrdp-testsuite-core/tests/dvc/create.rs create mode 100644 crates/ironrdp-testsuite-core/tests/dvc/data.rs rename crates/{ironrdp-dvc/src/pdu/tests => ironrdp-testsuite-core/tests/dvc}/data_first.rs (83%) create mode 100644 crates/ironrdp-testsuite-core/tests/dvc/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 76fc4a8c6..e58afcaf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1952,6 +1952,7 @@ dependencies = [ "ironrdp-cliprdr-format", "ironrdp-connector", "ironrdp-displaycontrol", + "ironrdp-dvc", "ironrdp-fuzzing", "ironrdp-graphics", "ironrdp-input", diff --git a/crates/ironrdp-dvc/Cargo.toml b/crates/ironrdp-dvc/Cargo.toml index bdfe660f9..fcc0697b6 100644 --- a/crates/ironrdp-dvc/Cargo.toml +++ b/crates/ironrdp-dvc/Cargo.toml @@ -13,6 +13,7 @@ categories.workspace = true [lib] doctest = false +test = false [features] default = [] diff --git a/crates/ironrdp-dvc/src/pdu.rs b/crates/ironrdp-dvc/src/pdu.rs index 821bc78c4..9720ca035 100644 --- a/crates/ironrdp-dvc/src/pdu.rs +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -1,6 +1,3 @@ -#[cfg(test)] -mod tests; - use alloc::format; use core::fmt; @@ -18,7 +15,7 @@ use ironrdp_pdu::{ use ironrdp_svc::SvcPduEncode; /// Dynamic Virtual Channel PDU's that are sent by both client and server. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum DrdynvcDataPdu { DataFirst(DataFirstPdu), Data(DataPdu), @@ -60,7 +57,7 @@ impl PduEncode for DrdynvcDataPdu { } /// Dynamic Virtual Channel PDU's that are sent by the client. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum DrdynvcClientPdu { Capabilities(CapabilitiesResponsePdu), Create(CreateResponsePdu), @@ -114,7 +111,7 @@ impl PduDecode<'_> for DrdynvcClientPdu { } /// Dynamic Virtual Channel PDU's that are sent by the server. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum DrdynvcServerPdu { Capabilities(CapabilitiesRequestPdu), Create(CreateRequestPdu), @@ -195,6 +192,14 @@ impl Header { } } + fn with_cb_id(self, cb_id: FieldType) -> Self { + Self { cb_id, ..self } + } + + fn with_sp(self, sp: FieldType) -> Self { + Self { sp, ..self } + } + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { ensure_fixed_part_size!(in: dst); dst.write_u8((self.cmd as u8) << 4 | Into::::into(self.sp) << 2 | Into::::into(self.cb_id)); @@ -303,6 +308,22 @@ impl DataFirstPdu { } } + #[must_use] + pub fn with_cb_id_type(self, cb_id: FieldType) -> Self { + Self { + header: self.header.with_cb_id(cb_id), + ..self + } + } + + #[must_use] + pub fn with_sp_type(self, sp: FieldType) -> Self { + Self { + header: self.header.with_sp(sp), + ..self + } + } + fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { let fixed_part_size = checked_sum(&[header.cb_id.size_of_val(), header.sp.size_of_val()])?; ensure_size!(in: src, size: fixed_part_size); @@ -343,12 +364,12 @@ impl DataFirstPdu { } #[derive(Debug, Copy, Clone, PartialEq, Eq)] -struct FieldType(u8); +pub struct FieldType(u8); impl FieldType { - const U8: Self = Self(0x00); - const U16: Self = Self(0x01); - const U32: Self = Self(0x02); + pub const U8: Self = Self(0x00); + pub const U16: Self = Self(0x01); + pub const U32: Self = Self(0x02); fn encode_val(&self, value: u32, dst: &mut WriteCursor<'_>) -> PduResult<()> { ensure_size!(in: dst, size: self.size_of_val()); @@ -555,6 +576,14 @@ impl ClosePdu { } } + #[must_use] + pub fn with_cb_id_type(self, cb_id: FieldType) -> Self { + Self { + header: self.header.with_cb_id(cb_id), + ..self + } + } + fn decode(header: Header, src: &mut ReadCursor<'_>) -> PduResult { ensure_size!(in: src, size: Self::headerless_size(&header)); let channel_id = header.cb_id.decode_val(src)?; diff --git a/crates/ironrdp-dvc/src/pdu/tests.rs b/crates/ironrdp-dvc/src/pdu/tests.rs deleted file mode 100644 index e9d87c377..000000000 --- a/crates/ironrdp-dvc/src/pdu/tests.rs +++ /dev/null @@ -1,12 +0,0 @@ -use super::*; - -#[cfg(test)] -mod capabilities; -#[cfg(test)] -mod close; -#[cfg(test)] -mod create; -#[cfg(test)] -mod data; -#[cfg(test)] -mod data_first; diff --git a/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs b/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs deleted file mode 100644 index 7f9ac3d87..000000000 --- a/crates/ironrdp-dvc/src/pdu/tests/capabilities.rs +++ /dev/null @@ -1,68 +0,0 @@ -use super::*; -use alloc::vec; -use lazy_static::lazy_static; - -const REQ_V1_ENCODED: [u8; 4] = [0x50, 0x00, 0x01, 0x00]; -const REQ_V2_ENCODED: [u8; 12] = [0x50, 0x00, 0x02, 0x00, 0x33, 0x33, 0x11, 0x11, 0x3d, 0x0a, 0xa7, 0x04]; -const RESP_V1_ENCODED: [u8; 4] = [0x50, 0x00, 0x01, 0x00]; - -lazy_static! { - static ref REQ_V1_DECODED: CapabilitiesRequestPdu = CapabilitiesRequestPdu::new(CapsVersion::V1, None); - static ref REQ_V2_DECODED: CapabilitiesRequestPdu = - CapabilitiesRequestPdu::new(CapsVersion::V2, Some([0x3333, 0x1111, 0x0a3d, 0x04a7])); - static ref RESP_V1_DECODED: CapabilitiesResponsePdu = CapabilitiesResponsePdu::new(CapsVersion::V1); -} - -#[test] -fn decodes_request_v1() { - let mut src = ReadCursor::new(&REQ_V1_ENCODED); - match DrdynvcServerPdu::decode(&mut src).unwrap() { - DrdynvcServerPdu::Capabilities(pdu) => assert_eq!(*REQ_V1_DECODED, pdu), - _ => panic!("Expected Capabilities"), - } -} - -#[test] -fn encodes_request_v1() { - let data = &*REQ_V1_DECODED; - let mut buffer = vec![0x00; data.size()]; - let mut cursor = WriteCursor::new(&mut buffer); - data.encode(&mut cursor).unwrap(); - assert_eq!(REQ_V1_ENCODED.as_ref(), buffer.as_slice()); -} - -#[test] -fn decodes_request_v2() { - let mut src = ReadCursor::new(&REQ_V2_ENCODED); - match DrdynvcServerPdu::decode(&mut src).unwrap() { - DrdynvcServerPdu::Capabilities(pdu) => assert_eq!(*REQ_V2_DECODED, pdu), - _ => panic!("Expected Capabilities"), - } -} - -#[test] -fn encodes_request_v2() { - let data = &*REQ_V2_DECODED; - let mut buffer = vec![0x00; data.size()]; - let mut cursor = WriteCursor::new(&mut buffer); - data.encode(&mut cursor).unwrap(); - assert_eq!(REQ_V2_ENCODED.as_ref(), buffer.as_slice()); -} - -#[test] -fn decodes_response_v1() { - let mut src = ReadCursor::new(&RESP_V1_ENCODED); - match DrdynvcClientPdu::decode(&mut src).unwrap() { - DrdynvcClientPdu::Capabilities(pdu) => assert_eq!(*RESP_V1_DECODED, pdu), - _ => panic!("Expected Capabilities"), - } -} - -#[test] -fn encodes_response_v1() { - let data = &*RESP_V1_DECODED; - let mut buffer = vec![0x00; CapabilitiesResponsePdu::size()]; - let mut cursor = WriteCursor::new(&mut buffer); - data.encode(&mut cursor).unwrap(); - assert_eq!(RESP_V1_ENCODED.as_ref(), buffer.as_slice()); -} diff --git a/crates/ironrdp-dvc/src/pdu/tests/close.rs b/crates/ironrdp-dvc/src/pdu/tests/close.rs deleted file mode 100644 index 413c31d84..000000000 --- a/crates/ironrdp-dvc/src/pdu/tests/close.rs +++ /dev/null @@ -1,39 +0,0 @@ -use alloc::vec; -use lazy_static::lazy_static; - -use super::*; - -const CHANNEL_ID: u32 = 0x0303; -const ENCODED: [u8; 3] = [0x41, 0x03, 0x03]; - -lazy_static! { - static ref DECODED: ClosePdu = { - let mut pdu = ClosePdu::new(CHANNEL_ID); - pdu.header.cb_id = FieldType::U16; - pdu - }; -} - -#[test] -fn decodes_close() { - let mut src = ReadCursor::new(&ENCODED); - match DrdynvcClientPdu::decode(&mut src).unwrap() { - DrdynvcClientPdu::Close(pdu) => assert_eq!(*DECODED, pdu), - _ => panic!("Expected Close"), - } - - let mut src = ReadCursor::new(&ENCODED); - match DrdynvcServerPdu::decode(&mut src).unwrap() { - DrdynvcServerPdu::Close(pdu) => assert_eq!(*DECODED, pdu), - _ => panic!("Expected Close"), - } -} - -#[test] -fn encodes_close() { - let data = &*DECODED; - let mut buffer = vec![0x00; data.size()]; - let mut cursor = WriteCursor::new(&mut buffer); - data.encode(&mut cursor).unwrap(); - assert_eq!(ENCODED.as_slice(), buffer.as_slice()); -} diff --git a/crates/ironrdp-dvc/src/pdu/tests/create.rs b/crates/ironrdp-dvc/src/pdu/tests/create.rs deleted file mode 100644 index 4458379be..000000000 --- a/crates/ironrdp-dvc/src/pdu/tests/create.rs +++ /dev/null @@ -1,48 +0,0 @@ -use super::*; -use alloc::vec; -use lazy_static::lazy_static; - -const CHANNEL_ID: u32 = 0x0000_0003; -const REQ_ENCODED: [u8; 10] = [0x10, 0x03, 0x74, 0x65, 0x73, 0x74, 0x64, 0x76, 0x63, 0x00]; -const RESP_ENCODED: [u8; 6] = [0x10, 0x03, 0x00, 0x00, 0x00, 0x00]; - -lazy_static! { - static ref REQ_DECODED: CreateRequestPdu = CreateRequestPdu::new(CHANNEL_ID, String::from("testdvc")); - static ref RESP_DECODED: CreateResponsePdu = CreateResponsePdu::new(CHANNEL_ID, CreationStatus::OK); -} - -#[test] -fn decodes_create_request() { - let mut src = ReadCursor::new(&REQ_ENCODED); - match DrdynvcServerPdu::decode(&mut src).unwrap() { - DrdynvcServerPdu::Create(pdu) => assert_eq!(*REQ_DECODED, pdu), - _ => panic!("Expected Create"), - } -} - -#[test] -fn encodes_create_request() { - let data = &*REQ_DECODED; - let mut buffer = vec![0x00; data.size()]; - let mut cursor = WriteCursor::new(&mut buffer); - data.encode(&mut cursor).unwrap(); - assert_eq!(REQ_ENCODED.as_slice(), buffer.as_slice()); -} - -#[test] -fn decodes_create_response() { - let mut src = ReadCursor::new(&RESP_ENCODED); - match DrdynvcClientPdu::decode(&mut src).unwrap() { - DrdynvcClientPdu::Create(pdu) => assert_eq!(*RESP_DECODED, pdu), - _ => panic!("Expected Create"), - } -} - -#[test] -fn encodes_create_response() { - let data = &*RESP_DECODED; - let mut buffer = vec![0x00; data.size()]; - let mut cursor = WriteCursor::new(&mut buffer); - data.encode(&mut cursor).unwrap(); - assert_eq!(RESP_ENCODED.as_slice(), buffer.as_slice()); -} diff --git a/crates/ironrdp-dvc/src/pdu/tests/data.rs b/crates/ironrdp-dvc/src/pdu/tests/data.rs deleted file mode 100644 index 82d5c20e2..000000000 --- a/crates/ironrdp-dvc/src/pdu/tests/data.rs +++ /dev/null @@ -1,40 +0,0 @@ -use super::*; -use alloc::vec; -use lazy_static::lazy_static; - -const CHANNEL_ID: u32 = 0x03; -const PREFIX: [u8; 2] = [0x30, 0x03]; -const DATA: [u8; 12] = [0x71; 12]; - -lazy_static! { - static ref ENCODED: Vec = { - let mut result = PREFIX.to_vec(); - result.extend(DATA); - result - }; - static ref DECODED: DataPdu = DataPdu::new(CHANNEL_ID, DATA.to_vec()); -} - -#[test] -fn decodes_data() { - let mut src = ReadCursor::new(&ENCODED); - match DrdynvcClientPdu::decode(&mut src).unwrap() { - DrdynvcClientPdu::Data(DrdynvcDataPdu::Data(d)) => assert_eq!(*DECODED, d), - _ => panic!("Expected Data"), - } - - let mut src = ReadCursor::new(&ENCODED); - match DrdynvcServerPdu::decode(&mut src).unwrap() { - DrdynvcServerPdu::Data(DrdynvcDataPdu::Data(d)) => assert_eq!(*DECODED, d), - _ => panic!("Expected Data"), - } -} - -#[test] -fn encodes_data() { - let data = &*DECODED; - let mut buffer = vec![0x00; data.size()]; - let mut cursor = WriteCursor::new(&mut buffer); - data.encode(&mut cursor).unwrap(); - assert_eq!(ENCODED.as_slice(), buffer.as_slice()); -} diff --git a/crates/ironrdp-testsuite-core/Cargo.toml b/crates/ironrdp-testsuite-core/Cargo.toml index 669e14208..c33b567bd 100644 --- a/crates/ironrdp-testsuite-core/Cargo.toml +++ b/crates/ironrdp-testsuite-core/Cargo.toml @@ -27,6 +27,7 @@ hex = "0.4" ironrdp-cliprdr.workspace = true ironrdp-cliprdr-format.workspace = true ironrdp-connector.workspace = true +ironrdp-dvc.workspace = true ironrdp-fuzzing.workspace = true ironrdp-graphics.workspace = true ironrdp-input.workspace = true diff --git a/crates/ironrdp-testsuite-core/tests/dvc/capabilities.rs b/crates/ironrdp-testsuite-core/tests/dvc/capabilities.rs new file mode 100644 index 000000000..e44f7ba94 --- /dev/null +++ b/crates/ironrdp-testsuite-core/tests/dvc/capabilities.rs @@ -0,0 +1,46 @@ +use super::*; + +const REQ_V1_ENCODED: [u8; 4] = [0x50, 0x00, 0x01, 0x00]; +const REQ_V2_ENCODED: [u8; 12] = [0x50, 0x00, 0x02, 0x00, 0x33, 0x33, 0x11, 0x11, 0x3d, 0x0a, 0xa7, 0x04]; +const RESP_V1_ENCODED: [u8; 4] = [0x50, 0x00, 0x01, 0x00]; + +lazy_static! { + static ref REQ_V1_DECODED_SERVER: DrdynvcServerPdu = + DrdynvcServerPdu::Capabilities(CapabilitiesRequestPdu::new(CapsVersion::V1, None)); + static ref REQ_V2_DECODED_SERVER: DrdynvcServerPdu = DrdynvcServerPdu::Capabilities(CapabilitiesRequestPdu::new( + CapsVersion::V2, + Some([0x3333, 0x1111, 0x0a3d, 0x04a7]) + )); + static ref RESP_V1_DECODED_CLIENT: DrdynvcClientPdu = + DrdynvcClientPdu::Capabilities(CapabilitiesResponsePdu::new(CapsVersion::V1)); +} + +#[test] +fn decodes_request_v1() { + test_decodes(&REQ_V1_ENCODED, &*REQ_V1_DECODED_SERVER); +} + +#[test] +fn encodes_request_v1() { + test_encodes(&*REQ_V1_DECODED_SERVER, &REQ_V1_ENCODED); +} + +#[test] +fn decodes_request_v2() { + test_decodes(&REQ_V2_ENCODED, &*REQ_V2_DECODED_SERVER); +} + +#[test] +fn encodes_request_v2() { + test_encodes(&*REQ_V2_DECODED_SERVER, &REQ_V2_ENCODED); +} + +#[test] +fn decodes_response_v1() { + test_decodes(&RESP_V1_ENCODED, &*RESP_V1_DECODED_CLIENT); +} + +#[test] +fn encodes_response_v1() { + test_encodes(&*RESP_V1_DECODED_CLIENT, &RESP_V1_ENCODED); +} diff --git a/crates/ironrdp-testsuite-core/tests/dvc/close.rs b/crates/ironrdp-testsuite-core/tests/dvc/close.rs new file mode 100644 index 000000000..1d71074c2 --- /dev/null +++ b/crates/ironrdp-testsuite-core/tests/dvc/close.rs @@ -0,0 +1,23 @@ +use super::*; + +const CHANNEL_ID: u32 = 0x0303; +const ENCODED: [u8; 3] = [0x41, 0x03, 0x03]; + +lazy_static! { + static ref DECODED_CLIENT: DrdynvcClientPdu = + DrdynvcClientPdu::Close(ClosePdu::new(CHANNEL_ID).with_cb_id_type(FieldType::U16)); + static ref DECODED_SERVER: DrdynvcServerPdu = + DrdynvcServerPdu::Close(ClosePdu::new(CHANNEL_ID).with_cb_id_type(FieldType::U16)); +} + +#[test] +fn decodes_close() { + test_decodes(&ENCODED, &*DECODED_CLIENT); + test_decodes(&ENCODED, &*DECODED_SERVER); +} + +#[test] +fn encodes_close() { + test_encodes(&*DECODED_CLIENT, &ENCODED); + test_encodes(&*DECODED_SERVER, &ENCODED); +} diff --git a/crates/ironrdp-testsuite-core/tests/dvc/create.rs b/crates/ironrdp-testsuite-core/tests/dvc/create.rs new file mode 100644 index 000000000..8b459b1af --- /dev/null +++ b/crates/ironrdp-testsuite-core/tests/dvc/create.rs @@ -0,0 +1,32 @@ +use super::*; + +const CHANNEL_ID: u32 = 0x0000_0003; +const REQ_ENCODED: [u8; 10] = [0x10, 0x03, 0x74, 0x65, 0x73, 0x74, 0x64, 0x76, 0x63, 0x00]; +const RESP_ENCODED: [u8; 6] = [0x10, 0x03, 0x00, 0x00, 0x00, 0x00]; + +lazy_static! { + static ref REQ_DECODED_SERVER: DrdynvcServerPdu = + DrdynvcServerPdu::Create(CreateRequestPdu::new(CHANNEL_ID, String::from("testdvc"))); + static ref RESP_DECODED_CLIENT: DrdynvcClientPdu = + DrdynvcClientPdu::Create(CreateResponsePdu::new(CHANNEL_ID, CreationStatus::OK)); +} + +#[test] +fn decodes_create_request() { + test_decodes(&REQ_ENCODED, &*REQ_DECODED_SERVER); +} + +#[test] +fn encodes_create_request() { + test_encodes(&*REQ_DECODED_SERVER, &REQ_ENCODED); +} + +#[test] +fn decodes_create_response() { + test_decodes(&RESP_ENCODED, &*RESP_DECODED_CLIENT); +} + +#[test] +fn encodes_create_response() { + test_encodes(&*RESP_DECODED_CLIENT, &RESP_ENCODED); +} diff --git a/crates/ironrdp-testsuite-core/tests/dvc/data.rs b/crates/ironrdp-testsuite-core/tests/dvc/data.rs new file mode 100644 index 000000000..f42e216e5 --- /dev/null +++ b/crates/ironrdp-testsuite-core/tests/dvc/data.rs @@ -0,0 +1,29 @@ +use super::*; + +const CHANNEL_ID: u32 = 0x03; +const PREFIX: [u8; 2] = [0x30, 0x03]; +const DATA: [u8; 12] = [0x71; 12]; + +lazy_static! { + static ref ENCODED: Vec = { + let mut result = PREFIX.to_vec(); + result.extend(DATA); + result + }; + static ref DECODED_CLIENT: DrdynvcClientPdu = + DrdynvcClientPdu::Data(DrdynvcDataPdu::Data(DataPdu::new(CHANNEL_ID, DATA.to_vec()))); + static ref DECODED_SERVER: DrdynvcServerPdu = + DrdynvcServerPdu::Data(DrdynvcDataPdu::Data(DataPdu::new(CHANNEL_ID, DATA.to_vec()))); +} + +#[test] +fn decodes_data() { + test_decodes(&ENCODED, &*DECODED_CLIENT); + test_decodes(&ENCODED, &*DECODED_SERVER); +} + +#[test] +fn encodes_data() { + test_encodes(&*DECODED_CLIENT, &ENCODED); + test_encodes(&*DECODED_SERVER, &ENCODED); +} diff --git a/crates/ironrdp-dvc/src/pdu/tests/data_first.rs b/crates/ironrdp-testsuite-core/tests/dvc/data_first.rs similarity index 83% rename from crates/ironrdp-dvc/src/pdu/tests/data_first.rs rename to crates/ironrdp-testsuite-core/tests/dvc/data_first.rs index 64e7964e3..8fee7b891 100644 --- a/crates/ironrdp-dvc/src/pdu/tests/data_first.rs +++ b/crates/ironrdp-testsuite-core/tests/dvc/data_first.rs @@ -1,9 +1,4 @@ use super::*; -use crate::Vec; -use alloc::vec; -use ironrdp_pdu::cursor::{ReadCursor, WriteCursor}; -use ironrdp_pdu::PduDecode; -use lazy_static::lazy_static; const LENGTH: u32 = 0xC7B; const CHANNEL_ID: u32 = 0x03; @@ -107,69 +102,53 @@ lazy_static! { result.extend(DATA); result }; - static ref DECODED: DataFirstPdu = { - let mut res = DataFirstPdu::new(CHANNEL_ID, LENGTH, DATA.to_vec()); - res.header.cb_id = FieldType::U8; - res.header.sp = FieldType::U16; - res - }; + static ref DECODED_CLIENT: DrdynvcClientPdu = DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst( + DataFirstPdu::new(CHANNEL_ID, LENGTH, DATA.to_vec()) + .with_cb_id_type(FieldType::U8) + .with_sp_type(FieldType::U16) + )); + static ref DECODED_SERVER: DrdynvcServerPdu = DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst( + DataFirstPdu::new(CHANNEL_ID, LENGTH, DATA.to_vec()) + .with_cb_id_type(FieldType::U8) + .with_sp_type(FieldType::U16) + )); static ref EDGE_CASE_ENCODED: Vec = { let mut result = EDGE_CASE_PREFIX.to_vec(); result.append(&mut EDGE_CASE_DATA.to_vec()); result }; - static ref EDGE_CASE_DECODED: DataFirstPdu = { - let mut res = DataFirstPdu::new(EDGE_CASE_CHANNEL_ID, EDGE_CASE_LENGTH, EDGE_CASE_DATA.to_vec()); - res.header.cb_id = FieldType::U8; - res.header.sp = FieldType::U16; - res - }; + static ref EDGE_CASE_DECODED_CLIENT: DrdynvcClientPdu = DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst( + DataFirstPdu::new(EDGE_CASE_CHANNEL_ID, EDGE_CASE_LENGTH, EDGE_CASE_DATA.to_vec()) + .with_cb_id_type(FieldType::U8) + .with_sp_type(FieldType::U16) + )); + static ref EDGE_CASE_DECODED_SERVER: DrdynvcServerPdu = DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst( + DataFirstPdu::new(EDGE_CASE_CHANNEL_ID, EDGE_CASE_LENGTH, EDGE_CASE_DATA.to_vec()) + .with_cb_id_type(FieldType::U8) + .with_sp_type(FieldType::U16) + )); } #[test] fn decodes_data_first() { - let mut src = ReadCursor::new(&ENCODED); - match DrdynvcClientPdu::decode(&mut src).unwrap() { - DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*DECODED, df), - _ => panic!("Expected DataFirst"), - } - - let mut src = ReadCursor::new(&ENCODED); - match DrdynvcServerPdu::decode(&mut src).unwrap() { - DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*DECODED, df), - _ => panic!("Expected DataFirst"), - } + test_decodes(&ENCODED, &*DECODED_CLIENT); + test_decodes(&ENCODED, &*DECODED_SERVER); } #[test] fn encodes_data_first() { - let data_first = &*DECODED; - let mut buffer = vec![0x00; data_first.size()]; - let mut cursor = WriteCursor::new(&mut buffer); - data_first.encode(&mut cursor).unwrap(); - assert_eq!(ENCODED.as_slice(), buffer.as_slice()); + test_encodes(&*DECODED_CLIENT, &ENCODED); + test_encodes(&*DECODED_SERVER, &ENCODED); } #[test] fn decodes_data_first_edge_case() { - let mut src = ReadCursor::new(&EDGE_CASE_ENCODED); - match DrdynvcClientPdu::decode(&mut src).unwrap() { - DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*EDGE_CASE_DECODED, df), - _ => panic!("Expected DataFirst"), - } - - let mut src = ReadCursor::new(&EDGE_CASE_ENCODED); - match DrdynvcServerPdu::decode(&mut src).unwrap() { - DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst(df)) => assert_eq!(*EDGE_CASE_DECODED, df), - _ => panic!("Expected DataFirst"), - } + test_decodes(&EDGE_CASE_ENCODED, &*EDGE_CASE_DECODED_CLIENT); + test_decodes(&EDGE_CASE_ENCODED, &*EDGE_CASE_DECODED_SERVER); } #[test] fn encodes_data_first_edge_case() { - let data_first = &*EDGE_CASE_DECODED; - let mut buffer = vec![0x00; data_first.size()]; - let mut cursor = WriteCursor::new(&mut buffer); - data_first.encode(&mut cursor).unwrap(); - assert_eq!(EDGE_CASE_ENCODED.as_slice(), buffer.as_slice()); + test_encodes(&*EDGE_CASE_DECODED_CLIENT, &EDGE_CASE_ENCODED); + test_encodes(&*EDGE_CASE_DECODED_SERVER, &EDGE_CASE_ENCODED); } diff --git a/crates/ironrdp-testsuite-core/tests/dvc/mod.rs b/crates/ironrdp-testsuite-core/tests/dvc/mod.rs new file mode 100644 index 000000000..1f8c3a3b0 --- /dev/null +++ b/crates/ironrdp-testsuite-core/tests/dvc/mod.rs @@ -0,0 +1,32 @@ +use ironrdp_dvc::pdu::ClosePdu; +use ironrdp_dvc::pdu::DataPdu; +use ironrdp_dvc::pdu::{CapabilitiesRequestPdu, CapabilitiesResponsePdu, CapsVersion}; +use ironrdp_dvc::pdu::{CreateRequestPdu, CreateResponsePdu, CreationStatus}; +use ironrdp_dvc::pdu::{DataFirstPdu, FieldType}; +use ironrdp_dvc::pdu::{DrdynvcClientPdu, DrdynvcDataPdu, DrdynvcServerPdu}; +use ironrdp_pdu::PduEncode; +use ironrdp_pdu::{ + cursor::{ReadCursor, WriteCursor}, + PduDecode, +}; +use lazy_static::lazy_static; + +// TODO: This likely generalizes to many tests and can thus be reused outside of this module. +fn test_encodes(data: &T, expected: &[u8]) { + let mut buffer = vec![0x00; data.size()]; + let mut cursor = WriteCursor::new(&mut buffer); + data.encode(&mut cursor).unwrap(); + assert_eq!(expected, buffer.as_slice()); +} + +// TODO: This likely generalizes to many tests and can thus be reused outside of this module. +fn test_decodes<'a, T: PduDecode<'a> + PartialEq + std::fmt::Debug>(encoded: &'a [u8], expected: &T) { + let mut src = ReadCursor::new(encoded); + assert_eq!(*expected, T::decode(&mut src).unwrap()); +} + +mod capabilities; +mod close; +mod create; +mod data; +mod data_first; diff --git a/crates/ironrdp-testsuite-core/tests/main.rs b/crates/ironrdp-testsuite-core/tests/main.rs index 7efa52b73..7c55cffec 100644 --- a/crates/ironrdp-testsuite-core/tests/main.rs +++ b/crates/ironrdp-testsuite-core/tests/main.rs @@ -11,6 +11,7 @@ mod clipboard; mod displaycontrol; +mod dvc; mod fuzz_regression; mod graphics; mod input; From 9503901f066b2341480c1a2b4a35357d8c262944 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 21 Mar 2024 07:17:20 -0700 Subject: [PATCH 42/84] Add admonition/TODOs for byteorder, num-derive, num-traits, lazy_static --- Cargo.toml | 9 +++++++-- crates/ironrdp-ainput/Cargo.toml | 4 ++-- crates/ironrdp-dvc/Cargo.toml | 2 +- crates/ironrdp-graphics/Cargo.toml | 8 ++++---- crates/ironrdp-pdu/Cargo.toml | 8 ++++---- crates/ironrdp-testsuite-core/Cargo.toml | 2 +- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index de56c28b3..9895d9bd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,15 +51,20 @@ ironrdp-tls = { version = "0.1", path = "crates/ironrdp-tls" } ironrdp-tokio = { version = "0.1", path = "crates/ironrdp-tokio" } ironrdp = { version = "0.5", path = "crates/ironrdp" } +bitflags = "2.4" expect-test = "1" +png = "0.17" proptest = "1.4" rstest = "0.18" sspi = "0.11" tracing = { version = "0.1", features = ["log"] } thiserror = "1.0" -png = "0.17" -bitflags = "2.4" + +# Note: we are trying to move away from using these crates. +# They are being kept around for now for legacy compatibility, +# but new usage should be avoided. byteorder = "1.5" +lazy_static = "1.4" # prefer https://doc.rust-lang.org/std/sync/struct.OnceLock.html num-derive = "0.4" num-traits = "0.2" diff --git a/crates/ironrdp-ainput/Cargo.toml b/crates/ironrdp-ainput/Cargo.toml index b0d424c64..6ef58e3e8 100644 --- a/crates/ironrdp-ainput/Cargo.toml +++ b/crates/ironrdp-ainput/Cargo.toml @@ -20,5 +20,5 @@ ironrdp-dvc.workspace = true ironrdp-pdu.workspace = true bitflags.workspace = true -num-derive.workspace = true -num-traits.workspace = true +num-derive.workspace = true # TODO: remove +num-traits.workspace = true # TODO: remove diff --git a/crates/ironrdp-dvc/Cargo.toml b/crates/ironrdp-dvc/Cargo.toml index fcc0697b6..38b58efd0 100644 --- a/crates/ironrdp-dvc/Cargo.toml +++ b/crates/ironrdp-dvc/Cargo.toml @@ -26,4 +26,4 @@ tracing.workspace = true slab = "0.4.9" [dev-dependencies] -lazy_static = "1.4" +lazy_static.workspace = true # TODO: remove diff --git a/crates/ironrdp-graphics/Cargo.toml b/crates/ironrdp-graphics/Cargo.toml index 7d21ee166..4e0d49663 100644 --- a/crates/ironrdp-graphics/Cargo.toml +++ b/crates/ironrdp-graphics/Cargo.toml @@ -19,12 +19,12 @@ doctest = false bit_field = "0.10" bitflags.workspace = true bitvec = "1.0" -byteorder.workspace = true +byteorder.workspace = true # TODO: remove ironrdp-error.workspace = true ironrdp-pdu = { workspace = true, features = ["std"] } -lazy_static = "1.4" -num-derive.workspace = true -num-traits.workspace = true +lazy_static.workspace = true # TODO: remove +num-derive.workspace = true # TODO: remove +num-traits.workspace = true # TODO: remove thiserror.workspace = true [dev-dependencies] diff --git a/crates/ironrdp-pdu/Cargo.toml b/crates/ironrdp-pdu/Cargo.toml index a1b901d08..0bd578863 100644 --- a/crates/ironrdp-pdu/Cargo.toml +++ b/crates/ironrdp-pdu/Cargo.toml @@ -27,14 +27,14 @@ tap = "1" # TODO: get rid of these dependencies (related code should probably go into another crate) bit_field = "0.10" -byteorder.workspace = true +byteorder.workspace = true # TODO: remove der-parser = "8.2" thiserror.workspace = true md5 = { package = "md-5", version = "0.10" } num-bigint = "0.4" -num-derive.workspace = true +num-derive.workspace = true # TODO: remove num-integer = "0.1" -num-traits.workspace = true +num-traits.workspace = true # TODO: remove sha1 = "0.10" x509-cert = { version = "0.2", default-features = false, features = ["std"] } pkcs1 = "0.7" @@ -42,4 +42,4 @@ pkcs1 = "0.7" [dev-dependencies] expect-test.workspace = true ironrdp-testsuite-core.workspace = true # TODO: move more tests under ironrdp-testsuite-core itself -lazy_static = "1.4" +lazy_static.workspace = true # TODO: remove diff --git a/crates/ironrdp-testsuite-core/Cargo.toml b/crates/ironrdp-testsuite-core/Cargo.toml index c33b567bd..633c0610d 100644 --- a/crates/ironrdp-testsuite-core/Cargo.toml +++ b/crates/ironrdp-testsuite-core/Cargo.toml @@ -18,7 +18,7 @@ harness = true [dependencies] array-concat = "0.5" ironrdp-pdu.workspace = true -lazy_static = "1.4" +lazy_static.workspace = true # TODO: remove paste = "1" [dev-dependencies] From 368df9555df4980073a151f518f9b6e030568e13 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 21 Mar 2024 08:05:58 -0700 Subject: [PATCH 43/84] Uses OnceLock instead of lazy_static in the dvc tests --- Cargo.lock | 1 - crates/ironrdp-dvc/Cargo.toml | 3 - crates/ironrdp-graphics/Cargo.toml | 2 +- crates/ironrdp-pdu/Cargo.toml | 2 +- crates/ironrdp-testsuite-core/Cargo.toml | 2 +- .../tests/dvc/capabilities.rs | 41 +++++--- .../ironrdp-testsuite-core/tests/dvc/close.rs | 22 +++-- .../tests/dvc/create.rs | 23 +++-- .../ironrdp-testsuite-core/tests/dvc/data.rs | 32 ++++--- .../tests/dvc/data_first.rs | 96 ++++++++++++------- .../ironrdp-testsuite-core/tests/dvc/mod.rs | 2 +- 11 files changed, 140 insertions(+), 86 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e58afcaf5..7997ca82d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1782,7 +1782,6 @@ version = "0.1.0" dependencies = [ "ironrdp-pdu", "ironrdp-svc", - "lazy_static", "slab", "tracing", ] diff --git a/crates/ironrdp-dvc/Cargo.toml b/crates/ironrdp-dvc/Cargo.toml index 38b58efd0..8f8908e9a 100644 --- a/crates/ironrdp-dvc/Cargo.toml +++ b/crates/ironrdp-dvc/Cargo.toml @@ -24,6 +24,3 @@ ironrdp-svc.workspace = true ironrdp-pdu = { workspace = true, features = ["alloc"] } tracing.workspace = true slab = "0.4.9" - -[dev-dependencies] -lazy_static.workspace = true # TODO: remove diff --git a/crates/ironrdp-graphics/Cargo.toml b/crates/ironrdp-graphics/Cargo.toml index 4e0d49663..2f9657cca 100644 --- a/crates/ironrdp-graphics/Cargo.toml +++ b/crates/ironrdp-graphics/Cargo.toml @@ -22,7 +22,7 @@ bitvec = "1.0" byteorder.workspace = true # TODO: remove ironrdp-error.workspace = true ironrdp-pdu = { workspace = true, features = ["std"] } -lazy_static.workspace = true # TODO: remove +lazy_static.workspace = true # TODO: remove in favor of https://doc.rust-lang.org/std/sync/struct.OnceLock.html num-derive.workspace = true # TODO: remove num-traits.workspace = true # TODO: remove thiserror.workspace = true diff --git a/crates/ironrdp-pdu/Cargo.toml b/crates/ironrdp-pdu/Cargo.toml index 0bd578863..9da7b34ff 100644 --- a/crates/ironrdp-pdu/Cargo.toml +++ b/crates/ironrdp-pdu/Cargo.toml @@ -42,4 +42,4 @@ pkcs1 = "0.7" [dev-dependencies] expect-test.workspace = true ironrdp-testsuite-core.workspace = true # TODO: move more tests under ironrdp-testsuite-core itself -lazy_static.workspace = true # TODO: remove +lazy_static.workspace = true # TODO: remove in favor of https://doc.rust-lang.org/std/sync/struct.OnceLock.html diff --git a/crates/ironrdp-testsuite-core/Cargo.toml b/crates/ironrdp-testsuite-core/Cargo.toml index 633c0610d..9d4212260 100644 --- a/crates/ironrdp-testsuite-core/Cargo.toml +++ b/crates/ironrdp-testsuite-core/Cargo.toml @@ -18,7 +18,7 @@ harness = true [dependencies] array-concat = "0.5" ironrdp-pdu.workspace = true -lazy_static.workspace = true # TODO: remove +lazy_static.workspace = true # TODO: remove in favor of https://doc.rust-lang.org/std/sync/struct.OnceLock.html paste = "1" [dev-dependencies] diff --git a/crates/ironrdp-testsuite-core/tests/dvc/capabilities.rs b/crates/ironrdp-testsuite-core/tests/dvc/capabilities.rs index e44f7ba94..9a398e8b1 100644 --- a/crates/ironrdp-testsuite-core/tests/dvc/capabilities.rs +++ b/crates/ironrdp-testsuite-core/tests/dvc/capabilities.rs @@ -4,43 +4,54 @@ const REQ_V1_ENCODED: [u8; 4] = [0x50, 0x00, 0x01, 0x00]; const REQ_V2_ENCODED: [u8; 12] = [0x50, 0x00, 0x02, 0x00, 0x33, 0x33, 0x11, 0x11, 0x3d, 0x0a, 0xa7, 0x04]; const RESP_V1_ENCODED: [u8; 4] = [0x50, 0x00, 0x01, 0x00]; -lazy_static! { - static ref REQ_V1_DECODED_SERVER: DrdynvcServerPdu = - DrdynvcServerPdu::Capabilities(CapabilitiesRequestPdu::new(CapsVersion::V1, None)); - static ref REQ_V2_DECODED_SERVER: DrdynvcServerPdu = DrdynvcServerPdu::Capabilities(CapabilitiesRequestPdu::new( - CapsVersion::V2, - Some([0x3333, 0x1111, 0x0a3d, 0x04a7]) - )); - static ref RESP_V1_DECODED_CLIENT: DrdynvcClientPdu = - DrdynvcClientPdu::Capabilities(CapabilitiesResponsePdu::new(CapsVersion::V1)); +static REQ_V1_DECODED_SERVER: OnceLock = OnceLock::new(); +static REQ_V2_DECODED_SERVER: OnceLock = OnceLock::new(); +static RESP_V1_DECODED_CLIENT: OnceLock = OnceLock::new(); + +fn req_v1_decoded_server() -> &'static DrdynvcServerPdu { + REQ_V1_DECODED_SERVER + .get_or_init(|| DrdynvcServerPdu::Capabilities(CapabilitiesRequestPdu::new(CapsVersion::V1, None))) +} + +fn req_v2_decoded_server() -> &'static DrdynvcServerPdu { + REQ_V2_DECODED_SERVER.get_or_init(|| { + DrdynvcServerPdu::Capabilities(CapabilitiesRequestPdu::new( + CapsVersion::V2, + Some([0x3333, 0x1111, 0x0a3d, 0x04a7]), + )) + }) +} + +fn resp_v1_decoded_client() -> &'static DrdynvcClientPdu { + RESP_V1_DECODED_CLIENT.get_or_init(|| DrdynvcClientPdu::Capabilities(CapabilitiesResponsePdu::new(CapsVersion::V1))) } #[test] fn decodes_request_v1() { - test_decodes(&REQ_V1_ENCODED, &*REQ_V1_DECODED_SERVER); + test_decodes(&REQ_V1_ENCODED, req_v1_decoded_server()); } #[test] fn encodes_request_v1() { - test_encodes(&*REQ_V1_DECODED_SERVER, &REQ_V1_ENCODED); + test_encodes(req_v1_decoded_server(), &REQ_V1_ENCODED); } #[test] fn decodes_request_v2() { - test_decodes(&REQ_V2_ENCODED, &*REQ_V2_DECODED_SERVER); + test_decodes(&REQ_V2_ENCODED, req_v2_decoded_server()); } #[test] fn encodes_request_v2() { - test_encodes(&*REQ_V2_DECODED_SERVER, &REQ_V2_ENCODED); + test_encodes(req_v2_decoded_server(), &REQ_V2_ENCODED); } #[test] fn decodes_response_v1() { - test_decodes(&RESP_V1_ENCODED, &*RESP_V1_DECODED_CLIENT); + test_decodes(&RESP_V1_ENCODED, resp_v1_decoded_client()); } #[test] fn encodes_response_v1() { - test_encodes(&*RESP_V1_DECODED_CLIENT, &RESP_V1_ENCODED); + test_encodes(resp_v1_decoded_client(), &RESP_V1_ENCODED); } diff --git a/crates/ironrdp-testsuite-core/tests/dvc/close.rs b/crates/ironrdp-testsuite-core/tests/dvc/close.rs index 1d71074c2..c7279de1a 100644 --- a/crates/ironrdp-testsuite-core/tests/dvc/close.rs +++ b/crates/ironrdp-testsuite-core/tests/dvc/close.rs @@ -3,21 +3,25 @@ use super::*; const CHANNEL_ID: u32 = 0x0303; const ENCODED: [u8; 3] = [0x41, 0x03, 0x03]; -lazy_static! { - static ref DECODED_CLIENT: DrdynvcClientPdu = - DrdynvcClientPdu::Close(ClosePdu::new(CHANNEL_ID).with_cb_id_type(FieldType::U16)); - static ref DECODED_SERVER: DrdynvcServerPdu = - DrdynvcServerPdu::Close(ClosePdu::new(CHANNEL_ID).with_cb_id_type(FieldType::U16)); +static DECODED_CLIENT: OnceLock = OnceLock::new(); +static DECODED_SERVER: OnceLock = OnceLock::new(); + +fn decoded_client() -> &'static DrdynvcClientPdu { + DECODED_CLIENT.get_or_init(|| DrdynvcClientPdu::Close(ClosePdu::new(CHANNEL_ID).with_cb_id_type(FieldType::U16))) +} + +fn decoded_server() -> &'static DrdynvcServerPdu { + DECODED_SERVER.get_or_init(|| DrdynvcServerPdu::Close(ClosePdu::new(CHANNEL_ID).with_cb_id_type(FieldType::U16))) } #[test] fn decodes_close() { - test_decodes(&ENCODED, &*DECODED_CLIENT); - test_decodes(&ENCODED, &*DECODED_SERVER); + test_decodes(&ENCODED, decoded_client()); + test_decodes(&ENCODED, decoded_server()); } #[test] fn encodes_close() { - test_encodes(&*DECODED_CLIENT, &ENCODED); - test_encodes(&*DECODED_SERVER, &ENCODED); + test_encodes(decoded_client(), &ENCODED); + test_encodes(decoded_server(), &ENCODED); } diff --git a/crates/ironrdp-testsuite-core/tests/dvc/create.rs b/crates/ironrdp-testsuite-core/tests/dvc/create.rs index 8b459b1af..9740ffe05 100644 --- a/crates/ironrdp-testsuite-core/tests/dvc/create.rs +++ b/crates/ironrdp-testsuite-core/tests/dvc/create.rs @@ -4,29 +4,34 @@ const CHANNEL_ID: u32 = 0x0000_0003; const REQ_ENCODED: [u8; 10] = [0x10, 0x03, 0x74, 0x65, 0x73, 0x74, 0x64, 0x76, 0x63, 0x00]; const RESP_ENCODED: [u8; 6] = [0x10, 0x03, 0x00, 0x00, 0x00, 0x00]; -lazy_static! { - static ref REQ_DECODED_SERVER: DrdynvcServerPdu = - DrdynvcServerPdu::Create(CreateRequestPdu::new(CHANNEL_ID, String::from("testdvc"))); - static ref RESP_DECODED_CLIENT: DrdynvcClientPdu = - DrdynvcClientPdu::Create(CreateResponsePdu::new(CHANNEL_ID, CreationStatus::OK)); +static REQ_DECODED_SERVER: OnceLock = OnceLock::new(); +static RESP_DECODED_CLIENT: OnceLock = OnceLock::new(); + +fn req_decoded_server() -> &'static DrdynvcServerPdu { + REQ_DECODED_SERVER + .get_or_init(|| DrdynvcServerPdu::Create(CreateRequestPdu::new(CHANNEL_ID, String::from("testdvc")))) +} + +fn resp_decoded_client() -> &'static DrdynvcClientPdu { + RESP_DECODED_CLIENT.get_or_init(|| DrdynvcClientPdu::Create(CreateResponsePdu::new(CHANNEL_ID, CreationStatus::OK))) } #[test] fn decodes_create_request() { - test_decodes(&REQ_ENCODED, &*REQ_DECODED_SERVER); + test_decodes(&REQ_ENCODED, req_decoded_server()); } #[test] fn encodes_create_request() { - test_encodes(&*REQ_DECODED_SERVER, &REQ_ENCODED); + test_encodes(req_decoded_server(), &REQ_ENCODED); } #[test] fn decodes_create_response() { - test_decodes(&RESP_ENCODED, &*RESP_DECODED_CLIENT); + test_decodes(&RESP_ENCODED, resp_decoded_client()); } #[test] fn encodes_create_response() { - test_encodes(&*RESP_DECODED_CLIENT, &RESP_ENCODED); + test_encodes(resp_decoded_client(), &RESP_ENCODED); } diff --git a/crates/ironrdp-testsuite-core/tests/dvc/data.rs b/crates/ironrdp-testsuite-core/tests/dvc/data.rs index f42e216e5..93d40462f 100644 --- a/crates/ironrdp-testsuite-core/tests/dvc/data.rs +++ b/crates/ironrdp-testsuite-core/tests/dvc/data.rs @@ -4,26 +4,34 @@ const CHANNEL_ID: u32 = 0x03; const PREFIX: [u8; 2] = [0x30, 0x03]; const DATA: [u8; 12] = [0x71; 12]; -lazy_static! { - static ref ENCODED: Vec = { +static ENCODED: OnceLock> = OnceLock::new(); +static DECODED_CLIENT: OnceLock = OnceLock::new(); +static DECODED_SERVER: OnceLock = OnceLock::new(); + +fn encoded() -> &'static Vec { + ENCODED.get_or_init(|| { let mut result = PREFIX.to_vec(); - result.extend(DATA); + result.extend(&DATA); result - }; - static ref DECODED_CLIENT: DrdynvcClientPdu = - DrdynvcClientPdu::Data(DrdynvcDataPdu::Data(DataPdu::new(CHANNEL_ID, DATA.to_vec()))); - static ref DECODED_SERVER: DrdynvcServerPdu = - DrdynvcServerPdu::Data(DrdynvcDataPdu::Data(DataPdu::new(CHANNEL_ID, DATA.to_vec()))); + }) +} + +fn decoded_client() -> &'static DrdynvcClientPdu { + DECODED_CLIENT.get_or_init(|| DrdynvcClientPdu::Data(DrdynvcDataPdu::Data(DataPdu::new(CHANNEL_ID, DATA.to_vec())))) +} + +fn decoded_server() -> &'static DrdynvcServerPdu { + DECODED_SERVER.get_or_init(|| DrdynvcServerPdu::Data(DrdynvcDataPdu::Data(DataPdu::new(CHANNEL_ID, DATA.to_vec())))) } #[test] fn decodes_data() { - test_decodes(&ENCODED, &*DECODED_CLIENT); - test_decodes(&ENCODED, &*DECODED_SERVER); + test_decodes(encoded(), decoded_client()); + test_decodes(encoded(), decoded_server()); } #[test] fn encodes_data() { - test_encodes(&*DECODED_CLIENT, &ENCODED); - test_encodes(&*DECODED_SERVER, &ENCODED); + test_encodes(decoded_client(), encoded()); + test_encodes(decoded_server(), encoded()); } diff --git a/crates/ironrdp-testsuite-core/tests/dvc/data_first.rs b/crates/ironrdp-testsuite-core/tests/dvc/data_first.rs index 8fee7b891..81c7605cb 100644 --- a/crates/ironrdp-testsuite-core/tests/dvc/data_first.rs +++ b/crates/ironrdp-testsuite-core/tests/dvc/data_first.rs @@ -96,59 +96,89 @@ const EDGE_CASE_DATA: [u8; EDGE_CASE_LENGTH as usize] = [ 0x74, 0x36, 0x76, 0xa6, 0x53, 0x9f, 0x33, 0x56, 0x98, 0x88, 0x92, 0x2a, 0xd1, 0x90, 0x1, ]; -lazy_static! { - static ref ENCODED: Vec = { +static ENCODED: OnceLock> = OnceLock::new(); +static DECODED_CLIENT: OnceLock = OnceLock::new(); +static DECODED_SERVER: OnceLock = OnceLock::new(); +static EDGE_CASE_ENCODED: OnceLock> = OnceLock::new(); +static EDGE_CASE_DECODED_CLIENT: OnceLock = OnceLock::new(); +static EDGE_CASE_DECODED_SERVER: OnceLock = OnceLock::new(); + +fn encoded() -> &'static Vec { + ENCODED.get_or_init(|| { let mut result = PREFIX.to_vec(); result.extend(DATA); result - }; - static ref DECODED_CLIENT: DrdynvcClientPdu = DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst( - DataFirstPdu::new(CHANNEL_ID, LENGTH, DATA.to_vec()) - .with_cb_id_type(FieldType::U8) - .with_sp_type(FieldType::U16) - )); - static ref DECODED_SERVER: DrdynvcServerPdu = DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst( - DataFirstPdu::new(CHANNEL_ID, LENGTH, DATA.to_vec()) - .with_cb_id_type(FieldType::U8) - .with_sp_type(FieldType::U16) - )); - static ref EDGE_CASE_ENCODED: Vec = { + }) +} + +fn decoded_client() -> &'static DrdynvcClientPdu { + DECODED_CLIENT.get_or_init(|| { + DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst( + DataFirstPdu::new(CHANNEL_ID, LENGTH, DATA.to_vec()) + .with_cb_id_type(FieldType::U8) + .with_sp_type(FieldType::U16), + )) + }) +} + +fn decoded_server() -> &'static DrdynvcServerPdu { + DECODED_SERVER.get_or_init(|| { + DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst( + DataFirstPdu::new(CHANNEL_ID, LENGTH, DATA.to_vec()) + .with_cb_id_type(FieldType::U8) + .with_sp_type(FieldType::U16), + )) + }) +} + +fn edge_case_encoded() -> &'static Vec { + EDGE_CASE_ENCODED.get_or_init(|| { let mut result = EDGE_CASE_PREFIX.to_vec(); result.append(&mut EDGE_CASE_DATA.to_vec()); result - }; - static ref EDGE_CASE_DECODED_CLIENT: DrdynvcClientPdu = DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst( - DataFirstPdu::new(EDGE_CASE_CHANNEL_ID, EDGE_CASE_LENGTH, EDGE_CASE_DATA.to_vec()) - .with_cb_id_type(FieldType::U8) - .with_sp_type(FieldType::U16) - )); - static ref EDGE_CASE_DECODED_SERVER: DrdynvcServerPdu = DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst( - DataFirstPdu::new(EDGE_CASE_CHANNEL_ID, EDGE_CASE_LENGTH, EDGE_CASE_DATA.to_vec()) - .with_cb_id_type(FieldType::U8) - .with_sp_type(FieldType::U16) - )); + }) +} + +fn edge_case_decoded_client() -> &'static DrdynvcClientPdu { + EDGE_CASE_DECODED_CLIENT.get_or_init(|| { + DrdynvcClientPdu::Data(DrdynvcDataPdu::DataFirst( + DataFirstPdu::new(EDGE_CASE_CHANNEL_ID, EDGE_CASE_LENGTH, EDGE_CASE_DATA.to_vec()) + .with_cb_id_type(FieldType::U8) + .with_sp_type(FieldType::U16), + )) + }) +} + +fn edge_case_decoded_server() -> &'static DrdynvcServerPdu { + EDGE_CASE_DECODED_SERVER.get_or_init(|| { + DrdynvcServerPdu::Data(DrdynvcDataPdu::DataFirst( + DataFirstPdu::new(EDGE_CASE_CHANNEL_ID, EDGE_CASE_LENGTH, EDGE_CASE_DATA.to_vec()) + .with_cb_id_type(FieldType::U8) + .with_sp_type(FieldType::U16), + )) + }) } #[test] fn decodes_data_first() { - test_decodes(&ENCODED, &*DECODED_CLIENT); - test_decodes(&ENCODED, &*DECODED_SERVER); + test_decodes(encoded(), decoded_client()); + test_decodes(encoded(), decoded_server()); } #[test] fn encodes_data_first() { - test_encodes(&*DECODED_CLIENT, &ENCODED); - test_encodes(&*DECODED_SERVER, &ENCODED); + test_encodes(decoded_client(), encoded()); + test_encodes(decoded_server(), encoded()); } #[test] fn decodes_data_first_edge_case() { - test_decodes(&EDGE_CASE_ENCODED, &*EDGE_CASE_DECODED_CLIENT); - test_decodes(&EDGE_CASE_ENCODED, &*EDGE_CASE_DECODED_SERVER); + test_decodes(edge_case_encoded(), edge_case_decoded_client()); + test_decodes(edge_case_encoded(), edge_case_decoded_server()); } #[test] fn encodes_data_first_edge_case() { - test_encodes(&*EDGE_CASE_DECODED_CLIENT, &EDGE_CASE_ENCODED); - test_encodes(&*EDGE_CASE_DECODED_SERVER, &EDGE_CASE_ENCODED); + test_encodes(edge_case_decoded_client(), edge_case_encoded()); + test_encodes(edge_case_decoded_server(), edge_case_encoded()); } diff --git a/crates/ironrdp-testsuite-core/tests/dvc/mod.rs b/crates/ironrdp-testsuite-core/tests/dvc/mod.rs index 1f8c3a3b0..5e243e545 100644 --- a/crates/ironrdp-testsuite-core/tests/dvc/mod.rs +++ b/crates/ironrdp-testsuite-core/tests/dvc/mod.rs @@ -9,7 +9,7 @@ use ironrdp_pdu::{ cursor::{ReadCursor, WriteCursor}, PduDecode, }; -use lazy_static::lazy_static; +use std::sync::OnceLock; // TODO: This likely generalizes to many tests and can thus be reused outside of this module. fn test_encodes(data: &T, expected: &[u8]) { From 567e537fb100e375357f88593467181ef8debfe0 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 21 Mar 2024 12:55:50 -0600 Subject: [PATCH 44/84] Attempts to implement the connection activation sequence rerun upon receipt of a DeactivateAll in the web client --- crates/ironrdp-connector/src/connection.rs | 9 ++-- .../src/connection_activation.rs | 6 +++ crates/ironrdp-connector/src/lib.rs | 2 +- crates/ironrdp-session/src/active_stage.rs | 4 ++ crates/ironrdp-web/src/session.rs | 51 ++++++++++++++++++- 5 files changed, 66 insertions(+), 6 deletions(-) diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 77596ee4a..6538a57b7 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -552,15 +552,18 @@ impl Sequence for ClientConnector { io_channel_id, user_channel_id, desktop_size, + graphics_config, + no_server_pointer, + pointer_software_rendering, } => ClientConnectorState::Connected { result: ConnectionResult { io_channel_id, user_channel_id, static_channels: mem::take(&mut self.static_channels), desktop_size, - graphics_config: self.config.graphics.clone(), - no_server_pointer: self.config.no_server_pointer, - pointer_software_rendering: self.config.pointer_software_rendering, + graphics_config, + no_server_pointer, + pointer_software_rendering, connection_activation, }, }, diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs index 686d91b47..80a540da7 100644 --- a/crates/ironrdp-connector/src/connection_activation.rs +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -187,6 +187,9 @@ impl Sequence for ConnectionActivationSequence { io_channel_id, user_channel_id, desktop_size, + graphics_config: self.config.graphics, + no_server_pointer: self.config.no_server_pointer, + pointer_software_rendering: self.config.pointer_software_rendering, } }; @@ -218,6 +221,9 @@ pub enum ConnectionActivationState { io_channel_id: u16, user_channel_id: u16, desktop_size: DesktopSize, + graphics_config: Option, + no_server_pointer: bool, + pointer_software_rendering: bool, }, } diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index ee842e139..b21312e09 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -37,7 +37,7 @@ pub struct DesktopSize { pub height: u16, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct GraphicsConfig { pub avc444: bool, diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index d04a579fc..900b31119 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -149,6 +149,10 @@ impl ActiveStage { Ok(stage_outputs) } + pub fn set_fastpath_processor(&mut self, processor: fast_path::Processor) { + self.fast_path_processor = processor; + } + /// Encodes client-side graceful shutdown request. Note that upon sending this request, /// client should wait for server's ShutdownDenied PDU before closing the connection. /// diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index b230e2d02..5f011e791 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -12,13 +12,15 @@ use gloo_net::websocket; use gloo_net::websocket::futures::WebSocket; use ironrdp::cliprdr::backend::ClipboardMessage; use ironrdp::cliprdr::CliprdrClient; +use ironrdp::connector::connection_activation::ConnectionActivationState; use ironrdp::connector::credssp::KerberosConfig; use ironrdp::connector::{self, ClientConnector, Credentials}; use ironrdp::graphics::image_processing::PixelFormat; use ironrdp::pdu::input::fast_path::FastPathInputEvent; use ironrdp::pdu::write_buf::WriteBuf; use ironrdp::session::image::DecodedImage; -use ironrdp::session::{ActiveStage, ActiveStageOutput, GracefulDisconnectReason}; +use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDisconnectReason}; +use ironrdp_futures::single_connect_step_read; use rgb::AsPixels as _; use tap::prelude::*; use wasm_bindgen::prelude::*; @@ -384,6 +386,7 @@ pub struct Session { #[wasm_bindgen] impl Session { + #[allow(unused_assignments)] pub async fn run(&self) -> Result { let rdp_reader = self .rdp_reader @@ -603,7 +606,51 @@ impl Session { hotspot_y, })?; } - ActiveStageOutput::DeactivateAll(_) => todo!("DeactivateAll"), + ActiveStageOutput::DeactivateAll(mut connection_activation) => { + // Execute the Deactivation-Reactivation Sequence: + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dfc234ce-481a-4674-9a5d-2a7bafb14432 + debug!("Received Server Deactivate All PDU, executing Deactivation-Reactivation Sequence"); + let mut buf = WriteBuf::new(); + loop { + let written = + single_connect_step_read(&mut framed, &mut connection_activation, &mut buf).await?; + + if written.size().is_some() { + self.writer_tx + .unbounded_send(buf.filled().to_vec()) + .context("Send frame to writer task")?; + } + + if let ConnectionActivationState::Finalized { + io_channel_id, + user_channel_id, + desktop_size, + graphics_config: _, + no_server_pointer, + pointer_software_rendering, + } = connection_activation.state + { + // Reset the image we decode fastpath frames into with + // potentially updated session size. + // + // Note: the compiler apparently loses track of the control flow here, + // hence the need for #[allow(unused_assignments)] at the top of this + // function. + image = DecodedImage::new(PixelFormat::RgbA32, desktop_size.width, desktop_size.height); + // Create a new [`FastPathProcessor`] with potentially updated + // io/user channel ids. + active_stage.set_fastpath_processor( + fast_path::ProcessorBuilder { + io_channel_id, + user_channel_id, + no_server_pointer, + pointer_software_rendering, + } + .build(), + ); + } + } + } ActiveStageOutput::Terminate(reason) => break 'outer reason, } } From 83be7c8aba0b1e7b8cf899501a0f836900f29dcc Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 21 Mar 2024 13:30:09 -0600 Subject: [PATCH 45/84] Deletes gfx.rs --- crates/ironrdp-session/src/x224/gfx.rs | 198 ------------------------- 1 file changed, 198 deletions(-) delete mode 100644 crates/ironrdp-session/src/x224/gfx.rs diff --git a/crates/ironrdp-session/src/x224/gfx.rs b/crates/ironrdp-session/src/x224/gfx.rs deleted file mode 100644 index 8a1115b7a..000000000 --- a/crates/ironrdp-session/src/x224/gfx.rs +++ /dev/null @@ -1,198 +0,0 @@ -use bitflags::bitflags; -use ironrdp_connector::GraphicsConfig; -use ironrdp_graphics::zgfx; -use ironrdp_pdu::dvc::gfx::{ - CapabilitiesAdvertisePdu, CapabilitiesV103Flags, CapabilitiesV104Flags, CapabilitiesV107Flags, - CapabilitiesV10Flags, CapabilitiesV81Flags, CapabilitiesV8Flags, CapabilitySet, ClientPdu, FrameAcknowledgePdu, - QueueDepth, ServerPdu, -}; -use ironrdp_pdu::{decode, encode_vec}; - -use crate::x224::DynamicChannelDataHandler; -use crate::{SessionError, SessionErrorExt, SessionResult}; - -pub trait GfxHandler { - fn on_message(&self, message: ServerPdu) -> SessionResult>; -} - -pub(crate) struct Handler { - decompressor: zgfx::Decompressor, - decompressed_buffer: Vec, - frames_decoded: u32, - gfx_handler: Option>, -} - -impl Handler { - pub(crate) fn new(gfx_handler: Option>) -> Self { - Self { - decompressor: zgfx::Decompressor::new(), - decompressed_buffer: Vec::with_capacity(1024 * 16), - frames_decoded: 0, - gfx_handler, - } - } -} - -impl DynamicChannelDataHandler for Handler { - fn process_complete_data(&mut self, complete_data: Vec) -> SessionResult>> { - let mut client_pdu_buffer: Vec = Vec::new(); - self.decompressed_buffer.clear(); - self.decompressor - .decompress(complete_data.as_slice(), &mut self.decompressed_buffer)?; - let slice = &mut self.decompressed_buffer.as_slice(); - while !slice.is_empty() { - let gfx_pdu: ServerPdu = decode(slice).map_err(SessionError::pdu)?; - debug!("Got GFX PDU: {:?}", gfx_pdu); - - if let ServerPdu::EndFrame(end_frame_pdu) = &gfx_pdu { - self.frames_decoded += 1; - // Enqueue an acknowledge for every end frame - let client_pdu = ClientPdu::FrameAcknowledge(FrameAcknowledgePdu { - queue_depth: QueueDepth::Suspend, - frame_id: end_frame_pdu.frame_id, - total_frames_decoded: self.frames_decoded, - }); - debug!("Sending GFX PDU: {:?}", client_pdu); - client_pdu_buffer.append(&mut encode_vec(&client_pdu).map_err(SessionError::pdu)?); - } else { - // Handle the normal PDU - } - - // If there is a listener send all the data to the listener - if let Some(handler) = self.gfx_handler.as_mut() { - // Handle the normal PDU - let client_pdu = handler.on_message(gfx_pdu)?; - - if let Some(client_pdu) = client_pdu { - client_pdu_buffer.append(&mut encode_vec(&client_pdu).map_err(SessionError::pdu)?); - } - } - } - - if !client_pdu_buffer.is_empty() { - return Ok(Some(client_pdu_buffer)); - } - - Ok(None) - } -} - -bitflags! { - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - struct CapabilityVersion: u32 { - const V8 = 1 << 0; - const V8_1 = 1 << 1; - const V10 = 1 << 2; - const V10_1 = 1 << 3; - const V10_2 = 1 << 4; - const V10_3 = 1 << 5; - const V10_4 = 1 << 6; - const V10_5 = 1 << 7; - const V10_6 = 1 << 8; - const V10_6ERR = 1 << 9; - const V10_7 = 1 << 10; - } -} - -pub(crate) fn create_capabilities_advertise(graphics_config: &Option) -> SessionResult> { - let mut capabilities = Vec::new(); - - if let Some(config) = graphics_config { - let capability_version = CapabilityVersion::from_bits(config.capabilities) - .ok_or_else(|| reason_err!("GFX", "invalid capabilities mask: {:x}", config.capabilities))?; - - if capability_version.contains(CapabilityVersion::V8) { - let flags = if config.thin_client { - CapabilitiesV8Flags::THIN_CLIENT - } else if config.small_cache { - CapabilitiesV8Flags::SMALL_CACHE - } else { - CapabilitiesV8Flags::empty() - }; - - capabilities.push(CapabilitySet::V8 { flags }); - } - - if capability_version.contains(CapabilityVersion::V8_1) { - let mut flags = CapabilitiesV81Flags::empty(); - if config.thin_client { - flags |= CapabilitiesV81Flags::THIN_CLIENT; - } - - if config.small_cache { - flags |= CapabilitiesV81Flags::SMALL_CACHE; - } - - if config.h264 { - flags |= CapabilitiesV81Flags::AVC420_ENABLED; - } - - capabilities.push(CapabilitySet::V8_1 { flags }); - } - - if config.avc444 { - let flags = if config.small_cache { - CapabilitiesV10Flags::SMALL_CACHE - } else { - CapabilitiesV10Flags::empty() - }; - - if capability_version.contains(CapabilityVersion::V10) { - capabilities.push(CapabilitySet::V10 { flags }); - } - - if capability_version.contains(CapabilityVersion::V10_1) { - capabilities.push(CapabilitySet::V10_1 {}); - } - - if capability_version.contains(CapabilityVersion::V10_2) { - capabilities.push(CapabilitySet::V10_2 { flags }); - } - - if capability_version.contains(CapabilityVersion::V10_3) { - let flags = if config.thin_client { - CapabilitiesV103Flags::AVC_THIN_CLIENT - } else { - CapabilitiesV103Flags::empty() - }; - capabilities.push(CapabilitySet::V10_3 { flags }); - } - - let mut flags = if config.small_cache { - CapabilitiesV104Flags::SMALL_CACHE - } else { - CapabilitiesV104Flags::empty() - }; - - if config.thin_client { - flags |= CapabilitiesV104Flags::AVC_THIN_CLIENT; - } - - if capability_version.contains(CapabilityVersion::V10_4) { - capabilities.push(CapabilitySet::V10_4 { flags }); - } - - if capability_version.contains(CapabilityVersion::V10_5) { - capabilities.push(CapabilitySet::V10_5 { flags }); - } - - if capability_version.contains(CapabilityVersion::V10_6) { - capabilities.push(CapabilitySet::V10_6 { flags }); - } - - if capability_version.contains(CapabilityVersion::V10_6ERR) { - capabilities.push(CapabilitySet::V10_6Err { flags }); - } - - if capability_version.contains(CapabilityVersion::V10_7) { - capabilities.push(CapabilitySet::V10_7 { - flags: CapabilitiesV107Flags::from_bits(flags.bits()).unwrap(), - }); - } - } - } - info!(?capabilities); - let capabilities_advertise = ClientPdu::CapabilitiesAdvertise(CapabilitiesAdvertisePdu(capabilities)); - - encode_vec(&capabilities_advertise).map_err(SessionError::pdu) -} From 764abdd4ca79190a5c8561efdd64c7818bc38680 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Fri, 22 Mar 2024 16:31:23 -0500 Subject: [PATCH 46/84] Adds debug log for Deactivation-Reactivation Sequence completed --- crates/ironrdp-web/src/session.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 5f011e791..657a391b1 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -630,6 +630,7 @@ impl Session { pointer_software_rendering, } = connection_activation.state { + debug!("Deactivation-Reactivation Sequence completed"); // Reset the image we decode fastpath frames into with // potentially updated session size. // From 56c05b66312cd3f2edb74cc0b3c7716c8fce7b2a Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Fri, 22 Mar 2024 16:35:30 -0500 Subject: [PATCH 47/84] adds break for the deactivation-reactivation loop --- crates/ironrdp-web/src/session.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 657a391b1..72c405f58 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -611,7 +611,7 @@ impl Session { // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dfc234ce-481a-4674-9a5d-2a7bafb14432 debug!("Received Server Deactivate All PDU, executing Deactivation-Reactivation Sequence"); let mut buf = WriteBuf::new(); - loop { + 'activation_seq: loop { let written = single_connect_step_read(&mut framed, &mut connection_activation, &mut buf).await?; @@ -649,6 +649,7 @@ impl Session { } .build(), ); + break 'activation_seq; } } } From f4658d9d0dc0f0650202c135c28c01b66e76679c Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 12:18:43 -0700 Subject: [PATCH 48/84] Creates connection_activation module - Abstracts the CapabilitiesExchange and ConnectionFinalization sequences into a separate connection_activation module. This allows us to reuse them when we receive a Server Deactivate All. - Creates IoChannelPdu enum to be used to account for the potential of receiving a ServerDeactivateAll on the I/O channel --- Cargo.toml | 1 + crates/ironrdp-client/src/rdp.rs | 1 + crates/ironrdp-connector/src/connection.rs | 287 +++------------ .../src/connection_activation.rs | 341 ++++++++++++++++++ .../src/connection_finalization.rs | 4 +- crates/ironrdp-connector/src/legacy.rs | 32 +- crates/ironrdp-connector/src/lib.rs | 1 + crates/ironrdp-graphics/Cargo.toml | 2 +- crates/ironrdp-pdu/Cargo.toml | 2 +- crates/ironrdp-pdu/src/rdp/capability_sets.rs | 9 + crates/ironrdp-pdu/src/rdp/headers.rs | 40 ++ crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs | 6 + crates/ironrdp-session/src/active_stage.rs | 3 + crates/ironrdp-session/src/x224/mod.rs | 108 +++--- crates/ironrdp-web/src/session.rs | 1 + 15 files changed, 543 insertions(+), 295 deletions(-) create mode 100644 crates/ironrdp-connector/src/connection_activation.rs diff --git a/Cargo.toml b/Cargo.toml index e8938c6c4..a79ce9f7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ tracing = { version = "0.1", features = ["log"] } thiserror = "1.0" png = "0.17" bitflags = "2.4" +byteorder = "1.5" [profile.dev] opt-level = 1 diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index a7e5f2774..46f0db754 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -280,6 +280,7 @@ async fn active_session( ActiveStageOutput::PointerBitmap(_) => { // Not applicable, because we use the software cursor rendering. } + ActiveStageOutput::DeactivateAll(_) => todo!("DeactivateAll"), ActiveStageOutput::Terminate(reason) => break 'outer reason, } } diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 907c0efc7..46f53dd89 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -2,22 +2,19 @@ use std::borrow::Cow; use std::mem; use std::net::SocketAddr; -use ironrdp_pdu::rdp::capability_sets::CapabilitySet; use ironrdp_pdu::rdp::client_info::{PerformanceFlags, TimezoneInfo}; use ironrdp_pdu::write_buf::WriteBuf; use ironrdp_pdu::{decode, encode_vec, gcc, mcs, nego, rdp, PduEncode, PduHint}; use ironrdp_svc::{StaticChannelSet, StaticVirtualChannel, SvcClientProcessor}; use crate::channel_connection::{ChannelConnectionSequence, ChannelConnectionState}; -use crate::connection_finalization::ConnectionFinalizationSequence; +use crate::connection_activation::{ConnectionActivationSequence, ConnectionActivationState}; use crate::license_exchange::LicenseExchangeSequence; use crate::{ - encode_x224_packet, legacy, Config, ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize, Sequence, - State, Written, + encode_x224_packet, Config, ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize, Sequence, State, + Written, }; -const DEFAULT_POINTER_CACHE_SIZE: u16 = 32; - #[derive(Debug)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct ConnectionResult { @@ -75,14 +72,10 @@ pub enum ClientConnectorState { user_channel_id: u16, }, CapabilitiesExchange { - io_channel_id: u16, - user_channel_id: u16, + connection_activation: ConnectionActivationSequence, }, ConnectionFinalization { - io_channel_id: u16, - user_channel_id: u16, - desktop_size: DesktopSize, - connection_finalization: ConnectionFinalizationSequence, + connection_activation: ConnectionActivationSequence, }, Connected { result: ConnectionResult, @@ -104,8 +97,12 @@ impl State for ClientConnectorState { Self::ConnectTimeAutoDetection { .. } => "ConnectTimeAutoDetection", Self::LicensingExchange { .. } => "LicensingExchange", Self::MultitransportBootstrapping { .. } => "MultitransportBootstrapping", - Self::CapabilitiesExchange { .. } => "CapabilitiesExchange", - Self::ConnectionFinalization { .. } => "ConnectionFinalization", + Self::CapabilitiesExchange { + connection_activation, .. + } => connection_activation.state().name(), + Self::ConnectionFinalization { + connection_activation, .. + } => connection_activation.state().name(), Self::Connected { .. } => "Connected", } } @@ -203,11 +200,12 @@ impl Sequence for ClientConnector { ClientConnectorState::ConnectTimeAutoDetection { .. } => None, ClientConnectorState::LicensingExchange { license_exchange, .. } => license_exchange.next_pdu_hint(), ClientConnectorState::MultitransportBootstrapping { .. } => None, - ClientConnectorState::CapabilitiesExchange { .. } => Some(&ironrdp_pdu::X224_HINT), + ClientConnectorState::CapabilitiesExchange { + connection_activation, .. + } => connection_activation.next_pdu_hint(), ClientConnectorState::ConnectionFinalization { - connection_finalization, - .. - } => connection_finalization.next_pdu_hint(), + connection_activation, .. + } => connection_activation.next_pdu_hint(), ClientConnectorState::Connected { .. } => None, } } @@ -514,120 +512,57 @@ impl Sequence for ClientConnector { } => ( Written::Nothing, ClientConnectorState::CapabilitiesExchange { - io_channel_id, - user_channel_id, + connection_activation: ConnectionActivationSequence::new( + self.config.clone(), + io_channel_id, + user_channel_id, + ), }, ), //== Capabilities Exchange ==/ // The server sends the set of capabilities it supports to the client. ClientConnectorState::CapabilitiesExchange { - io_channel_id, - user_channel_id, + mut connection_activation, } => { - debug!("Capabilities Exchange"); - - let send_data_indication_ctx = legacy::decode_send_data_indication(input)?; - let share_control_ctx = legacy::decode_share_control(send_data_indication_ctx)?; - - debug!(message = ?share_control_ctx.pdu, "Received"); - - if share_control_ctx.channel_id != io_channel_id { - warn!( - io_channel_id, - share_control_ctx.channel_id, "Unexpected channel ID for received Share Control Pdu" - ); - } - - let capability_sets = if let rdp::headers::ShareControlPdu::ServerDemandActive(server_demand_active) = - share_control_ctx.pdu - { - server_demand_active.pdu.capability_sets - } else { - return Err(general_err!( - "unexpected Share Control Pdu (expected ServerDemandActive)", - )); - }; - - for c in &capability_sets { - if let rdp::capability_sets::CapabilitySet::General(g) = c { - if g.protocol_version != rdp::capability_sets::PROTOCOL_VER { - warn!(version = g.protocol_version, "Unexpected protocol version"); - } - break; - } + let written = connection_activation.step(input, output)?; + match connection_activation.state { + ConnectionActivationState::ConnectionFinalization { .. } => ( + written, + ClientConnectorState::ConnectionFinalization { connection_activation }, + ), + _ => return Err(general_err!("invalid state (this is a bug)")), } - - let desktop_size = capability_sets - .iter() - .find_map(|c| match c { - rdp::capability_sets::CapabilitySet::Bitmap(b) => Some(DesktopSize { - width: b.desktop_width, - height: b.desktop_height, - }), - _ => None, - }) - .unwrap_or(DesktopSize { - width: self.config.desktop_size.width, - height: self.config.desktop_size.height, - }); - - let client_confirm_active = rdp::headers::ShareControlPdu::ClientConfirmActive( - create_client_confirm_active(&self.config, capability_sets), - ); - - debug!(message = ?client_confirm_active, "Send"); - - let written = legacy::encode_share_control( - user_channel_id, - io_channel_id, - share_control_ctx.share_id, - client_confirm_active, - output, - )?; - - ( - Written::from_size(written)?, - ClientConnectorState::ConnectionFinalization { - io_channel_id, - user_channel_id, - desktop_size, - connection_finalization: ConnectionFinalizationSequence::new(io_channel_id, user_channel_id), - }, - ) } //== Connection Finalization ==// // Client and server exchange a few PDUs in order to finalize the connection. // Client may send PDUs one after the other without waiting for a response in order to speed up the process. ClientConnectorState::ConnectionFinalization { - io_channel_id, - user_channel_id, - desktop_size, - mut connection_finalization, + mut connection_activation, } => { - debug!("Connection Finalization"); - - let written = connection_finalization.step(input, output)?; + let written = connection_activation.step(input, output)?; - let next_state = if connection_finalization.state.is_terminal() { - ClientConnectorState::Connected { - result: ConnectionResult { + let next_state = if !connection_activation.state.is_terminal() { + ClientConnectorState::ConnectionFinalization { connection_activation } + } else { + match connection_activation.state { + ConnectionActivationState::Finalized { io_channel_id, user_channel_id, - static_channels: mem::take(&mut self.static_channels), desktop_size, - graphics_config: self.config.graphics.clone(), - no_server_pointer: self.config.no_server_pointer, - pointer_software_rendering: self.config.pointer_software_rendering, + } => ClientConnectorState::Connected { + result: ConnectionResult { + io_channel_id, + user_channel_id, + static_channels: mem::take(&mut self.static_channels), + desktop_size, + graphics_config: self.config.graphics.clone(), + no_server_pointer: self.config.no_server_pointer, + pointer_software_rendering: self.config.pointer_software_rendering, + }, }, - } - } else { - ClientConnectorState::ConnectionFinalization { - io_channel_id, - user_channel_id, - desktop_size, - connection_finalization, + _ => return Err(general_err!("invalid state (this is a bug)")), } }; @@ -826,131 +761,3 @@ fn create_client_info_pdu(config: &Config, routing_addr: &SocketAddr) -> rdp::Cl client_info, } } - -fn create_client_confirm_active( - config: &Config, - mut server_capability_sets: Vec, -) -> rdp::capability_sets::ClientConfirmActive { - use ironrdp_pdu::rdp::capability_sets::*; - - server_capability_sets.retain(|capability_set| matches!(capability_set, CapabilitySet::MultiFragmentUpdate(_))); - - let lossy_bitmap_compression = config - .bitmap - .as_ref() - .map(|bitmap| bitmap.lossy_compression) - .unwrap_or(false); - - let drawing_flags = if lossy_bitmap_compression { - BitmapDrawingFlags::ALLOW_SKIP_ALPHA - | BitmapDrawingFlags::ALLOW_DYNAMIC_COLOR_FIDELITY - | BitmapDrawingFlags::ALLOW_COLOR_SUBSAMPLING - } else { - BitmapDrawingFlags::ALLOW_SKIP_ALPHA - }; - - server_capability_sets.extend_from_slice(&[ - CapabilitySet::General(General { - major_platform_type: config.platform, - extra_flags: GeneralExtraFlags::FASTPATH_OUTPUT_SUPPORTED | GeneralExtraFlags::NO_BITMAP_COMPRESSION_HDR, - ..Default::default() - }), - CapabilitySet::Bitmap(Bitmap { - pref_bits_per_pix: 32, - desktop_width: config.desktop_size.width, - desktop_height: config.desktop_size.height, - desktop_resize_flag: false, - drawing_flags, - }), - CapabilitySet::Order(Order::new( - OrderFlags::NEGOTIATE_ORDER_SUPPORT | OrderFlags::ZERO_BOUNDS_DELTAS_SUPPORT, - OrderSupportExFlags::empty(), - 0, - 0, - )), - CapabilitySet::BitmapCache(BitmapCache { - caches: [CacheEntry { - entries: 0, - max_cell_size: 0, - }; BITMAP_CACHE_ENTRIES_NUM], - }), - CapabilitySet::Input(Input { - input_flags: InputFlags::all(), - keyboard_layout: 0, - keyboard_type: Some(config.keyboard_type), - keyboard_subtype: config.keyboard_subtype, - keyboard_function_key: config.keyboard_functional_keys_count, - keyboard_ime_filename: config.ime_file_name.clone(), - }), - CapabilitySet::Pointer(Pointer { - // Pointer cache should be set to non-zero value to enable client-side pointer rendering. - color_pointer_cache_size: DEFAULT_POINTER_CACHE_SIZE, - pointer_cache_size: DEFAULT_POINTER_CACHE_SIZE, - }), - CapabilitySet::Brush(Brush { - support_level: SupportLevel::Default, - }), - CapabilitySet::GlyphCache(GlyphCache { - glyph_cache: [CacheDefinition { - entries: 0, - max_cell_size: 0, - }; GLYPH_CACHE_NUM], - frag_cache: CacheDefinition { - entries: 0, - max_cell_size: 0, - }, - glyph_support_level: GlyphSupportLevel::None, - }), - CapabilitySet::OffscreenBitmapCache(OffscreenBitmapCache { - is_supported: false, - cache_size: 0, - cache_entries: 0, - }), - CapabilitySet::VirtualChannel(VirtualChannel { - flags: VirtualChannelFlags::NO_COMPRESSION, - chunk_size: Some(0), // ignored - }), - CapabilitySet::Sound(Sound { - flags: SoundFlags::empty(), - }), - CapabilitySet::LargePointer(LargePointer { - // Setting `LargePointerSupportFlags::UP_TO_384X384_PIXELS` allows server to send - // `TS_FP_LARGEPOINTERATTRIBUTE` update messages, which are required for client-side - // rendering of pointers bigger than 96x96 pixels. - flags: LargePointerSupportFlags::UP_TO_384X384_PIXELS, - }), - CapabilitySet::SurfaceCommands(SurfaceCommands { - flags: CmdFlags::SET_SURFACE_BITS | CmdFlags::STREAM_SURFACE_BITS | CmdFlags::FRAME_MARKER, - }), - CapabilitySet::BitmapCodecs(BitmapCodecs(vec![Codec { - id: 0x03, // RemoteFX - property: CodecProperty::RemoteFx(RemoteFxContainer::ClientContainer(RfxClientCapsContainer { - capture_flags: CaptureFlags::empty(), - caps_data: RfxCaps(RfxCapset(vec![RfxICap { - flags: RfxICapFlags::empty(), - entropy_bits: EntropyBits::Rlgr3, - }])), - })), - }])), - CapabilitySet::FrameAcknowledge(FrameAcknowledge { - max_unacknowledged_frame_count: 2, - }), - ]); - - if !server_capability_sets - .iter() - .any(|c| matches!(&c, CapabilitySet::MultiFragmentUpdate(_))) - { - server_capability_sets.push(CapabilitySet::MultiFragmentUpdate(MultifragmentUpdate { - max_request_size: 1024, - })); - } - - ClientConfirmActive { - originator_id: SERVER_CHANNEL_ID, - pdu: DemandActive { - source_descriptor: "IRONRDP".to_owned(), - capability_sets: server_capability_sets, - }, - } -} diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs new file mode 100644 index 000000000..9332e3a68 --- /dev/null +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -0,0 +1,341 @@ +use std::mem; + +use ironrdp_pdu::rdp::{self, capability_sets::CapabilitySet}; + +use crate::{legacy, Config, ConnectionFinalizationSequence, ConnectorResult, DesktopSize, Sequence, State, Written}; + +/// Represents the Capability Exchange and Connection Finalization phases +/// of the connection sequence (section [1.3.1.1]). +/// +/// This is abstracted into its own struct to allow it to be used for the ordinary +/// RDP connection sequence [`ClientConnector`] that occurs for every RDP connection, +/// as well as the Deactivation-Reactivation Sequence ([1.3.1.3]) that occurs when +/// a [Server Deactivate All PDU] is received. +/// +/// [1.3.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/023f1e69-cfe8-4ee6-9ee0-7e759fb4e4ee +/// [1.3.1.3]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dfc234ce-481a-4674-9a5d-2a7bafb14432 +/// [`ClientConnector`]: crate::ClientConnector +/// [Server Deactivate All PDU]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/8a29971a-df3c-48da-add2-8ed9a05edc89 +#[derive(Debug, Clone)] +pub struct ConnectionActivationSequence { + pub state: ConnectionActivationState, + pub config: Config, +} + +impl ConnectionActivationSequence { + pub fn new(config: Config, io_channel_id: u16, user_channel_id: u16) -> Self { + Self { + state: ConnectionActivationState::CapabilitiesExchange { + io_channel_id, + user_channel_id, + }, + config, + } + } +} + +impl Sequence for ConnectionActivationSequence { + fn next_pdu_hint(&self) -> Option<&dyn ironrdp_pdu::PduHint> { + match &self.state { + ConnectionActivationState::Consumed => None, + ConnectionActivationState::Finalized { .. } => None, + ConnectionActivationState::CapabilitiesExchange { .. } => Some(&ironrdp_pdu::X224_HINT), + ConnectionActivationState::ConnectionFinalization { + connection_finalization, + .. + } => connection_finalization.next_pdu_hint(), + } + } + + fn state(&self) -> &dyn State { + &self.state + } + + fn step(&mut self, input: &[u8], output: &mut ironrdp_pdu::write_buf::WriteBuf) -> ConnectorResult { + let (written, next_state) = match mem::take(&mut self.state) { + // Invalid state + ConnectionActivationState::Consumed => { + return Err(general_err!("connector sequence state is consumed (this is a bug)")) + } + ConnectionActivationState::Finalized { .. } => { + return Err(general_err!("connector sequence state is finalized (this is a bug)")) + } + ConnectionActivationState::CapabilitiesExchange { + io_channel_id, + user_channel_id, + } => { + debug!("Capabilities Exchange"); + + let send_data_indication_ctx = legacy::decode_send_data_indication(input)?; + let share_control_ctx = legacy::decode_share_control(send_data_indication_ctx)?; + + debug!(message = ?share_control_ctx.pdu, "Received"); + + if share_control_ctx.channel_id != io_channel_id { + warn!( + io_channel_id, + share_control_ctx.channel_id, "Unexpected channel ID for received Share Control Pdu" + ); + } + + let capability_sets = if let rdp::headers::ShareControlPdu::ServerDemandActive(server_demand_active) = + share_control_ctx.pdu + { + server_demand_active.pdu.capability_sets + } else { + return Err(general_err!( + "unexpected Share Control Pdu (expected ServerDemandActive)", + )); + }; + + for c in &capability_sets { + if let rdp::capability_sets::CapabilitySet::General(g) = c { + if g.protocol_version != rdp::capability_sets::PROTOCOL_VER { + warn!(version = g.protocol_version, "Unexpected protocol version"); + } + break; + } + } + + let desktop_size = capability_sets + .iter() + .find_map(|c| match c { + rdp::capability_sets::CapabilitySet::Bitmap(b) => Some(DesktopSize { + width: b.desktop_width, + height: b.desktop_height, + }), + _ => None, + }) + .unwrap_or(DesktopSize { + width: self.config.desktop_size.width, + height: self.config.desktop_size.height, + }); + + let client_confirm_active = rdp::headers::ShareControlPdu::ClientConfirmActive( + create_client_confirm_active(&self.config, capability_sets), + ); + + debug!(message = ?client_confirm_active, "Send"); + + let written = legacy::encode_share_control( + user_channel_id, + io_channel_id, + share_control_ctx.share_id, + client_confirm_active, + output, + )?; + + ( + Written::from_size(written)?, + ConnectionActivationState::ConnectionFinalization { + io_channel_id, + user_channel_id, + desktop_size, + connection_finalization: ConnectionFinalizationSequence::new(io_channel_id, user_channel_id), + }, + ) + } + ConnectionActivationState::ConnectionFinalization { + io_channel_id, + user_channel_id, + desktop_size, + mut connection_finalization, + } => { + debug!("Connection Finalization"); + + let written = connection_finalization.step(input, output)?; + + let next_state = if !connection_finalization.state.is_terminal() { + ConnectionActivationState::ConnectionFinalization { + io_channel_id, + user_channel_id, + desktop_size, + connection_finalization, + } + } else { + ConnectionActivationState::Finalized { + io_channel_id, + user_channel_id, + desktop_size, + } + }; + + (written, next_state) + } + }; + + self.state = next_state; + + Ok(written) + } +} + +#[derive(Default, Debug, Clone)] +pub enum ConnectionActivationState { + #[default] + Consumed, + CapabilitiesExchange { + io_channel_id: u16, + user_channel_id: u16, + }, + ConnectionFinalization { + io_channel_id: u16, + user_channel_id: u16, + desktop_size: DesktopSize, + connection_finalization: ConnectionFinalizationSequence, + }, + Finalized { + io_channel_id: u16, + user_channel_id: u16, + desktop_size: DesktopSize, + }, +} + +impl State for ConnectionActivationState { + fn name(&self) -> &'static str { + match self { + ConnectionActivationState::Consumed => "Consumed", + ConnectionActivationState::CapabilitiesExchange { .. } => "CapabilitiesExchange", + ConnectionActivationState::ConnectionFinalization { .. } => "ConnectionFinalization", + ConnectionActivationState::Finalized { .. } => "Finalized", + } + } + + fn is_terminal(&self) -> bool { + matches!(self, ConnectionActivationState::Finalized { .. }) + } + + fn as_any(&self) -> &dyn core::any::Any { + self + } +} + +const DEFAULT_POINTER_CACHE_SIZE: u16 = 32; + +fn create_client_confirm_active( + config: &Config, + mut server_capability_sets: Vec, +) -> rdp::capability_sets::ClientConfirmActive { + use ironrdp_pdu::rdp::capability_sets::*; + + server_capability_sets.retain(|capability_set| matches!(capability_set, CapabilitySet::MultiFragmentUpdate(_))); + + let lossy_bitmap_compression = config + .bitmap + .as_ref() + .map(|bitmap| bitmap.lossy_compression) + .unwrap_or(false); + + let drawing_flags = if lossy_bitmap_compression { + BitmapDrawingFlags::ALLOW_SKIP_ALPHA + | BitmapDrawingFlags::ALLOW_DYNAMIC_COLOR_FIDELITY + | BitmapDrawingFlags::ALLOW_COLOR_SUBSAMPLING + } else { + BitmapDrawingFlags::ALLOW_SKIP_ALPHA + }; + + server_capability_sets.extend_from_slice(&[ + CapabilitySet::General(General { + major_platform_type: config.platform, + extra_flags: GeneralExtraFlags::FASTPATH_OUTPUT_SUPPORTED | GeneralExtraFlags::NO_BITMAP_COMPRESSION_HDR, + ..Default::default() + }), + CapabilitySet::Bitmap(Bitmap { + pref_bits_per_pix: 32, + desktop_width: config.desktop_size.width, + desktop_height: config.desktop_size.height, + desktop_resize_flag: false, + drawing_flags, + }), + CapabilitySet::Order(Order::new( + OrderFlags::NEGOTIATE_ORDER_SUPPORT | OrderFlags::ZERO_BOUNDS_DELTAS_SUPPORT, + OrderSupportExFlags::empty(), + 0, + 0, + )), + CapabilitySet::BitmapCache(BitmapCache { + caches: [CacheEntry { + entries: 0, + max_cell_size: 0, + }; BITMAP_CACHE_ENTRIES_NUM], + }), + CapabilitySet::Input(Input { + input_flags: InputFlags::all(), + keyboard_layout: 0, + keyboard_type: Some(config.keyboard_type), + keyboard_subtype: config.keyboard_subtype, + keyboard_function_key: config.keyboard_functional_keys_count, + keyboard_ime_filename: config.ime_file_name.clone(), + }), + CapabilitySet::Pointer(Pointer { + // Pointer cache should be set to non-zero value to enable client-side pointer rendering. + color_pointer_cache_size: DEFAULT_POINTER_CACHE_SIZE, + pointer_cache_size: DEFAULT_POINTER_CACHE_SIZE, + }), + CapabilitySet::Brush(Brush { + support_level: SupportLevel::Default, + }), + CapabilitySet::GlyphCache(GlyphCache { + glyph_cache: [CacheDefinition { + entries: 0, + max_cell_size: 0, + }; GLYPH_CACHE_NUM], + frag_cache: CacheDefinition { + entries: 0, + max_cell_size: 0, + }, + glyph_support_level: GlyphSupportLevel::None, + }), + CapabilitySet::OffscreenBitmapCache(OffscreenBitmapCache { + is_supported: false, + cache_size: 0, + cache_entries: 0, + }), + CapabilitySet::VirtualChannel(VirtualChannel { + flags: VirtualChannelFlags::NO_COMPRESSION, + chunk_size: Some(0), // ignored + }), + CapabilitySet::Sound(Sound { + flags: SoundFlags::empty(), + }), + CapabilitySet::LargePointer(LargePointer { + // Setting `LargePointerSupportFlags::UP_TO_384X384_PIXELS` allows server to send + // `TS_FP_LARGEPOINTERATTRIBUTE` update messages, which are required for client-side + // rendering of pointers bigger than 96x96 pixels. + flags: LargePointerSupportFlags::UP_TO_384X384_PIXELS, + }), + CapabilitySet::SurfaceCommands(SurfaceCommands { + flags: CmdFlags::SET_SURFACE_BITS | CmdFlags::STREAM_SURFACE_BITS | CmdFlags::FRAME_MARKER, + }), + CapabilitySet::BitmapCodecs(BitmapCodecs(vec![Codec { + id: 0x03, // RemoteFX + property: CodecProperty::RemoteFx(RemoteFxContainer::ClientContainer(RfxClientCapsContainer { + capture_flags: CaptureFlags::empty(), + caps_data: RfxCaps(RfxCapset(vec![RfxICap { + flags: RfxICapFlags::empty(), + entropy_bits: EntropyBits::Rlgr3, + }])), + })), + }])), + CapabilitySet::FrameAcknowledge(FrameAcknowledge { + max_unacknowledged_frame_count: 2, + }), + ]); + + if !server_capability_sets + .iter() + .any(|c| matches!(&c, CapabilitySet::MultiFragmentUpdate(_))) + { + server_capability_sets.push(CapabilitySet::MultiFragmentUpdate(MultifragmentUpdate { + max_request_size: 1024, + })); + } + + ClientConfirmActive { + originator_id: SERVER_CHANNEL_ID, + pdu: DemandActive { + source_descriptor: "IRONRDP".to_owned(), + capability_sets: server_capability_sets, + }, + } +} diff --git a/crates/ironrdp-connector/src/connection_finalization.rs b/crates/ironrdp-connector/src/connection_finalization.rs index 2136cb13b..2932bb8ba 100644 --- a/crates/ironrdp-connector/src/connection_finalization.rs +++ b/crates/ironrdp-connector/src/connection_finalization.rs @@ -8,7 +8,7 @@ use ironrdp_pdu::PduHint; use crate::{legacy, ConnectorResult, Sequence, State, Written}; -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] #[non_exhaustive] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum ConnectionFinalizationState { @@ -47,7 +47,7 @@ impl State for ConnectionFinalizationState { } } -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct ConnectionFinalizationSequence { pub state: ConnectionFinalizationState, diff --git a/crates/ironrdp-connector/src/legacy.rs b/crates/ironrdp-connector/src/legacy.rs index 6c41828af..1b0ff06ca 100644 --- a/crates/ironrdp-connector/src/legacy.rs +++ b/crates/ironrdp-connector/src/legacy.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; +use ironrdp_pdu::rdp::headers::ServerDeactivateAll; use ironrdp_pdu::write_buf::WriteBuf; use ironrdp_pdu::{decode, encode_vec, rdp, PduDecode, PduEncode}; @@ -147,7 +148,7 @@ pub fn decode_share_data(ctx: SendDataIndicationCtx<'_>) -> ConnectorResult) -> ConnectorResult) -> ConnectorResult { + let ctx = decode_share_control(ctx)?; + + match ctx.pdu { + rdp::headers::ShareControlPdu::ServerDeactivateAll(deactivate_all) => { + Ok(IoChannelPdu::DeactivateAll(deactivate_all)) + } + rdp::headers::ShareControlPdu::Data(share_data_header) => { + let share_data_ctx = ShareDataCtx { + initiator_id: ctx.initiator_id, + channel_id: ctx.channel_id, + share_id: ctx.share_id, + pdu_source: ctx.pdu_source, + pdu: share_data_header.share_data_pdu, + }; + + Ok(IoChannelPdu::Data(share_data_ctx)) + } + _ => Err(general_err!( + "received unexpected Share Control Pdu (expected Share Data Header or Server Deactivate All)" + )), + } +} + impl ironrdp_error::legacy::CatchAllKind for crate::ConnectorErrorKind { const CATCH_ALL_VALUE: Self = crate::ConnectorErrorKind::General; } diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index 98e1ad968..ee842e139 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -11,6 +11,7 @@ pub mod legacy; mod channel_connection; mod connection; +pub mod connection_activation; mod connection_finalization; pub mod credssp; mod license_exchange; diff --git a/crates/ironrdp-graphics/Cargo.toml b/crates/ironrdp-graphics/Cargo.toml index 6fe765262..21850254b 100644 --- a/crates/ironrdp-graphics/Cargo.toml +++ b/crates/ironrdp-graphics/Cargo.toml @@ -19,7 +19,7 @@ doctest = false bit_field = "0.10" bitflags.workspace = true bitvec = "1.0" -byteorder = "1.5" +byteorder.workspace = true ironrdp-error.workspace = true ironrdp-pdu = { workspace = true, features = ["std"] } lazy_static = "1.4" diff --git a/crates/ironrdp-pdu/Cargo.toml b/crates/ironrdp-pdu/Cargo.toml index 53b0b1c56..ab38f33b5 100644 --- a/crates/ironrdp-pdu/Cargo.toml +++ b/crates/ironrdp-pdu/Cargo.toml @@ -27,7 +27,7 @@ tap = "1" # TODO: get rid of these dependencies (related code should probably go into another crate) bit_field = "0.10" -byteorder = "1.5" +byteorder.workspace = true der-parser = "8.2" thiserror.workspace = true md5 = { package = "md-5", version = "0.10" } diff --git a/crates/ironrdp-pdu/src/rdp/capability_sets.rs b/crates/ironrdp-pdu/src/rdp/capability_sets.rs index 844c5aac1..699a302b4 100644 --- a/crates/ironrdp-pdu/src/rdp/capability_sets.rs +++ b/crates/ironrdp-pdu/src/rdp/capability_sets.rs @@ -59,6 +59,9 @@ const ORIGINATOR_ID_FIELD_SIZE: usize = 2; const NULL_TERMINATOR: &str = "\0"; +/// [2.2.1.13.1] Server Demand Active PDU +/// +/// [2.2.1.13.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/a07abad1-38bb-4a1a-96c9-253e3d5440df #[derive(Debug, Clone, PartialEq, Eq)] pub struct ServerDemandActive { pub pdu: DemandActive, @@ -100,6 +103,9 @@ impl<'de> PduDecode<'de> for ServerDemandActive { } } +/// [2.2.1.13.2] Client Confirm Active PDU +/// +/// [2.2.1.13.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/4c3c2710-0bf0-4c54-8e69-aff40ffcde66 #[derive(Debug, Clone, PartialEq, Eq)] pub struct ClientConfirmActive { /// According to [MSDN](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/4e9722c3-ad83-43f5-af5a-529f73d88b48), @@ -147,6 +153,9 @@ impl<'de> PduDecode<'de> for ClientConfirmActive { } } +/// 2.2.1.13.1.1 Demand Active PDU Data (TS_DEMAND_ACTIVE_PDU) +/// +/// [2.2.1.13.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/bd612af5-cb54-43a2-9646-438bc3ecf5db #[derive(Debug, Clone, PartialEq, Eq)] pub struct DemandActive { pub source_descriptor: String, diff --git a/crates/ironrdp-pdu/src/rdp/headers.rs b/crates/ironrdp-pdu/src/rdp/headers.rs index 166584085..3e266b69e 100644 --- a/crates/ironrdp-pdu/src/rdp/headers.rs +++ b/crates/ironrdp-pdu/src/rdp/headers.rs @@ -158,6 +158,7 @@ pub enum ShareControlPdu { ServerDemandActive(ServerDemandActive), ClientConfirmActive(ClientConfirmActive), Data(ShareDataHeader), + ServerDeactivateAll(ServerDeactivateAll), } impl ShareControlPdu { @@ -168,6 +169,7 @@ impl ShareControlPdu { ShareControlPdu::ServerDemandActive(_) => "Server Demand Active PDU", ShareControlPdu::ClientConfirmActive(_) => "Client Confirm Active PDU", ShareControlPdu::Data(_) => "Data PDU", + ShareControlPdu::ServerDeactivateAll(_) => "Server Deactivate All PDU", } } @@ -176,6 +178,7 @@ impl ShareControlPdu { ShareControlPdu::ServerDemandActive(_) => ShareControlPduType::DemandActivePdu, ShareControlPdu::ClientConfirmActive(_) => ShareControlPduType::ConfirmActivePdu, ShareControlPdu::Data(_) => ShareControlPduType::DataPdu, + ShareControlPdu::ServerDeactivateAll(_) => ShareControlPduType::DeactivateAllPdu, } } @@ -188,6 +191,9 @@ impl ShareControlPdu { Ok(ShareControlPdu::ClientConfirmActive(ClientConfirmActive::decode(src)?)) } ShareControlPduType::DataPdu => Ok(ShareControlPdu::Data(ShareDataHeader::decode(src)?)), + ShareControlPduType::DeactivateAllPdu => { + Ok(ShareControlPdu::ServerDeactivateAll(ServerDeactivateAll::decode(src)?)) + } _ => Err(invalid_message_err!("share_type", "unexpected share control PDU type")), } } @@ -199,6 +205,7 @@ impl PduEncode for ShareControlPdu { ShareControlPdu::ServerDemandActive(pdu) => pdu.encode(dst), ShareControlPdu::ClientConfirmActive(pdu) => pdu.encode(dst), ShareControlPdu::Data(share_data_header) => share_data_header.encode(dst), + ShareControlPdu::ServerDeactivateAll(deactivate_all) => deactivate_all.encode(dst), } } @@ -211,6 +218,7 @@ impl PduEncode for ShareControlPdu { ShareControlPdu::ServerDemandActive(pdu) => pdu.size(), ShareControlPdu::ClientConfirmActive(pdu) => pdu.size(), ShareControlPdu::Data(share_data_header) => share_data_header.size(), + ShareControlPdu::ServerDeactivateAll(deactivate_all) => deactivate_all.size(), } } } @@ -505,3 +513,35 @@ bitflags! { const FLUSHED = 0x80; } } + +/// 2.2.3.1 Server Deactivate All PDU +/// +/// [2.2.3.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/8a29971a-df3c-48da-add2-8ed9a05edc89 +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ServerDeactivateAll; + +impl PduDecode<'_> for ServerDeactivateAll { + fn decode(src: &mut ReadCursor<'_>) -> PduResult { + let length_source_descriptor = src.read_u16(); + let _ = src.read_slice(length_source_descriptor.into()); + Ok(Self) + } +} + +impl PduEncode for ServerDeactivateAll { + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + // A 16-bit, unsigned integer. The size in bytes of the sourceDescriptor field. + dst.write_u16(1); + // Variable number of bytes. The source descriptor. This field SHOULD be set to 0x00. + dst.write_u8(0); + Ok(()) + } + + fn name(&self) -> &'static str { + "Server Deactivate All" + } + + fn size(&self) -> usize { + 2 /* length_source_descriptor */ + 1 /* source_descriptor */ + } +} diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs index 8f9ddb15d..a0ce362a7 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs @@ -73,6 +73,9 @@ pub enum Orientation { PortraitFlipped = 270, } +/// [2.2.2.2.1] DISPLAYCONTROL_MONITOR_LAYOUT_PDU +/// +/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c #[derive(Debug, Clone, PartialEq, Eq)] pub struct Monitor { pub flags: MonitorFlags, @@ -154,6 +157,9 @@ impl<'de> PduDecode<'de> for Monitor { } } +/// [2.2.2.2] DISPLAYCONTROL_MONITOR_LAYOUT_PDU +/// +/// [2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/22741217-12a0-4fb8-b5a0-df43905aaf06 #[derive(Debug, Clone, PartialEq, Eq)] pub struct MonitorLayoutPdu { pub monitors: Vec, diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index b9b2e5d56..11da1fb5f 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -1,5 +1,6 @@ use std::rc::Rc; +use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::ConnectionResult; use ironrdp_graphics::pointer::DecodedPointer; use ironrdp_pdu::geometry::InclusiveRectangle; @@ -198,6 +199,7 @@ pub enum ActiveStageOutput { PointerPosition { x: u16, y: u16 }, PointerBitmap(Rc), Terminate(GracefulDisconnectReason), + DeactivateAll(ConnectionActivationSequence), } impl TryFrom for ActiveStageOutput { @@ -215,6 +217,7 @@ impl TryFrom for ActiveStageOutput { Ok(Self::Terminate(reason)) } + x224::ProcessorOutput::DeactivateAll(cas) => Ok(Self::DeactivateAll(cas)), } } } diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index e3e5f2c69..ac2709f53 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -4,6 +4,7 @@ mod gfx; use std::cmp; use std::collections::HashMap; +use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::legacy::SendDataIndicationCtx; use ironrdp_connector::GraphicsConfig; use ironrdp_pdu::dvc::FieldType; @@ -29,6 +30,8 @@ pub enum ProcessorOutput { ResponseFrame(Vec), /// A graceful disconnect notification. Client should close the connection upon receiving this. Disconnect(DisconnectReason), + /// Received a [`ironrdp_pdu::rdp::headers::ServerDeactivateAll`] PDU. + DeactivateAll(ConnectionActivationSequence), } pub struct Processor { @@ -123,58 +126,63 @@ impl Processor { fn process_io_channel(&self, data_ctx: SendDataIndicationCtx<'_>) -> SessionResult> { debug_assert_eq!(data_ctx.channel_id, self.io_channel_id); - let ctx = ironrdp_connector::legacy::decode_share_data(data_ctx).map_err(crate::legacy::map_error)?; - - match ctx.pdu { - ShareDataPdu::SaveSessionInfo(session_info) => { - debug!("Got Session Save Info PDU: {session_info:?}"); - 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 graceful_disconnect = error_info_to_graceful_disconnect_reason(&e); - - if let Some(reason) = graceful_disconnect { - debug!("Received server-side graceful disconnect request: {reason}"); - - Ok(vec![ProcessorOutput::Disconnect(reason)]) - } else { - Err(reason_err!("ServerSetErrorInfo", "{}", e.description())) + let io_channel = ironrdp_connector::legacy::decode_io_channel(data_ctx).map_err(crate::legacy::map_error)?; + + match io_channel { + ironrdp_connector::legacy::IoChannelPdu::Data(ctx) => { + match ctx.pdu { + ShareDataPdu::SaveSessionInfo(session_info) => { + debug!("Got Session Save Info PDU: {session_info:?}"); + 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 graceful_disconnect = error_info_to_graceful_disconnect_reason(&e); + + if let Some(reason) = graceful_disconnect { + debug!("Received server-side graceful disconnect request: {reason}"); + + Ok(vec![ProcessorOutput::Disconnect(reason)]) + } else { + Err(reason_err!("ServerSetErrorInfo", "{}", e.description())) + } + } + 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_pdu::encode_vec(&ultimatum).map_err(SessionError::pdu); + + Ok(vec![ + ProcessorOutput::ResponseFrame(encoded_pdu?), + ProcessorOutput::Disconnect(DisconnectReason::UserRequested), + ]) + } + _ => Err(reason_err!( + "IO channel", + "unexpected PDU: expected Session Save Info PDU, got: {:?}", + ctx.pdu.as_short_name() + )), } } - 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_pdu::encode_vec(&ultimatum).map_err(SessionError::pdu); - - Ok(vec![ - ProcessorOutput::ResponseFrame(encoded_pdu?), - ProcessorOutput::Disconnect(DisconnectReason::UserRequested), - ]) - } - _ => Err(reason_err!( - "IO channel", - "unexpected PDU: expected Session Save Info PDU, got: {:?}", - ctx.pdu.as_short_name() - )), + ironrdp_connector::legacy::IoChannelPdu::DeactivateAll(_) => todo!(), } } diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 93943d9a6..b230e2d02 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -603,6 +603,7 @@ impl Session { hotspot_y, })?; } + ActiveStageOutput::DeactivateAll(_) => todo!("DeactivateAll"), ActiveStageOutput::Terminate(reason) => break 'outer reason, } } From bab4181c530d65e57cca5e58f2b5303d483ac6ab Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 11 Mar 2024 12:39:44 -0700 Subject: [PATCH 49/84] Breaks single_connect_step into two separate methods: `single_connect_step_read` and `single_connect_step_write`. Also adds a `ConnectionActivationSequence::reset_clone` method to aid in reusing the sequence for Deactivate All PDU handling. Passes `ConnectionActivationSequence` to `x224::Processor` to hand back upon receiving a Deactivate All PDU. --- crates/ironrdp-async/src/connector.rs | 36 ++++++++++++---- crates/ironrdp-connector/src/connection.rs | 2 + .../src/connection_activation.rs | 42 ++++++++++++++++--- crates/ironrdp-session/src/active_stage.rs | 1 + crates/ironrdp-session/src/x224/mod.rs | 7 +++- 5 files changed, 74 insertions(+), 14 deletions(-) diff --git a/crates/ironrdp-async/src/connector.rs b/crates/ironrdp-async/src/connector.rs index bf5fc48eb..d8d0e8d2f 100644 --- a/crates/ironrdp-async/src/connector.rs +++ b/crates/ironrdp-async/src/connector.rs @@ -2,8 +2,8 @@ use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, Kerbe use ironrdp_connector::sspi::credssp::ClientState; use ironrdp_connector::sspi::generator::GeneratorState; use ironrdp_connector::{ - custom_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, - Sequence as _, ServerName, State as _, + custom_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, Sequence, + ServerName, State as _, Written, }; use ironrdp_pdu::write_buf::WriteBuf; @@ -187,10 +187,23 @@ where S: FramedWrite + FramedRead, { buf.clear(); + let written = single_connect_step_read(framed, connector, buf).await?; + single_connect_step_write(framed, buf, written).await +} + +pub async fn single_connect_step_read( + framed: &mut Framed, + connector: &mut dyn Sequence, + buf: &mut WriteBuf, +) -> ConnectorResult +where + S: FramedRead, +{ + buf.clear(); - let written = if let Some(next_pdu_hint) = connector.next_pdu_hint() { + if let Some(next_pdu_hint) = connector.next_pdu_hint() { debug!( - connector.state = connector.state.name(), + connector.state = connector.state().name(), hint = ?next_pdu_hint, "Wait for PDU" ); @@ -202,11 +215,20 @@ where trace!(length = pdu.len(), "PDU received"); - connector.step(&pdu, buf)? + connector.step(&pdu, buf) } else { - connector.step_no_input(buf)? - }; + connector.step_no_input(buf) + } +} +async fn single_connect_step_write( + framed: &mut Framed, + buf: &mut WriteBuf, + written: Written, +) -> ConnectorResult<()> +where + S: FramedWrite, +{ if let Some(response_len) = written.size() { debug_assert_eq!(buf.filled_len(), response_len); let response = buf.filled(); diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 46f53dd89..77596ee4a 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -25,6 +25,7 @@ pub struct ConnectionResult { pub graphics_config: Option, pub no_server_pointer: bool, pub pointer_software_rendering: bool, + pub connection_activation: ConnectionActivationSequence, } #[derive(Default, Debug)] @@ -560,6 +561,7 @@ impl Sequence for ClientConnector { graphics_config: self.config.graphics.clone(), no_server_pointer: self.config.no_server_pointer, pointer_software_rendering: self.config.pointer_software_rendering, + connection_activation, }, }, _ => return Err(general_err!("invalid state (this is a bug)")), diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs index 9332e3a68..67017f826 100644 --- a/crates/ironrdp-connector/src/connection_activation.rs +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -32,6 +32,38 @@ impl ConnectionActivationSequence { config, } } + + #[must_use] + pub fn reset_clone(&self) -> Self { + self.clone().reset() + } + + fn reset(mut self) -> Self { + match &self.state { + ConnectionActivationState::CapabilitiesExchange { + io_channel_id, + user_channel_id, + } + | ConnectionActivationState::ConnectionFinalization { + io_channel_id, + user_channel_id, + .. + } + | ConnectionActivationState::Finalized { + io_channel_id, + user_channel_id, + .. + } => { + self.state = ConnectionActivationState::CapabilitiesExchange { + io_channel_id: *io_channel_id, + user_channel_id: *user_channel_id, + }; + + self + } + ConnectionActivationState::Consumed => self, + } + } } impl Sequence for ConnectionActivationSequence { @@ -53,12 +85,10 @@ impl Sequence for ConnectionActivationSequence { fn step(&mut self, input: &[u8], output: &mut ironrdp_pdu::write_buf::WriteBuf) -> ConnectorResult { let (written, next_state) = match mem::take(&mut self.state) { - // Invalid state - ConnectionActivationState::Consumed => { - return Err(general_err!("connector sequence state is consumed (this is a bug)")) - } - ConnectionActivationState::Finalized { .. } => { - return Err(general_err!("connector sequence state is finalized (this is a bug)")) + ConnectionActivationState::Consumed | ConnectionActivationState::Finalized { .. } => { + return Err(general_err!( + "connector sequence state is finalized or consumed (this is a bug)" + )); } ConnectionActivationState::CapabilitiesExchange { io_channel_id, diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index 11da1fb5f..d04a579fc 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -29,6 +29,7 @@ impl ActiveStage { connection_result.io_channel_id, connection_result.graphics_config, graphics_handler, + connection_result.connection_activation, ); let fast_path_processor = fast_path::ProcessorBuilder { diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index ac2709f53..acd16d512 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -45,6 +45,7 @@ pub struct Processor { drdynvc_initialized: bool, graphics_config: Option, graphics_handler: Option>, + connection_activation: ConnectionActivationSequence, } impl Processor { @@ -54,6 +55,7 @@ impl Processor { io_channel_id: u16, graphics_config: Option, graphics_handler: Option>, + connection_activation: ConnectionActivationSequence, ) -> Self { let drdynvc_channel_id = static_channels.iter().find_map(|(type_id, channel)| { if channel.is_drdynvc() { @@ -73,6 +75,7 @@ impl Processor { drdynvc_initialized: false, graphics_config, graphics_handler, + connection_activation, } } @@ -182,7 +185,9 @@ impl Processor { )), } } - ironrdp_connector::legacy::IoChannelPdu::DeactivateAll(_) => todo!(), + ironrdp_connector::legacy::IoChannelPdu::DeactivateAll(_) => Ok(vec![ProcessorOutput::DeactivateAll( + self.connection_activation.reset_clone(), + )]), } } From 074f47d790787c79434cbe63c7279d965f764a77 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 11 Mar 2024 18:30:40 -0700 Subject: [PATCH 50/84] Sets desktop_resize_flag to true which is required for the Microsoft::Windows::RDS::DisplayControl DVC to work. --- crates/ironrdp-connector/src/connection_activation.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs index 67017f826..686d91b47 100644 --- a/crates/ironrdp-connector/src/connection_activation.rs +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -274,7 +274,8 @@ fn create_client_confirm_active( pref_bits_per_pix: 32, desktop_width: config.desktop_size.width, desktop_height: config.desktop_size.height, - desktop_resize_flag: false, + // This is required to be true in order for the Microsoft::Windows::RDS::DisplayControl DVC to work. + desktop_resize_flag: true, drawing_flags, }), CapabilitySet::Order(Order::new( From 66ceda2b20ac7f0f3aece513425a48f37f60fe7a Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 11 Mar 2024 21:45:00 -0700 Subject: [PATCH 51/84] Removes the explicit state machine from DecodingContext. State comes to us from the server via the BlockType in the BlockHeader, so it needn't be rigidly tracked explicitly in DecodingContext. This makes the RFX pipeline more flexible, which in turn allows us to seamlessly use it when we get another sync sequence mid-stream due to having fielded a Server Deactivate PDU. --- crates/ironrdp-pdu/src/codecs/rfx.rs | 16 ++++++- .../src/codecs/rfx/data_messages.rs | 16 ++++--- .../src/codecs/rfx/header_messages.rs | 16 ++++--- crates/ironrdp-session/src/rfx.rs | 44 +++++++++---------- crates/ironrdp-session/src/x224/mod.rs | 5 ++- 5 files changed, 62 insertions(+), 35 deletions(-) diff --git a/crates/ironrdp-pdu/src/codecs/rfx.rs b/crates/ironrdp-pdu/src/codecs/rfx.rs index 0d1982f69..634c7634f 100644 --- a/crates/ironrdp-pdu/src/codecs/rfx.rs +++ b/crates/ironrdp-pdu/src/codecs/rfx.rs @@ -76,6 +76,11 @@ pub struct BlockHeader { } impl BlockHeader { + pub fn from_buffer_consume(buffer: &mut &[u8]) -> Result { + let ty = BlockType::from_buffer(buffer)?; + Self::from_buffer_consume_with_type(buffer, ty) + } + fn from_buffer_consume_with_type(buffer: &mut &[u8], ty: BlockType) -> Result { let block_length = buffer.read_u32::()? as usize; @@ -98,8 +103,7 @@ impl BlockHeader { } fn from_buffer_consume_with_expected_type(buffer: &mut &[u8], expected_type: BlockType) -> Result { - let ty = buffer.read_u16::()?; - let ty = BlockType::from_u16(ty).ok_or(RfxError::InvalidBlockType(ty))?; + let ty = BlockType::from_buffer(buffer)?; if ty != expected_type { return Err(RfxError::UnexpectedBlockType { expected: expected_type, @@ -220,6 +224,14 @@ pub enum BlockType { Extension = 0xCCC7, } +impl BlockType { + fn from_buffer(buffer: &mut &[u8]) -> Result { + let ty = buffer.read_u16::()?; + let ty = BlockType::from_u16(ty).ok_or(RfxError::InvalidBlockType(ty))?; + Ok(ty) + } +} + #[derive(Debug, Error)] pub enum RfxError { #[error("IO error")] diff --git a/crates/ironrdp-pdu/src/codecs/rfx/data_messages.rs b/crates/ironrdp-pdu/src/codecs/rfx/data_messages.rs index 02d0f9900..5f378f7eb 100644 --- a/crates/ironrdp-pdu/src/codecs/rfx/data_messages.rs +++ b/crates/ironrdp-pdu/src/codecs/rfx/data_messages.rs @@ -120,11 +120,8 @@ pub struct FrameBeginPdu { pub number_of_regions: i16, } -impl PduBufferParsing<'_> for FrameBeginPdu { - type Error = RfxError; - - fn from_buffer_consume(buffer: &mut &[u8]) -> Result { - let header = BlockHeader::from_buffer_consume_with_expected_type(buffer, BlockType::FrameBegin)?; +impl FrameBeginPdu { + pub fn from_buffer_consume_with_header(buffer: &mut &[u8], header: BlockHeader) -> Result { CodecChannelHeader::from_buffer_consume_with_type(buffer, BlockType::FrameBegin)?; let mut buffer = buffer.split_to(header.data_length); @@ -136,6 +133,15 @@ impl PduBufferParsing<'_> for FrameBeginPdu { number_of_regions, }) } +} + +impl PduBufferParsing<'_> for FrameBeginPdu { + type Error = RfxError; + + fn from_buffer_consume(buffer: &mut &[u8]) -> Result { + let header = BlockHeader::from_buffer_consume_with_expected_type(buffer, BlockType::FrameBegin)?; + Self::from_buffer_consume_with_header(buffer, header) + } fn to_buffer_consume(&self, buffer: &mut &mut [u8]) -> Result<(), Self::Error> { let header = BlockHeader { diff --git a/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs b/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs index 07754c7da..67dc717af 100644 --- a/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs +++ b/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs @@ -16,11 +16,8 @@ const CHANNEL_SIZE: usize = 5; #[derive(Debug, Clone, PartialEq, Eq)] pub struct SyncPdu; -impl PduBufferParsing<'_> for SyncPdu { - type Error = RfxError; - - fn from_buffer_consume(buffer: &mut &[u8]) -> Result { - let header = BlockHeader::from_buffer_consume_with_expected_type(buffer, BlockType::Sync)?; +impl SyncPdu { + pub fn from_buffer_consume_with_header(buffer: &mut &[u8], header: BlockHeader) -> Result { let mut buffer = buffer.split_to(header.data_length); let magic = buffer.read_u32::()?; @@ -34,6 +31,15 @@ impl PduBufferParsing<'_> for SyncPdu { Ok(Self) } } +} + +impl PduBufferParsing<'_> for SyncPdu { + type Error = RfxError; + + fn from_buffer_consume(buffer: &mut &[u8]) -> Result { + let header = BlockHeader::from_buffer_consume_with_expected_type(buffer, BlockType::Sync)?; + Self::from_buffer_consume_with_header(buffer, header) + } fn to_buffer_consume(&self, buffer: &mut &mut [u8]) -> Result<(), Self::Error> { let header = BlockHeader { diff --git a/crates/ironrdp-session/src/rfx.rs b/crates/ironrdp-session/src/rfx.rs index 7ba8efc6a..8d2576bf6 100644 --- a/crates/ironrdp-session/src/rfx.rs +++ b/crates/ironrdp-session/src/rfx.rs @@ -15,7 +15,6 @@ const TILE_SIZE: u16 = 64; pub type FrameId = u32; pub struct DecodingContext { - state: SequenceState, context: rfx::ContextPdu, channels: rfx::ChannelsPdu, decoding_tiles: DecodingTileContext, @@ -24,7 +23,6 @@ pub struct DecodingContext { impl Default for DecodingContext { fn default() -> Self { Self { - state: SequenceState::HeaderMessages, context: rfx::ContextPdu { flags: rfx::OperatingMode::empty(), entropy_algorithm: rfx::EntropyAlgorithm::Rlgr1, @@ -47,20 +45,31 @@ impl DecodingContext { input: &mut &[u8], ) -> SessionResult<(FrameId, InclusiveRectangle)> { loop { - match self.state { - SequenceState::HeaderMessages => { - self.process_headers(input)?; + let block_header = rfx::BlockHeader::from_buffer_consume(input)?; + match block_header.ty { + rfx::BlockType::Sync => { + self.process_sync(input, block_header)?; } - SequenceState::DataMessages => { - return self.process_data_messages(image, destination, input); + rfx::BlockType::FrameBegin => { + return self.process_frame(input, block_header, image, destination); + } + _ => { + return Err(reason_err!( + "rfx::DecodingContext", + "unexpected RFX block type: {:?}", + block_header.ty + )); } } } } - fn process_headers(&mut self, input: &mut &[u8]) -> SessionResult<()> { - let _sync = rfx::SyncPdu::from_buffer_consume(input)?; + fn process_sync(&mut self, input: &mut &[u8], header: rfx::BlockHeader) -> SessionResult<()> { + let _sync = rfx::SyncPdu::from_buffer_consume_with_header(input, header)?; + self.process_headers(input) + } + fn process_headers(&mut self, input: &mut &[u8]) -> SessionResult<()> { let mut context = None; let mut channels = None; @@ -81,24 +90,24 @@ impl DecodingContext { self.context = context; self.channels = channels; - self.state = SequenceState::DataMessages; Ok(()) } #[instrument(skip_all)] - fn process_data_messages( + fn process_frame( &mut self, + input: &mut &[u8], + header: rfx::BlockHeader, image: &mut DecodedImage, destination: &InclusiveRectangle, - input: &mut &[u8], ) -> SessionResult<(FrameId, InclusiveRectangle)> { let channel = self.channels.0.first().unwrap(); let width = channel.width.as_u16(); let height = channel.height.as_u16(); let entropy_algorithm = self.context.entropy_algorithm; - let frame_begin = rfx::FrameBeginPdu::from_buffer_consume(input)?; + let frame_begin = rfx::FrameBeginPdu::from_buffer_consume_with_header(input, header)?; let mut region = rfx::RegionPdu::from_buffer_consume(input)?; let tile_set = rfx::TileSetPdu::from_buffer_consume(input)?; let _frame_end = rfx::FrameEndPdu::from_buffer_consume(input)?; @@ -145,10 +154,6 @@ impl DecodingContext { final_update_rectangle = final_update_rectangle.union(¤t_update_rectangle); } - if self.context.flags.contains(rfx::OperatingMode::IMAGE_MODE) { - self.state = SequenceState::HeaderMessages; - } - Ok((frame_begin.index, final_update_rectangle)) } } @@ -258,8 +263,3 @@ struct TileData<'a> { quants: [Quant; 3], data: [&'a [u8]; 3], } - -enum SequenceState { - HeaderMessages, - DataMessages, -} diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index acd16d512..f95bd0548 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -30,7 +30,10 @@ pub enum ProcessorOutput { ResponseFrame(Vec), /// A graceful disconnect notification. Client should close the connection upon receiving this. Disconnect(DisconnectReason), - /// Received a [`ironrdp_pdu::rdp::headers::ServerDeactivateAll`] PDU. + /// 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(ConnectionActivationSequence), } From 371ac3a36826f3f2c7a4658145135b208ac515b0 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 25 Mar 2024 10:01:10 -0500 Subject: [PATCH 52/84] Adds size checks to ServerDeactivateAll --- crates/ironrdp-pdu/src/rdp/headers.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/ironrdp-pdu/src/rdp/headers.rs b/crates/ironrdp-pdu/src/rdp/headers.rs index 3e266b69e..281bcd3e1 100644 --- a/crates/ironrdp-pdu/src/rdp/headers.rs +++ b/crates/ironrdp-pdu/src/rdp/headers.rs @@ -518,18 +518,24 @@ bitflags! { /// /// [2.2.3.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/8a29971a-df3c-48da-add2-8ed9a05edc89 #[derive(Debug, Clone, PartialEq, Eq)] -pub struct ServerDeactivateAll; +pub struct ServerDeactivateAll {} + +impl ServerDeactivateAll { + const FIXED_PART_SIZE: usize = 2 /* length_source_descriptor */ + 1 /* source_descriptor */; +} impl PduDecode<'_> for ServerDeactivateAll { fn decode(src: &mut ReadCursor<'_>) -> PduResult { + ensure_fixed_part_size!(in: src); let length_source_descriptor = src.read_u16(); let _ = src.read_slice(length_source_descriptor.into()); - Ok(Self) + Ok(Self {}) } } impl PduEncode for ServerDeactivateAll { fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_fixed_part_size!(in: dst); // A 16-bit, unsigned integer. The size in bytes of the sourceDescriptor field. dst.write_u16(1); // Variable number of bytes. The source descriptor. This field SHOULD be set to 0x00. @@ -542,6 +548,6 @@ impl PduEncode for ServerDeactivateAll { } fn size(&self) -> usize { - 2 /* length_source_descriptor */ + 1 /* source_descriptor */ + Self::FIXED_PART_SIZE } } From db346b3c2aaf2be522b55d67a595f88c62cc5b29 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 25 Mar 2024 11:46:24 -0500 Subject: [PATCH 53/84] Renames *connect_step* to *sequence_step* and moves this family to the framed module. These should be able to be used in both ironrdp-async/src/connector.rs and ironrdp-acceptor/src/lib.rs (however the latter has not been manually tested as of this commit). --- crates/ironrdp-acceptor/src/lib.rs | 50 ++---------------- crates/ironrdp-async/src/connector.rs | 74 ++------------------------- crates/ironrdp-async/src/framed.rs | 67 +++++++++++++++++++++++- 3 files changed, 75 insertions(+), 116 deletions(-) diff --git a/crates/ironrdp-acceptor/src/lib.rs b/crates/ironrdp-acceptor/src/lib.rs index 470a46cc8..6faba1152 100644 --- a/crates/ironrdp-acceptor/src/lib.rs +++ b/crates/ironrdp-acceptor/src/lib.rs @@ -1,8 +1,8 @@ #[macro_use] extern crate tracing; -use ironrdp_async::{Framed, FramedRead, FramedWrite, StreamWrapper}; -use ironrdp_connector::{custom_err, ConnectorResult, Sequence, Written}; +use ironrdp_async::{single_sequence_step, Framed, FramedRead, FramedWrite, StreamWrapper}; +use ironrdp_connector::ConnectorResult; use ironrdp_pdu::write_buf::WriteBuf; mod channel_connection; @@ -41,7 +41,7 @@ where return Ok(result); } - single_accept_state(&mut framed, acceptor, &mut buf).await?; + single_sequence_step(&mut framed, acceptor, &mut buf).await?; } } @@ -59,48 +59,6 @@ where return Ok((framed, result)); } - single_accept_state(&mut framed, acceptor, &mut buf).await?; + single_sequence_step(&mut framed, acceptor, &mut buf).await?; } } - -async fn single_accept_state( - framed: &mut Framed, - acceptor: &mut Acceptor, - buf: &mut WriteBuf, -) -> ConnectorResult -where - S: FramedRead + FramedWrite, -{ - buf.clear(); - - let written = if let Some(next_pdu_hint) = acceptor.next_pdu_hint() { - debug!( - acceptor.state = acceptor.state().name(), - hint = ?next_pdu_hint, - "Wait for PDU" - ); - - let pdu = framed - .read_by_hint(next_pdu_hint) - .await - .map_err(|e| custom_err!("read frame by hint", e))?; - - trace!(length = pdu.len(), "PDU received"); - - acceptor.step(&pdu, buf)? - } else { - acceptor.step_no_input(buf)? - }; - - if let Some(response_len) = written.size() { - debug_assert_eq!(buf.filled_len(), response_len); - let response = buf.filled(); - trace!(response_len, "Send response"); - framed - .write_all(response) - .await - .map_err(|e| custom_err!("write all", e))?; - } - - Ok(written) -} diff --git a/crates/ironrdp-async/src/connector.rs b/crates/ironrdp-async/src/connector.rs index d8d0e8d2f..e2725ef0e 100644 --- a/crates/ironrdp-async/src/connector.rs +++ b/crates/ironrdp-async/src/connector.rs @@ -2,13 +2,13 @@ use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, Kerbe use ironrdp_connector::sspi::credssp::ClientState; use ironrdp_connector::sspi::generator::GeneratorState; use ironrdp_connector::{ - custom_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, Sequence, - ServerName, State as _, Written, + custom_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, ServerName, + State as _, }; use ironrdp_pdu::write_buf::WriteBuf; use crate::framed::{Framed, FramedRead, FramedWrite}; -use crate::AsyncNetworkClient; +use crate::{single_sequence_step, AsyncNetworkClient}; #[non_exhaustive] pub struct ShouldUpgrade; @@ -23,7 +23,7 @@ where info!("Begin connection procedure"); while !connector.should_perform_security_upgrade() { - single_connect_step(framed, connector, &mut buf).await?; + single_sequence_step(framed, connector, &mut buf).await?; } Ok(ShouldUpgrade) @@ -73,7 +73,7 @@ where } let result = loop { - single_connect_step(framed, &mut connector, &mut buf).await?; + single_sequence_step(framed, &mut connector, &mut buf).await?; if let ClientConnectorState::Connected { result } = connector.state { break result; @@ -177,67 +177,3 @@ where Ok(()) } - -pub async fn single_connect_step( - framed: &mut Framed, - connector: &mut ClientConnector, - buf: &mut WriteBuf, -) -> ConnectorResult<()> -where - S: FramedWrite + FramedRead, -{ - buf.clear(); - let written = single_connect_step_read(framed, connector, buf).await?; - single_connect_step_write(framed, buf, written).await -} - -pub async fn single_connect_step_read( - framed: &mut Framed, - connector: &mut dyn Sequence, - buf: &mut WriteBuf, -) -> ConnectorResult -where - S: FramedRead, -{ - buf.clear(); - - if let Some(next_pdu_hint) = connector.next_pdu_hint() { - debug!( - connector.state = connector.state().name(), - hint = ?next_pdu_hint, - "Wait for PDU" - ); - - let pdu = framed - .read_by_hint(next_pdu_hint) - .await - .map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?; - - trace!(length = pdu.len(), "PDU received"); - - connector.step(&pdu, buf) - } else { - connector.step_no_input(buf) - } -} - -async fn single_connect_step_write( - framed: &mut Framed, - buf: &mut WriteBuf, - written: Written, -) -> ConnectorResult<()> -where - S: FramedWrite, -{ - if let Some(response_len) = written.size() { - debug_assert_eq!(buf.filled_len(), response_len); - let response = buf.filled(); - trace!(response_len, "Send response"); - framed - .write_all(response) - .await - .map_err(|e| ironrdp_connector::custom_err!("write all", e))?; - } - - Ok(()) -} diff --git a/crates/ironrdp-async/src/framed.rs b/crates/ironrdp-async/src/framed.rs index 0c37ddbeb..e142a881f 100644 --- a/crates/ironrdp-async/src/framed.rs +++ b/crates/ironrdp-async/src/framed.rs @@ -1,7 +1,8 @@ use std::io; use bytes::{Bytes, BytesMut}; -use ironrdp_pdu::PduHint; +use ironrdp_connector::{ConnectorResult, Sequence, Written}; +use ironrdp_pdu::{write_buf::WriteBuf, PduHint}; // TODO: investigate if we could use static async fn / return position impl trait in traits when stabilized: // https://github.com/rust-lang/rust/issues/91611 @@ -213,3 +214,67 @@ where self.stream.write_all(buf).await } } + +pub async fn single_sequence_step( + framed: &mut Framed, + sequence: &mut dyn Sequence, + buf: &mut WriteBuf, +) -> ConnectorResult<()> +where + S: FramedWrite + FramedRead, +{ + buf.clear(); + let written = single_sequence_step_read(framed, sequence, buf).await?; + single_sequence_step_write(framed, buf, written).await +} + +pub async fn single_sequence_step_read( + framed: &mut Framed, + sequence: &mut dyn Sequence, + buf: &mut WriteBuf, +) -> ConnectorResult +where + S: FramedRead, +{ + buf.clear(); + + if let Some(next_pdu_hint) = sequence.next_pdu_hint() { + debug!( + connector.state = sequence.state().name(), + hint = ?next_pdu_hint, + "Wait for PDU" + ); + + let pdu = framed + .read_by_hint(next_pdu_hint) + .await + .map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?; + + trace!(length = pdu.len(), "PDU received"); + + sequence.step(&pdu, buf) + } else { + sequence.step_no_input(buf) + } +} + +async fn single_sequence_step_write( + framed: &mut Framed, + buf: &mut WriteBuf, + written: Written, +) -> ConnectorResult<()> +where + S: FramedWrite, +{ + if let Some(response_len) = written.size() { + debug_assert_eq!(buf.filled_len(), response_len); + let response = buf.filled(); + trace!(response_len, "Send response"); + framed + .write_all(response) + .await + .map_err(|e| ironrdp_connector::custom_err!("write all", e))?; + } + + Ok(()) +} From 3cd8167729697ab85a9674d347829e010bd4c566 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 25 Mar 2024 11:51:39 -0500 Subject: [PATCH 54/84] Adds missing size check --- crates/ironrdp-pdu/src/rdp/headers.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ironrdp-pdu/src/rdp/headers.rs b/crates/ironrdp-pdu/src/rdp/headers.rs index 281bcd3e1..4493a8b24 100644 --- a/crates/ironrdp-pdu/src/rdp/headers.rs +++ b/crates/ironrdp-pdu/src/rdp/headers.rs @@ -528,6 +528,7 @@ impl PduDecode<'_> for ServerDeactivateAll { fn decode(src: &mut ReadCursor<'_>) -> PduResult { ensure_fixed_part_size!(in: src); let length_source_descriptor = src.read_u16(); + ensure_size!(in: src, size: length_source_descriptor.into()); let _ = src.read_slice(length_source_descriptor.into()); Ok(Self {}) } From 1de16664c6c7e90b0f9c5d9c3efc716f9bdbd631 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 25 Mar 2024 11:52:39 -0500 Subject: [PATCH 55/84] Fixes ServerDeactivateAll style --- crates/ironrdp-pdu/src/rdp/headers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ironrdp-pdu/src/rdp/headers.rs b/crates/ironrdp-pdu/src/rdp/headers.rs index 4493a8b24..75a82d484 100644 --- a/crates/ironrdp-pdu/src/rdp/headers.rs +++ b/crates/ironrdp-pdu/src/rdp/headers.rs @@ -518,7 +518,7 @@ bitflags! { /// /// [2.2.3.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/8a29971a-df3c-48da-add2-8ed9a05edc89 #[derive(Debug, Clone, PartialEq, Eq)] -pub struct ServerDeactivateAll {} +pub struct ServerDeactivateAll; impl ServerDeactivateAll { const FIXED_PART_SIZE: usize = 2 /* length_source_descriptor */ + 1 /* source_descriptor */; From 6e51da5fcce827725ddc5505459c758ab6a5ca02 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 25 Mar 2024 14:55:45 -0500 Subject: [PATCH 56/84] Explain unused_assignments at the point of its declaration --- crates/ironrdp-web/src/session.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 72c405f58..6e2e223e9 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -386,6 +386,10 @@ pub struct Session { #[wasm_bindgen] impl Session { + // #[allow(unused_assignments)] is required here in order to silence a false + // positive about an unused assignment below, which is actually used. Search + // for "unused_assignments" in a comment in this function to find the specific + // assignment that is being falsely reported as unused. #[allow(unused_assignments)] pub async fn run(&self) -> Result { let rdp_reader = self From 94c176bf6a9bf0288c5943a607f1f8c6e35fc18c Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 25 Mar 2024 19:58:49 -0500 Subject: [PATCH 57/84] Adds a callback mechanism to the DisplayControlClient which is called when the server sends the DisplayControlCapabilities (indicating that the client is ready for use --- crates/ironrdp-displaycontrol/src/client.rs | 68 +++++++++++++------- crates/ironrdp-displaycontrol/src/pdu/mod.rs | 12 ++++ 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/crates/ironrdp-displaycontrol/src/client.rs b/crates/ironrdp-displaycontrol/src/client.rs index 54a1d6480..92ad7405d 100644 --- a/crates/ironrdp-displaycontrol/src/client.rs +++ b/crates/ironrdp-displaycontrol/src/client.rs @@ -1,14 +1,51 @@ use crate::{ - pdu::{DisplayControlMonitorLayout, DisplayControlPdu, MonitorLayoutEntry}, + pdu::{DisplayControlCapabilities, DisplayControlPdu}, CHANNEL_NAME, }; use ironrdp_dvc::{encode_dvc_messages, DvcClientProcessor, DvcMessage, DvcProcessor}; -use ironrdp_pdu::PduResult; +use ironrdp_pdu::{cursor::ReadCursor, PduDecode, PduResult}; use ironrdp_svc::{impl_as_any, ChannelFlags, SvcMessage}; use tracing::debug; /// A client for the Display Control Virtual Channel. -pub struct DisplayControlClient {} +pub struct DisplayControlClient { + /// A callback that will be called when capabilities are received from the server. + /// If no callback is set, a default (inert) callback will be used. + on_capabilities_received: OnCapabilitiesReceived, +} + +impl Default for DisplayControlClient { + fn default() -> Self { + Self::new() + } +} + +impl DisplayControlClient { + pub fn new() -> Self { + Self { + on_capabilities_received: Box::new(|_| { + debug!("No capabilities received callback set, ignoring."); + Ok(Vec::new()) + }), + } + } + + /// Sets a callback that will be called when capabilities are received from the server. + #[must_use] + pub fn with_capabilities_received_callback(mut self, callback: F) -> Self + where + F: Fn(DisplayControlCapabilities) -> PduResult> + Send + Sync + 'static, + { + self.on_capabilities_received = Box::new(callback); + self + } + + /// Builds a [`DisplayControlPdu::MonitorLayout`] as an [`SvcMessage`] for a monitor with the given dimensions. + pub fn encode_monitor(&self, channel_id: u32, width: u32, height: u32) -> PduResult> { + let pdu = DisplayControlPdu::create_monitor_layout_pdu(width, height)?; + encode_dvc_messages(channel_id, vec![Box::new(pdu)], ChannelFlags::empty()) + } +} impl_as_any!(DisplayControlClient); @@ -22,29 +59,12 @@ impl DvcProcessor for DisplayControlClient { } fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult> { - // TODO: We can parse the payload here for completeness sake, - // in practice we don't need to do anything with the payload. - debug!("Got Display PDU of length: {}", payload.len()); - Ok(Vec::new()) + let caps = DisplayControlCapabilities::decode(&mut ReadCursor::new(payload))?; + debug!("received {:?}", caps); + (self.on_capabilities_received)(caps) } } impl DvcClientProcessor for DisplayControlClient {} -impl DisplayControlClient { - pub fn new() -> Self { - Self {} - } - - /// Fully encodes a [`MonitorLayoutPdu`] with the given monitors. - pub fn encode_monitors(&self, channel_id: u32, monitors: Vec) -> PduResult> { - let pdu: DisplayControlPdu = DisplayControlMonitorLayout::new(&monitors)?.into(); - encode_dvc_messages(channel_id, vec![Box::new(pdu)], ChannelFlags::empty()) - } -} - -impl Default for DisplayControlClient { - fn default() -> Self { - Self::new() - } -} +type OnCapabilitiesReceived = Box PduResult> + Send + Sync>; diff --git a/crates/ironrdp-displaycontrol/src/pdu/mod.rs b/crates/ironrdp-displaycontrol/src/pdu/mod.rs index 5c460c831..165990818 100644 --- a/crates/ironrdp-displaycontrol/src/pdu/mod.rs +++ b/crates/ironrdp-displaycontrol/src/pdu/mod.rs @@ -30,6 +30,18 @@ pub enum DisplayControlPdu { impl DisplayControlPdu { const NAME: &'static str = "DISPLAYCONTROL_HEADER"; const FIXED_PART_SIZE: usize = 4 /* Type */ + 4 /* Length */; + + /// Creates a new [`DisplayControlPdu::MonitorLayout`] PDU for a single monitor with the given width and height. + pub fn create_monitor_layout_pdu(width: u32, height: u32) -> PduResult { + let monitors = vec![ + MonitorLayoutEntry::new_primary(width, height)?.with_orientation(if width > height { + MonitorOrientation::Landscape + } else { + MonitorOrientation::Portrait + }), + ]; + Ok(DisplayControlMonitorLayout::new(&monitors).unwrap().into()) + } } impl PduEncode for DisplayControlPdu { From 6837e94194f3c7f3c2bb2ce69afca2158598ea22 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 26 Mar 2024 12:32:38 -0500 Subject: [PATCH 58/84] Automatically adjust the width of a new MonitorLayoutEntry so that it is even --- crates/ironrdp-displaycontrol/src/pdu/mod.rs | 16 +++++++++++++++- .../tests/displaycontrol/mod.rs | 11 +++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/crates/ironrdp-displaycontrol/src/pdu/mod.rs b/crates/ironrdp-displaycontrol/src/pdu/mod.rs index 165990818..cd55ffe72 100644 --- a/crates/ironrdp-displaycontrol/src/pdu/mod.rs +++ b/crates/ironrdp-displaycontrol/src/pdu/mod.rs @@ -5,6 +5,7 @@ use ironrdp_dvc::DvcPduEncode; use ironrdp_pdu::cursor::{ReadCursor, WriteCursor}; use ironrdp_pdu::{ensure_fixed_part_size, invalid_message_err, PduDecode, PduEncode, PduResult}; +use tracing::warn; const DISPLAYCONTROL_PDU_TYPE_CAPS: u32 = 0x00000005; const DISPLAYCONTROL_PDU_TYPE_MONITOR_LAYOUT: u32 = 0x00000002; @@ -346,7 +347,20 @@ impl MonitorLayoutEntry { const NAME: &'static str = "DISPLAYCONTROL_MONITOR_LAYOUT"; - fn new_impl(width: u32, height: u32) -> PduResult { + /// Creates a new [`MonitorLayoutEntry`]. + /// + /// - `width` and `height` MUST be >= 200 and <= 8192. + /// - `width` SHOULD be even. If it is odd, it will be adjusted + /// to the nearest even number by subtracting 1. + fn new_impl(mut width: u32, height: u32) -> PduResult { + if width % 2 != 0 { + let prev_width = width; + width = width.saturating_sub(1); + warn!( + "Monitor width cannot be odd, adjusting from {} to {}", + prev_width, width + ) + } validate_dimensions(width, height)?; Ok(Self { diff --git a/crates/ironrdp-testsuite-core/tests/displaycontrol/mod.rs b/crates/ironrdp-testsuite-core/tests/displaycontrol/mod.rs index d4b62da00..a003689a2 100644 --- a/crates/ironrdp-testsuite-core/tests/displaycontrol/mod.rs +++ b/crates/ironrdp-testsuite-core/tests/displaycontrol/mod.rs @@ -75,13 +75,20 @@ fn invalid_caps() { .expect_err("resolution more than 8k should not be a valid value"); } +#[test] +fn monitor_layout_entry_odd_dimensions_adjustment() { + let odd_value = 1023; + let entry = pdu::MonitorLayoutEntry::new_primary(odd_value, odd_value).expect("valid entry should be created"); + let (width, height) = entry.dimensions(); + assert_eq!(width, odd_value - 1); + assert_eq!(height, odd_value); +} + #[test] fn invalid_monitor_layout_entry() { pdu::MonitorLayoutEntry::new_primary(32 * 1024, 32 * 1024) .expect_err("resolution more than 8k should not be allowed"); - pdu::MonitorLayoutEntry::new_primary(1023, 1024).expect_err("only width which is power of two is allowed"); - pdu::MonitorLayoutEntry::new_primary(1024, 1024) .unwrap() .with_position(-1, 1) From 10c2415348e0ddfc097d87099c61a33088e908b9 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 26 Mar 2024 13:29:52 -0500 Subject: [PATCH 59/84] Enforces the callback by requiring it in DisplayControlClient::new. Also adds a ready flag to DisplayControlClient --- crates/ironrdp-displaycontrol/src/client.rs | 35 ++++++++++----------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/crates/ironrdp-displaycontrol/src/client.rs b/crates/ironrdp-displaycontrol/src/client.rs index 92ad7405d..1984301e9 100644 --- a/crates/ironrdp-displaycontrol/src/client.rs +++ b/crates/ironrdp-displaycontrol/src/client.rs @@ -12,32 +12,28 @@ pub struct DisplayControlClient { /// A callback that will be called when capabilities are received from the server. /// If no callback is set, a default (inert) callback will be used. on_capabilities_received: OnCapabilitiesReceived, -} - -impl Default for DisplayControlClient { - fn default() -> Self { - Self::new() - } + /// Indicates whether the capabilities have been received from the server. + ready: bool, } impl DisplayControlClient { - pub fn new() -> Self { + /// Creates a new [`DisplayControlClient`] with the given `callback`. + /// + /// The `callback` will be called when capabilities are received from the server. + /// It is important to note that the channel will not be fully operational until the capabilities are received. + /// Attempting to send messages before the capabilities are received will result in an error or a silent failure. + pub fn new(callback: F) -> Self + where + F: Fn(DisplayControlCapabilities) -> PduResult> + Send + Sync + 'static, + { Self { - on_capabilities_received: Box::new(|_| { - debug!("No capabilities received callback set, ignoring."); - Ok(Vec::new()) - }), + on_capabilities_received: Box::new(callback), + ready: false, } } - /// Sets a callback that will be called when capabilities are received from the server. - #[must_use] - pub fn with_capabilities_received_callback(mut self, callback: F) -> Self - where - F: Fn(DisplayControlCapabilities) -> PduResult> + Send + Sync + 'static, - { - self.on_capabilities_received = Box::new(callback); - self + pub fn ready(&self) -> bool { + self.ready } /// Builds a [`DisplayControlPdu::MonitorLayout`] as an [`SvcMessage`] for a monitor with the given dimensions. @@ -61,6 +57,7 @@ impl DvcProcessor for DisplayControlClient { fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult> { let caps = DisplayControlCapabilities::decode(&mut ReadCursor::new(payload))?; debug!("received {:?}", caps); + self.ready = true; (self.on_capabilities_received)(caps) } } From 2359d0030d88755c996f503538860011067e6ff5 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 26 Mar 2024 14:04:56 -0500 Subject: [PATCH 60/84] Mimics the ironrdp-web deactivation-reactivation sequence in ironrdp-client. Note that as of this commit, this functionality has not been tested in the wild and may yet have bugs --- crates/ironrdp-client/src/rdp.rs | 54 +++++++++++++++++++++- crates/ironrdp-session/src/active_stage.rs | 2 +- crates/ironrdp-session/src/x224/mod.rs | 4 +- crates/ironrdp-web/src/session.rs | 7 +-- 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 46f0db754..ad852f1a4 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -1,10 +1,13 @@ use ironrdp::cliprdr::backend::{ClipboardMessage, CliprdrBackendFactory}; +use ironrdp::connector::connection_activation::ConnectionActivationState; use ironrdp::connector::{ConnectionResult, ConnectorResult}; use ironrdp::graphics::image_processing::PixelFormat; use ironrdp::pdu::input::fast_path::FastPathInputEvent; +use ironrdp::pdu::write_buf::WriteBuf; use ironrdp::session::image::DecodedImage; -use ironrdp::session::{ActiveStage, ActiveStageOutput, GracefulDisconnectReason, SessionResult}; +use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDisconnectReason, SessionResult}; use ironrdp::{cliprdr, connector, rdpdr, rdpsnd, session}; +use ironrdp_tokio::single_connect_step_read; use rdpdr::NoopRdpdrBackend; use smallvec::SmallVec; use tokio::net::TcpStream; @@ -280,7 +283,54 @@ async fn active_session( ActiveStageOutput::PointerBitmap(_) => { // Not applicable, because we use the software cursor rendering. } - ActiveStageOutput::DeactivateAll(_) => todo!("DeactivateAll"), + ActiveStageOutput::DeactivateAll(mut connection_activation) => { + // Execute the Deactivation-Reactivation Sequence: + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dfc234ce-481a-4674-9a5d-2a7bafb14432 + debug!("Received Server Deactivate All PDU, executing Deactivation-Reactivation Sequence"); + let mut buf = WriteBuf::new(); + 'activation_seq: loop { + let written = single_connect_step_read(&mut framed, &mut *connection_activation, &mut buf) + .await + .map_err(|e| session::custom_err!("read deactivation-reactivation sequence step", e))?; + + if written.size().is_some() { + framed.write_all(buf.filled()).await.map_err(|e| { + session::custom_err!("write deactivation-reactivation sequence step", e) + })?; + } + + if let ConnectionActivationState::Finalized { + io_channel_id, + user_channel_id, + desktop_size, + graphics_config: _, + no_server_pointer, + pointer_software_rendering, + } = connection_activation.state + { + debug!("Deactivation-Reactivation Sequence completed"); + // Reset the image we decode fastpath frames into with + // potentially updated session size. + // + // Note: the compiler apparently loses track of the control flow here, + // hence the need for #[allow(unused_assignments)] at the top of this + // function. + image = DecodedImage::new(PixelFormat::RgbA32, desktop_size.width, desktop_size.height); + // Create a new [`FastPathProcessor`] with potentially updated + // io/user channel ids. + active_stage.set_fastpath_processor( + fast_path::ProcessorBuilder { + io_channel_id, + user_channel_id, + no_server_pointer, + pointer_software_rendering, + } + .build(), + ); + break 'activation_seq; + } + } + } ActiveStageOutput::Terminate(reason) => break 'outer reason, } } diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index 900b31119..cac360f3f 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -204,7 +204,7 @@ pub enum ActiveStageOutput { PointerPosition { x: u16, y: u16 }, PointerBitmap(Rc), Terminate(GracefulDisconnectReason), - DeactivateAll(ConnectionActivationSequence), + DeactivateAll(Box), } impl TryFrom for ActiveStageOutput { diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index f95bd0548..f649ebd27 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -34,7 +34,7 @@ pub enum ProcessorOutput { /// [Deactivation-Reactivation Sequence]. /// /// [Deactivation-Reactivation Sequence]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dfc234ce-481a-4674-9a5d-2a7bafb14432 - DeactivateAll(ConnectionActivationSequence), + DeactivateAll(Box), } pub struct Processor { @@ -189,7 +189,7 @@ impl Processor { } } ironrdp_connector::legacy::IoChannelPdu::DeactivateAll(_) => Ok(vec![ProcessorOutput::DeactivateAll( - self.connection_activation.reset_clone(), + Box::new(self.connection_activation.reset_clone()), )]), } } diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 6e2e223e9..5d648f110 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -610,14 +610,15 @@ impl Session { hotspot_y, })?; } - ActiveStageOutput::DeactivateAll(mut connection_activation) => { + ActiveStageOutput::DeactivateAll(mut box_connection_activation) => { // Execute the Deactivation-Reactivation Sequence: // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dfc234ce-481a-4674-9a5d-2a7bafb14432 debug!("Received Server Deactivate All PDU, executing Deactivation-Reactivation Sequence"); let mut buf = WriteBuf::new(); 'activation_seq: loop { let written = - single_connect_step_read(&mut framed, &mut connection_activation, &mut buf).await?; + single_connect_step_read(&mut framed, &mut *box_connection_activation, &mut buf) + .await?; if written.size().is_some() { self.writer_tx @@ -632,7 +633,7 @@ impl Session { graphics_config: _, no_server_pointer, pointer_software_rendering, - } = connection_activation.state + } = box_connection_activation.state { debug!("Deactivation-Reactivation Sequence completed"); // Reset the image we decode fastpath frames into with From 3abe20f1c34794ec958d3056e1e0f53a8d11c681 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 27 Mar 2024 07:38:50 -0500 Subject: [PATCH 61/84] Capitalize debug message --- crates/ironrdp-displaycontrol/src/client.rs | 2 +- crates/ironrdp-rdpdr/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ironrdp-displaycontrol/src/client.rs b/crates/ironrdp-displaycontrol/src/client.rs index 1984301e9..2797ae78d 100644 --- a/crates/ironrdp-displaycontrol/src/client.rs +++ b/crates/ironrdp-displaycontrol/src/client.rs @@ -56,7 +56,7 @@ impl DvcProcessor for DisplayControlClient { fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult> { let caps = DisplayControlCapabilities::decode(&mut ReadCursor::new(payload))?; - debug!("received {:?}", caps); + debug!("Received {:?}", caps); self.ready = true; (self.on_capabilities_received)(caps) } diff --git a/crates/ironrdp-rdpdr/src/lib.rs b/crates/ironrdp-rdpdr/src/lib.rs index 0b185a41d..59f8fc83a 100644 --- a/crates/ironrdp-rdpdr/src/lib.rs +++ b/crates/ironrdp-rdpdr/src/lib.rs @@ -190,7 +190,7 @@ impl SvcProcessor for Rdpdr { fn process(&mut self, src: &[u8]) -> PduResult> { let mut src = ReadCursor::new(src); let pdu = decode_cursor::(&mut src)?; - debug!("received {:?}", pdu); + debug!("Received {:?}", pdu); match pdu { RdpdrPdu::VersionAndIdPdu(pdu) if pdu.kind == VersionAndIdPduKind::ServerAnnounceRequest => { From 9921483a5c053d63a680078d36a749523413139c Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 27 Mar 2024 07:40:56 -0500 Subject: [PATCH 62/84] Removes Sync from DvcProcessor and OnCapabilitiesReceived --- crates/ironrdp-displaycontrol/src/client.rs | 4 ++-- crates/ironrdp-dvc/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ironrdp-displaycontrol/src/client.rs b/crates/ironrdp-displaycontrol/src/client.rs index 2797ae78d..263ace644 100644 --- a/crates/ironrdp-displaycontrol/src/client.rs +++ b/crates/ironrdp-displaycontrol/src/client.rs @@ -24,7 +24,7 @@ impl DisplayControlClient { /// Attempting to send messages before the capabilities are received will result in an error or a silent failure. pub fn new(callback: F) -> Self where - F: Fn(DisplayControlCapabilities) -> PduResult> + Send + Sync + 'static, + F: Fn(DisplayControlCapabilities) -> PduResult> + Send + 'static, { Self { on_capabilities_received: Box::new(callback), @@ -64,4 +64,4 @@ impl DvcProcessor for DisplayControlClient { impl DvcClientProcessor for DisplayControlClient {} -type OnCapabilitiesReceived = Box PduResult> + Send + Sync>; +type OnCapabilitiesReceived = Box PduResult> + Send>; diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 4e52bdc30..922c59b00 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -42,7 +42,7 @@ pub type DvcMessage = Box; /// The Dynamic Virtual Channel APIs exist to address limitations of Static Virtual Channels: /// - Limited number of channels /// - Packet reconstruction -pub trait DvcProcessor: AsAny + Send + Sync { +pub trait DvcProcessor: AsAny + Send { /// The name of the channel, e.g. "Microsoft::Windows::RDS::DisplayControl" fn channel_name(&self) -> &str; From 50c649e5f4f993979948fcad5f6b7b436b79ea7a Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 27 Mar 2024 20:58:21 -0500 Subject: [PATCH 63/84] Minor refactors --- crates/ironrdp-displaycontrol/src/client.rs | 15 ++++++++---- crates/ironrdp-displaycontrol/src/pdu/mod.rs | 25 ++++++++++---------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/crates/ironrdp-displaycontrol/src/client.rs b/crates/ironrdp-displaycontrol/src/client.rs index 263ace644..cca27f699 100644 --- a/crates/ironrdp-displaycontrol/src/client.rs +++ b/crates/ironrdp-displaycontrol/src/client.rs @@ -1,5 +1,5 @@ use crate::{ - pdu::{DisplayControlCapabilities, DisplayControlPdu}, + pdu::{DisplayControlCapabilities, DisplayControlMonitorLayout, DisplayControlPdu}, CHANNEL_NAME, }; use ironrdp_dvc::{encode_dvc_messages, DvcClientProcessor, DvcMessage, DvcProcessor}; @@ -10,7 +10,6 @@ use tracing::debug; /// A client for the Display Control Virtual Channel. pub struct DisplayControlClient { /// A callback that will be called when capabilities are received from the server. - /// If no callback is set, a default (inert) callback will be used. on_capabilities_received: OnCapabilitiesReceived, /// Indicates whether the capabilities have been received from the server. ready: bool, @@ -36,9 +35,15 @@ impl DisplayControlClient { self.ready } - /// Builds a [`DisplayControlPdu::MonitorLayout`] as an [`SvcMessage`] for a monitor with the given dimensions. - pub fn encode_monitor(&self, channel_id: u32, width: u32, height: u32) -> PduResult> { - let pdu = DisplayControlPdu::create_monitor_layout_pdu(width, height)?; + /// Builds a [`DisplayControlPdu::MonitorLayout`] with a single primary monitor + /// with the given `width` and `height`, and wraps it as an [`SvcMessage`]. + pub fn encode_single_primary_monitor( + &self, + channel_id: u32, + width: u32, + height: u32, + ) -> PduResult> { + let pdu: DisplayControlPdu = DisplayControlMonitorLayout::new_single_primary_monitor(width, height)?.into(); encode_dvc_messages(channel_id, vec![Box::new(pdu)], ChannelFlags::empty()) } } diff --git a/crates/ironrdp-displaycontrol/src/pdu/mod.rs b/crates/ironrdp-displaycontrol/src/pdu/mod.rs index cd55ffe72..fc394d5fe 100644 --- a/crates/ironrdp-displaycontrol/src/pdu/mod.rs +++ b/crates/ironrdp-displaycontrol/src/pdu/mod.rs @@ -31,18 +31,6 @@ pub enum DisplayControlPdu { impl DisplayControlPdu { const NAME: &'static str = "DISPLAYCONTROL_HEADER"; const FIXED_PART_SIZE: usize = 4 /* Type */ + 4 /* Length */; - - /// Creates a new [`DisplayControlPdu::MonitorLayout`] PDU for a single monitor with the given width and height. - pub fn create_monitor_layout_pdu(width: u32, height: u32) -> PduResult { - let monitors = vec![ - MonitorLayoutEntry::new_primary(width, height)?.with_orientation(if width > height { - MonitorOrientation::Landscape - } else { - MonitorOrientation::Portrait - }), - ]; - Ok(DisplayControlMonitorLayout::new(&monitors).unwrap().into()) - } } impl PduEncode for DisplayControlPdu { @@ -247,6 +235,19 @@ impl DisplayControlMonitorLayout { }) } + /// Creates a new [`DisplayControlMonitorLayout`] with a single primary monitor + /// with the given `width` and `height`. + pub fn new_single_primary_monitor(width: u32, height: u32) -> PduResult { + let monitors = vec![ + MonitorLayoutEntry::new_primary(width, height)?.with_orientation(if width > height { + MonitorOrientation::Landscape + } else { + MonitorOrientation::Portrait + }), + ]; + Ok(DisplayControlMonitorLayout::new(&monitors).unwrap()) + } + pub fn monitors(&self) -> &[MonitorLayoutEntry] { &self.monitors } From 6945c5d4662bae2ff7a9e6fe58a991fa2afeeb17 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Fri, 29 Mar 2024 23:05:26 -0400 Subject: [PATCH 64/84] Adds scale factor and physical dimensions to all monitor layout entries. This unfortunately does not appear to fix the performance issues we're observing after multiple resizes --- crates/ironrdp-displaycontrol/src/client.rs | 12 +++- crates/ironrdp-displaycontrol/src/pdu/mod.rs | 58 ++++++++++++++------ 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/crates/ironrdp-displaycontrol/src/client.rs b/crates/ironrdp-displaycontrol/src/client.rs index cca27f699..548b14cca 100644 --- a/crates/ironrdp-displaycontrol/src/client.rs +++ b/crates/ironrdp-displaycontrol/src/client.rs @@ -42,8 +42,18 @@ impl DisplayControlClient { channel_id: u32, width: u32, height: u32, + scale_factor: u32, + physical_width: u32, + physical_height: u32, ) -> PduResult> { - let pdu: DisplayControlPdu = DisplayControlMonitorLayout::new_single_primary_monitor(width, height)?.into(); + let pdu: DisplayControlPdu = DisplayControlMonitorLayout::new_single_primary_monitor( + width, + height, + scale_factor, + physical_width, + physical_height, + )? + .into(); encode_dvc_messages(channel_id, vec![Box::new(pdu)], ChannelFlags::empty()) } } diff --git a/crates/ironrdp-displaycontrol/src/pdu/mod.rs b/crates/ironrdp-displaycontrol/src/pdu/mod.rs index fc394d5fe..e9f814a0a 100644 --- a/crates/ironrdp-displaycontrol/src/pdu/mod.rs +++ b/crates/ironrdp-displaycontrol/src/pdu/mod.rs @@ -237,14 +237,22 @@ impl DisplayControlMonitorLayout { /// Creates a new [`DisplayControlMonitorLayout`] with a single primary monitor /// with the given `width` and `height`. - pub fn new_single_primary_monitor(width: u32, height: u32) -> PduResult { - let monitors = vec![ - MonitorLayoutEntry::new_primary(width, height)?.with_orientation(if width > height { - MonitorOrientation::Landscape - } else { - MonitorOrientation::Portrait - }), - ]; + pub fn new_single_primary_monitor( + width: u32, + height: u32, + scale_factor: u32, + physical_width: u32, + physical_height: u32, + ) -> PduResult { + let monitors = + vec![ + MonitorLayoutEntry::new_primary(width, height, scale_factor, physical_width, physical_height)? + .with_orientation(if width > height { + MonitorOrientation::Landscape + } else { + MonitorOrientation::Portrait + }), + ]; Ok(DisplayControlMonitorLayout::new(&monitors).unwrap()) } @@ -353,7 +361,13 @@ impl MonitorLayoutEntry { /// - `width` and `height` MUST be >= 200 and <= 8192. /// - `width` SHOULD be even. If it is odd, it will be adjusted /// to the nearest even number by subtracting 1. - fn new_impl(mut width: u32, height: u32) -> PduResult { + fn new_impl( + mut width: u32, + height: u32, + desktop_scale_factor: u32, + physical_width: u32, + physical_height: u32, + ) -> PduResult { if width % 2 != 0 { let prev_width = width; width = width.saturating_sub(1); @@ -370,24 +384,36 @@ impl MonitorLayoutEntry { top: 0, width, height, - physical_width: 0, - physical_height: 0, + physical_width, + physical_height, orientation: 0, - desktop_scale_factor: 100, + desktop_scale_factor, device_scale_factor: 100, }) } /// Creates a new primary monitor layout entry. - pub fn new_primary(width: u32, height: u32) -> PduResult { - let mut entry = Self::new_impl(width, height)?; + pub fn new_primary( + width: u32, + height: u32, + desktop_scale_factor: u32, + physical_width: u32, + physical_height: u32, + ) -> PduResult { + let mut entry = Self::new_impl(width, height, desktop_scale_factor, physical_width, physical_height)?; entry.is_primary = true; Ok(entry) } /// Creates a new secondary monitor layout entry. - pub fn new_secondary(width: u32, height: u32) -> PduResult { - Self::new_impl(width, height) + pub fn new_secondary( + width: u32, + height: u32, + desktop_scale_factor: u32, + physical_width: u32, + physical_height: u32, + ) -> PduResult { + Self::new_impl(width, height, desktop_scale_factor, physical_width, physical_height) } /// Sets the monitor's orientation. (Default is [`MonitorOrientation::Landscape`]) From 622a4f419c130364aaecd96d7b3d95cc2be42a79 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 2 Apr 2024 11:05:40 -0700 Subject: [PATCH 65/84] Fixes merge error where single_connect_step family was included (now changed to single_sequence_step) --- crates/ironrdp-async/src/connector.rs | 68 +----------------------- crates/ironrdp-blocking/src/connector.rs | 6 +-- crates/ironrdp-client/src/rdp.rs | 4 +- crates/ironrdp-web/src/session.rs | 4 +- 4 files changed, 9 insertions(+), 73 deletions(-) diff --git a/crates/ironrdp-async/src/connector.rs b/crates/ironrdp-async/src/connector.rs index 68551cd05..e2725ef0e 100644 --- a/crates/ironrdp-async/src/connector.rs +++ b/crates/ironrdp-async/src/connector.rs @@ -2,8 +2,8 @@ use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, Kerbe use ironrdp_connector::sspi::credssp::ClientState; use ironrdp_connector::sspi::generator::GeneratorState; use ironrdp_connector::{ - custom_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, Sequence, - ServerName, State as _, Written, + custom_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, ServerName, + State as _, }; use ironrdp_pdu::write_buf::WriteBuf; @@ -177,67 +177,3 @@ where Ok(()) } - -pub async fn single_connect_step( - framed: &mut Framed, - connector: &mut ClientConnector, - buf: &mut WriteBuf, -) -> ConnectorResult<()> -where - S: FramedWrite + FramedRead, -{ - buf.clear(); - let written = single_connect_step_read(framed, connector, buf).await?; - single_connect_step_write(framed, buf, written).await -} - -pub async fn single_connect_step_read( - framed: &mut Framed, - connector: &mut dyn Sequence, - buf: &mut WriteBuf, -) -> ConnectorResult -where - S: FramedRead, -{ - buf.clear(); - - if let Some(next_pdu_hint) = connector.next_pdu_hint() { - debug!( - connector.state = connector.state().name(), - hint = ?next_pdu_hint, - "Wait for PDU" - ); - - let pdu = framed - .read_by_hint(next_pdu_hint) - .await - .map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?; - - trace!(length = pdu.len(), "PDU received"); - - connector.step(&pdu, buf) - } else { - connector.step_no_input(buf) - } -} - -async fn single_connect_step_write( - framed: &mut Framed, - buf: &mut WriteBuf, - written: Written, -) -> ConnectorResult<()> -where - S: FramedWrite, -{ - if let Some(response_len) = written.size() { - debug_assert_eq!(buf.filled_len(), response_len); - let response = buf.filled(); - trace!(response_len, "Send response"); - framed - .write_all(response) - .await - .map_err(|e| ironrdp_connector::custom_err!("write all", e))?; - } - - Ok(()) -} diff --git a/crates/ironrdp-blocking/src/connector.rs b/crates/ironrdp-blocking/src/connector.rs index 30300c082..e840cd03b 100644 --- a/crates/ironrdp-blocking/src/connector.rs +++ b/crates/ironrdp-blocking/src/connector.rs @@ -25,7 +25,7 @@ where info!("Begin connection procedure"); while !connector.should_perform_security_upgrade() { - single_connect_step(framed, connector, &mut buf)?; + single_sequence_step(framed, connector, &mut buf)?; } Ok(ShouldUpgrade) @@ -78,7 +78,7 @@ where debug!("Remaining of connection sequence"); let result = loop { - single_connect_step(framed, &mut connector, &mut buf)?; + single_sequence_step(framed, &mut connector, &mut buf)?; if let ClientConnectorState::Connected { result } = connector.state { break result; @@ -173,7 +173,7 @@ where Ok(()) } -pub fn single_connect_step( +pub fn single_sequence_step( framed: &mut Framed, connector: &mut ClientConnector, buf: &mut WriteBuf, diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index d7a56b593..ea5022d43 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -7,7 +7,7 @@ use ironrdp::pdu::write_buf::WriteBuf; use ironrdp::session::image::DecodedImage; use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDisconnectReason, SessionResult}; use ironrdp::{cliprdr, connector, rdpdr, rdpsnd, session}; -use ironrdp_tokio::single_connect_step_read; +use ironrdp_tokio::single_sequence_step_read; use rdpdr::NoopRdpdrBackend; use smallvec::SmallVec; use tokio::net::TcpStream; @@ -289,7 +289,7 @@ async fn active_session( debug!("Received Server Deactivate All PDU, executing Deactivation-Reactivation Sequence"); let mut buf = WriteBuf::new(); 'activation_seq: loop { - let written = single_connect_step_read(&mut framed, &mut *connection_activation, &mut buf) + let written = single_sequence_step_read(&mut framed, &mut *connection_activation, &mut buf) .await .map_err(|e| session::custom_err!("read deactivation-reactivation sequence step", e))?; diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index e67164f12..7852fd789 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -21,7 +21,7 @@ use ironrdp::pdu::rdp::client_info::PerformanceFlags; use ironrdp::pdu::write_buf::WriteBuf; use ironrdp::session::image::DecodedImage; use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDisconnectReason}; -use ironrdp_futures::single_connect_step_read; +use ironrdp_futures::single_sequence_step_read; use rgb::AsPixels as _; use tap::prelude::*; use wasm_bindgen::prelude::*; @@ -618,7 +618,7 @@ impl Session { let mut buf = WriteBuf::new(); 'activation_seq: loop { let written = - single_connect_step_read(&mut framed, &mut *box_connection_activation, &mut buf) + single_sequence_step_read(&mut framed, &mut *box_connection_activation, &mut buf) .await?; if written.size().is_some() { From a888bb1954f03eb505c5844ce0fde8522eadd644 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 2 Apr 2024 11:09:02 -0700 Subject: [PATCH 66/84] remove unnecessary #[allow(unused_assignments)] --- crates/ironrdp-client/src/rdp.rs | 6 ------ crates/ironrdp-web/src/session.rs | 11 ----------- 2 files changed, 17 deletions(-) diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index ea5022d43..4e9c32951 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -308,12 +308,6 @@ async fn active_session( } = connection_activation.state { debug!("Deactivation-Reactivation Sequence completed"); - // Reset the image we decode fastpath frames into with - // potentially updated session size. - // - // Note: the compiler apparently loses track of the control flow here, - // hence the need for #[allow(unused_assignments)] at the top of this - // function. image = DecodedImage::new(PixelFormat::RgbA32, desktop_size.width, desktop_size.height); // Create a new [`FastPathProcessor`] with potentially updated // io/user channel ids. diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 7852fd789..f2f675ea0 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -387,11 +387,6 @@ pub struct Session { #[wasm_bindgen] impl Session { - // #[allow(unused_assignments)] is required here in order to silence a false - // positive about an unused assignment below, which is actually used. Search - // for "unused_assignments" in a comment in this function to find the specific - // assignment that is being falsely reported as unused. - #[allow(unused_assignments)] pub async fn run(&self) -> Result { let rdp_reader = self .rdp_reader @@ -636,12 +631,6 @@ impl Session { } = box_connection_activation.state { debug!("Deactivation-Reactivation Sequence completed"); - // Reset the image we decode fastpath frames into with - // potentially updated session size. - // - // Note: the compiler apparently loses track of the control flow here, - // hence the need for #[allow(unused_assignments)] at the top of this - // function. image = DecodedImage::new(PixelFormat::RgbA32, desktop_size.width, desktop_size.height); // Create a new [`FastPathProcessor`] with potentially updated // io/user channel ids. From 4811deb60c7b55a9c9b6f9335bb27c37d65da470 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 2 Apr 2024 17:27:50 -0700 Subject: [PATCH 67/84] Hooks up resize for `ironrdp-client` crate. This commit adds the ability to resize the RDP session window on the fly using the DisplayControl channel. It includes some known issues: 1. After a resize, session performance degrades. 2. Too large a resize can cause an `InvalidChannelWidth` or `InvalidChannelHeight` error. See: https://www.loom.com/share/7cbfc5b0e02d4aff998939f54816d14b?sid=8e69fec2-aaa7-4a0a-ae67-d277a5000972 --- Cargo.lock | 1 + crates/ironrdp-client/Cargo.toml | 2 +- crates/ironrdp-client/src/rdp.rs | 30 ++++++++++++++----- .../src/codecs/rfx/header_messages.rs | 2 +- crates/ironrdp-session/src/active_stage.rs | 5 ++++ crates/ironrdp/Cargo.toml | 2 ++ crates/ironrdp/src/lib.rs | 2 ++ 7 files changed, 34 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca144e813..e3d060043 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1631,6 +1631,7 @@ dependencies = [ "ironrdp-cliprdr", "ironrdp-cliprdr-native", "ironrdp-connector", + "ironrdp-displaycontrol", "ironrdp-dvc", "ironrdp-graphics", "ironrdp-input", diff --git a/crates/ironrdp-client/Cargo.toml b/crates/ironrdp-client/Cargo.toml index 1ee7e436e..d611a6e70 100644 --- a/crates/ironrdp-client/Cargo.toml +++ b/crates/ironrdp-client/Cargo.toml @@ -28,7 +28,7 @@ native-tls = ["ironrdp-tls/native-tls"] [dependencies] # Protocols -ironrdp = { workspace = true, features = ["input", "graphics", "dvc", "rdpdr", "rdpsnd", "cliprdr"] } +ironrdp = { workspace = true, features = ["input", "graphics", "dvc", "svc", "rdpdr", "rdpsnd", "cliprdr", "displaycontrol"] } ironrdp-cliprdr-native.workspace = true ironrdp-tls.workspace = true ironrdp-tokio.workspace = true diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 4e9c32951..f67e7ac05 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -1,11 +1,14 @@ use ironrdp::cliprdr::backend::{ClipboardMessage, CliprdrBackendFactory}; use ironrdp::connector::connection_activation::ConnectionActivationState; use ironrdp::connector::{ConnectionResult, ConnectorResult}; +use ironrdp::displaycontrol::client::DisplayControlClient; +use ironrdp::dvc::DrdynvcClient; use ironrdp::graphics::image_processing::PixelFormat; use ironrdp::pdu::input::fast_path::FastPathInputEvent; use ironrdp::pdu::write_buf::WriteBuf; use ironrdp::session::image::DecodedImage; use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDisconnectReason, SessionResult}; +use ironrdp::svc::SvcProcessorMessages; use ironrdp::{cliprdr, connector, rdpdr, rdpsnd, session}; use ironrdp_tokio::single_sequence_step_read; use rdpdr::NoopRdpdrBackend; @@ -107,7 +110,9 @@ async fn connect( let mut connector = connector::ClientConnector::new(config.connector.clone()) .with_server_addr(server_addr) - .with_static_channel(ironrdp::dvc::DrdynvcClient::new()) + .with_static_channel( + ironrdp::dvc::DrdynvcClient::new().with_dynamic_channel(DisplayControlClient::new(|_| Ok(Vec::new()))), + ) .with_static_channel(rdpsnd::Rdpsnd::new()) .with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0)); @@ -178,10 +183,6 @@ async fn active_session( match input_event { RdpInputEvent::Resize { mut width, mut height } => { - // TODO(#105): Add support for Display Update Virtual Channel Extension - // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/d2954508-f487-48bc-8731-39743e0854a9 - // One approach when this extension is not available is to perform a connection from scratch again. - // Find the last resize event while let Ok(newer_event) = input_event_receiver.try_recv() { if let RdpInputEvent::Resize { width: newer_width, height: newer_height } = newer_event { @@ -190,11 +191,24 @@ async fn active_session( } } - // TODO(#271): use the "auto-reconnect cookie": https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/15b0d1c9-2891-4adb-a45e-deb4aeeeab7c - info!(width, height, "resize event"); - return Ok(RdpControlFlow::ReconnectWithNewSize { width, height }) + if let Some((display_client, channel_id)) = active_stage.get_dvc_processor::() { + if let Some(channel_id) = channel_id { + let svc_messages = display_client.encode_single_primary_monitor(channel_id, width.into(), height.into()) + .map_err(|e| session::custom_err!("DisplayControl", e))?; + let frame = active_stage.process_svc_processor_messages(SvcProcessorMessages::::new(svc_messages))?; + vec![ActiveStageOutput::ResponseFrame(frame)] + } else { + // TODO: could add a mechanism that withholds the resize event until the channel is created rather than reconnecting + debug!("Display Control Virtual Channel is not yet connected, reconnecting with new size"); + return Ok(RdpControlFlow::ReconnectWithNewSize { width, height }) + } + } else { + // TODO(#271): use the "auto-reconnect cookie": https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/15b0d1c9-2891-4adb-a45e-deb4aeeeab7c + debug!("Display Control Virtual Channel is not available, reconnecting with new size"); + return Ok(RdpControlFlow::ReconnectWithNewSize { width, height }) + } }, RdpInputEvent::FastPath(events) => { trace!(?events); diff --git a/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs b/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs index 67dc717af..4886a57e0 100644 --- a/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs +++ b/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs @@ -196,7 +196,7 @@ impl RfxChannelHeight { (1..=2048) .contains(&value) .then_some(Self(value)) - .ok_or(RfxError::InvalidChannelWidth(value)) + .ok_or(RfxError::InvalidChannelHeight(value)) } pub fn as_u16(self) -> u16 { diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index 881dde6c9..5bcb32447 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -2,6 +2,7 @@ use std::rc::Rc; use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::ConnectionResult; +use ironrdp_dvc::{DvcProcessor, DynamicChannelId}; use ironrdp_graphics::pointer::DecodedPointer; use ironrdp_pdu::geometry::InclusiveRectangle; use ironrdp_pdu::input::fast_path::{FastPathInput, FastPathInputEvent}; @@ -177,6 +178,10 @@ impl ActiveStage { self.x224_processor.get_svc_processor_mut() } + pub fn get_dvc_processor(&mut self) -> Option<(&T, Option)> { + self.x224_processor.get_dvc_processor() + } + /// 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( diff --git a/crates/ironrdp/Cargo.toml b/crates/ironrdp/Cargo.toml index d5af798c7..799f54853 100644 --- a/crates/ironrdp/Cargo.toml +++ b/crates/ironrdp/Cargo.toml @@ -29,6 +29,7 @@ svc = ["dep:ironrdp-svc"] dvc = ["dep:ironrdp-dvc"] rdpdr = ["dep:ironrdp-rdpdr"] rdpsnd = ["dep:ironrdp-rdpsnd"] +displaycontrol = ["dep:ironrdp-displaycontrol"] [dependencies] ironrdp-pdu = { workspace = true, optional = true } @@ -43,6 +44,7 @@ ironrdp-svc = { workspace = true, optional = true } ironrdp-dvc = { workspace = true, optional = true } ironrdp-rdpdr = { workspace = true, optional = true } ironrdp-rdpsnd = { workspace = true, optional = true } +ironrdp-displaycontrol = { workspace = true, optional = true } [dev-dependencies] ironrdp-blocking.workspace = true diff --git a/crates/ironrdp/src/lib.rs b/crates/ironrdp/src/lib.rs index a9d26db0c..6262e297c 100644 --- a/crates/ironrdp/src/lib.rs +++ b/crates/ironrdp/src/lib.rs @@ -8,6 +8,8 @@ pub use ironrdp_acceptor as acceptor; pub use ironrdp_cliprdr as cliprdr; #[cfg(feature = "connector")] pub use ironrdp_connector as connector; +#[cfg(feature = "displaycontrol")] +pub use ironrdp_displaycontrol as displaycontrol; #[cfg(feature = "dvc")] pub use ironrdp_dvc as dvc; #[cfg(feature = "graphics")] From 58049ec412f8abdb78c0e7b2acbc1d34cd14756c Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 4 Apr 2024 11:58:57 -0700 Subject: [PATCH 68/84] Calculates and hooks in scale_factor for resizes --- crates/ironrdp-client/src/gui.rs | 8 +++++++ crates/ironrdp-client/src/rdp.rs | 13 +++++----- crates/ironrdp-connector/src/connection.rs | 4 +++- crates/ironrdp-displaycontrol/src/pdu/mod.rs | 25 +++++++++++++++++--- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/crates/ironrdp-client/src/gui.rs b/crates/ironrdp-client/src/gui.rs index f8e0f5f51..1ce020f4a 100644 --- a/crates/ironrdp-client/src/gui.rs +++ b/crates/ironrdp-client/src/gui.rs @@ -63,9 +63,17 @@ impl GuiContext { match event { Event::WindowEvent { window_id, event } if window_id == window.id() => match event { WindowEvent::Resized(size) => { + let scale_factor = (window.scale_factor() * 100.0) as u32; + // TODO: it should be possible to get the physical size here, however winit doesn't make it straightforward. + // FreeRDP does it based on DPI reading grabbed via [`SDL_GetDisplayDPI`](https://wiki.libsdl.org/SDL2/SDL_GetDisplayDPI): + // https://github.com/FreeRDP/FreeRDP/blob/ba8cf8cf2158018fb7abbedb51ab245f369be813/client/SDL/sdl_monitor.cpp#L250-L262 + let (physical_width, physical_height) = (0, 0); let _ = input_event_sender.send(RdpInputEvent::Resize { width: u16::try_from(size.width).unwrap(), height: u16::try_from(size.height).unwrap(), + scale_factor, + physical_width, + physical_height, }); } WindowEvent::CloseRequested => { diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index f3b3c6672..2bd965acf 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -114,12 +114,13 @@ async fn connect( let mut framed = ironrdp_tokio::TokioFramed::new(stream); - let mut connector = connector::ClientConnector::new(config.connector.clone()).with_server_addr(server_addr); - .with_static_channel( - ironrdp::dvc::DrdynvcClient::new().with_dynamic_channel(DisplayControlClient::new(|_| Ok(Vec::new()))), - ) - .with_static_channel(rdpsnd::Rdpsnd::new()) - .with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0)); + let mut connector = connector::ClientConnector::new(config.connector.clone()) + .with_server_addr(server_addr) + .with_static_channel( + ironrdp::dvc::DrdynvcClient::new().with_dynamic_channel(DisplayControlClient::new(|_| Ok(Vec::new()))), + ) + .with_static_channel(rdpsnd::Rdpsnd::new()) + .with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0)); if let Some(builder) = cliprdr_factory { let backend = builder.build_cliprdr_backend(); diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 99c114158..4bcdd25da 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -700,7 +700,9 @@ fn create_client_info_pdu(config: &Config, routing_addr: &SocketAddr) -> rdp::Cl }; // Default flags for all sessions - let mut flags = ClientInfoFlags::UNICODE + let mut flags = ClientInfoFlags::MOUSE + | ClientInfoFlags::MOUSE_HAS_WHEEL + | ClientInfoFlags::UNICODE | ClientInfoFlags::DISABLE_CTRL_ALT_DEL | ClientInfoFlags::LOGON_NOTIFY | ClientInfoFlags::LOGON_ERRORS diff --git a/crates/ironrdp-displaycontrol/src/pdu/mod.rs b/crates/ironrdp-displaycontrol/src/pdu/mod.rs index e9f814a0a..9fc9c3c1c 100644 --- a/crates/ironrdp-displaycontrol/src/pdu/mod.rs +++ b/crates/ironrdp-displaycontrol/src/pdu/mod.rs @@ -360,11 +360,13 @@ impl MonitorLayoutEntry { /// /// - `width` and `height` MUST be >= 200 and <= 8192. /// - `width` SHOULD be even. If it is odd, it will be adjusted - /// to the nearest even number by subtracting 1. + /// to the nearest even number by subtracting 1. + /// - `desktop_scale_factor` SHOULD be >= 100 and <= 500. If it is not, + /// it will be adjusted to the nearest valid value. fn new_impl( mut width: u32, height: u32, - desktop_scale_factor: u32, + mut desktop_scale_factor: u32, physical_width: u32, physical_height: u32, ) -> PduResult { @@ -372,10 +374,27 @@ impl MonitorLayoutEntry { let prev_width = width; width = width.saturating_sub(1); warn!( - "Monitor width cannot be odd, adjusting from {} to {}", + "Monitor width cannot be odd, adjusting from [{}] to [{}]", prev_width, width ) } + + if desktop_scale_factor < 100 { + warn!( + "Desktop scale factor [{}] is less than 100, adjusting to 100", + desktop_scale_factor + ); + desktop_scale_factor = 100; + } + + if desktop_scale_factor > 500 { + warn!( + "Desktop scale factor [{}] is greater than 500, adjusting to 500", + desktop_scale_factor + ); + desktop_scale_factor = 500; + } + validate_dimensions(width, height)?; Ok(Self { From 5229b0815a2549d2de72d02112fa4783742946ab Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 4 Apr 2024 11:59:38 -0700 Subject: [PATCH 69/84] Always use long length for FastPath messages, in line with FreeRDP --- crates/ironrdp-pdu/src/input/fast_path.rs | 6 ++---- crates/ironrdp-pdu/src/per.rs | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/ironrdp-pdu/src/input/fast_path.rs b/crates/ironrdp-pdu/src/input/fast_path.rs index 6686f0ac6..7590ccfa7 100644 --- a/crates/ironrdp-pdu/src/input/fast_path.rs +++ b/crates/ironrdp-pdu/src/input/fast_path.rs @@ -34,7 +34,7 @@ impl PduEncode for FastPathInputHeader { header.set_bits(6..8, self.flags.bits()); dst.write_u8(header); - per::write_length(dst, cast_length!("len", self.data_length + self.size())?); + per::write_long_length(dst, cast_length!("len", self.data_length + self.size())?); if self.num_events > 15 { dst.write_u8(self.num_events); } @@ -48,9 +48,7 @@ impl PduEncode for FastPathInputHeader { fn size(&self) -> usize { let num_events_length = if self.num_events < 16 { 0 } else { 1 }; - Self::FIXED_PART_SIZE - + per::sizeof_length(self.data_length as u16 + num_events_length as u16 + 1) - + num_events_length + Self::FIXED_PART_SIZE + per::sizeof_long_length() + num_events_length } } diff --git a/crates/ironrdp-pdu/src/per.rs b/crates/ironrdp-pdu/src/per.rs index e95a495e0..a58cfcbae 100644 --- a/crates/ironrdp-pdu/src/per.rs +++ b/crates/ironrdp-pdu/src/per.rs @@ -122,6 +122,10 @@ pub(crate) fn sizeof_length(length: u16) -> usize { } } +pub(crate) fn sizeof_long_length() -> usize { + 2 +} + pub(crate) fn sizeof_u32(value: u32) -> usize { if value <= 0xff { 2 From d57965e6f8573e2f47c4a52eeeca8da0524c24c2 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 8 Apr 2024 10:06:55 -0700 Subject: [PATCH 70/84] broken commit: but adds desktop scale factor to the clientinfo --- crates/ironrdp-client/src/config.rs | 1 + crates/ironrdp-client/src/main.rs | 1 + crates/ironrdp-connector/src/connection.rs | 14 +++++++++----- crates/ironrdp-connector/src/lib.rs | 1 + .../ironrdp-pdu/src/codecs/rfx/header_messages.rs | 10 ++-------- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/crates/ironrdp-client/src/config.rs b/crates/ironrdp-client/src/config.rs index fe5c5dec4..713bb0bda 100644 --- a/crates/ironrdp-client/src/config.rs +++ b/crates/ironrdp-client/src/config.rs @@ -296,6 +296,7 @@ impl Config { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT, }, + desktop_scale_factor: 100, // todo: make a default bitmap, client_build: semver::Version::parse(env!("CARGO_PKG_VERSION")) .map(|version| version.major * 100 + version.minor * 10 + version.patch) diff --git a/crates/ironrdp-client/src/main.rs b/crates/ironrdp-client/src/main.rs index 7cd5cecb9..6a7d5916b 100644 --- a/crates/ironrdp-client/src/main.rs +++ b/crates/ironrdp-client/src/main.rs @@ -19,6 +19,7 @@ fn main() -> anyhow::Result<()> { debug!("GUI context initialized"); let window_size = gui.window().inner_size(); + config.connector.desktop_scale_factor = (gui.window().scale_factor() * 100.0) as u32; config.connector.desktop_size.width = u16::try_from(window_size.width).unwrap(); config.connector.desktop_size.height = u16::try_from(window_size.height).unwrap(); diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 4bcdd25da..a8cb3936b 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -660,11 +660,15 @@ fn create_gcc_blocks<'a>( dig_product_id: Some(config.dig_product_id.clone()), connection_type: Some(ConnectionType::Lan), server_selected_protocol: Some(selected_protocol), - desktop_physical_width: None, - desktop_physical_height: None, - desktop_orientation: None, - desktop_scale_factor: None, - device_scale_factor: None, + desktop_physical_width: Some(0), + desktop_physical_height: Some(0), + desktop_orientation: if config.desktop_size.width > config.desktop_size.height { + Some(MonitorOrientation::Landscape as u16) + } else { + Some(MonitorOrientation::Portrait as u16) + }, + desktop_scale_factor: Some(config.desktop_scale_factor), + device_scale_factor: Some(100), }, }, security: ClientSecurityData { diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index 7ecf2cd4c..aa9e1a1c1 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -72,6 +72,7 @@ impl Credentials { pub struct Config { /// The initial desktop size to request pub desktop_size: DesktopSize, + pub desktop_scale_factor: u32, /// TLS + Graphical login (legacy) /// /// Also called SSL or TLS security protocol. diff --git a/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs b/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs index 4886a57e0..a0faf0c81 100644 --- a/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs +++ b/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs @@ -171,10 +171,7 @@ pub struct RfxChannelWidth(i16); impl RfxChannelWidth { pub fn new(value: i16) -> Result { - (1..=4096) - .contains(&value) - .then_some(Self(value)) - .ok_or(RfxError::InvalidChannelWidth(value)) + Ok(Self(value)) } pub fn as_u16(self) -> u16 { @@ -193,10 +190,7 @@ pub struct RfxChannelHeight(i16); impl RfxChannelHeight { pub fn new(value: i16) -> Result { - (1..=2048) - .contains(&value) - .then_some(Self(value)) - .ok_or(RfxError::InvalidChannelHeight(value)) + Ok(Self(value)) } pub fn as_u16(self) -> u16 { From 2440098c668f5f5ae014d67ac4a9a4134611afc9 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 17 Apr 2024 14:13:20 -0700 Subject: [PATCH 71/84] Adds some documentation and cerification to gcc blocks --- crates/ironrdp-pdu/src/gcc.rs | 3 + .../ironrdp-pdu/src/gcc/core_data/client.rs | 77 ++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/crates/ironrdp-pdu/src/gcc.rs b/crates/ironrdp-pdu/src/gcc.rs index 70a691288..d22ec9067 100644 --- a/crates/ironrdp-pdu/src/gcc.rs +++ b/crates/ironrdp-pdu/src/gcc.rs @@ -51,6 +51,9 @@ macro_rules! user_header_try { const USER_DATA_HEADER_SIZE: usize = 4; +/// 2.2.1.3 Client MCS Connect Initial PDU with GCC Conference Create Request +/// +/// [2.2.1.3]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/db6713ee-1c0e-4064-a3b3-0fac30b4037b #[derive(Debug, Clone, PartialEq, Eq)] pub struct ClientGccBlocks { pub core: ClientCoreData, diff --git a/crates/ironrdp-pdu/src/gcc/core_data/client.rs b/crates/ironrdp-pdu/src/gcc/core_data/client.rs index 52e91d86b..1af6ac05f 100644 --- a/crates/ironrdp-pdu/src/gcc/core_data/client.rs +++ b/crates/ironrdp-pdu/src/gcc/core_data/client.rs @@ -37,7 +37,9 @@ const DESKTOP_ORIENTATION_SIZE: usize = 2; const DESKTOP_SCALE_FACTOR_SIZE: usize = 4; const DEVICE_SCALE_FACTOR_SIZE: usize = 4; -/// TS_UD_CS_CORE (required part) +/// 2.2.1.3.2 Client Core Data (TS_UD_CS_CORE) (required part) +/// +/// [2.2.1.3.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/00f1da4a-ee9c-421a-852f-c19f92343d73 #[derive(Debug, Clone, PartialEq, Eq)] pub struct ClientCoreData { pub version: RdpVersion, @@ -182,7 +184,12 @@ impl<'de> PduDecode<'de> for ClientCoreData { } } -/// TS_UD_CS_CORE (optional part) +/// 2.2.1.3.2 Client Core Data (TS_UD_CS_CORE) (optional part) +/// +/// For every field in this structure, the previous fields MUST be present in order to be a valid structure. +/// It is incumbent on the user of this structure to ensure that the structure is valid. +/// +/// [2.2.1.3.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/00f1da4a-ee9c-421a-852f-c19f92343d73 #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct ClientCoreOptionalData { /// The requested color depth. Values in this field MUST be ignored if the highColorDepth field is present. @@ -217,26 +224,56 @@ impl PduEncode for ClientCoreOptionalData { } if let Some(value) = self.client_product_id { + if self.post_beta2_color_depth.is_none() { + return Err(invalid_message_err!( + "postBeta2ColorDepth", + "postBeta2ColorDepth must be present" + )); + } dst.write_u16(value); } if let Some(value) = self.serial_number { + if self.client_product_id.is_none() { + return Err(invalid_message_err!( + "clientProductId", + "clientProductId must be present" + )); + } dst.write_u32(value); } if let Some(value) = self.high_color_depth { + if self.serial_number.is_none() { + return Err(invalid_message_err!("serialNumber", "serialNumber must be present")); + } dst.write_u16(value.to_u16().unwrap()); } if let Some(value) = self.supported_color_depths { + if self.high_color_depth.is_none() { + return Err(invalid_message_err!("highColorDepth", "highColorDepth must be present")); + } dst.write_u16(value.bits()); } if let Some(value) = self.early_capability_flags { + if self.supported_color_depths.is_none() { + return Err(invalid_message_err!( + "supportedColorDepths", + "supportedColorDepths must be present" + )); + } dst.write_u16(value.bits()); } if let Some(ref value) = self.dig_product_id { + if self.early_capability_flags.is_none() { + return Err(invalid_message_err!( + "earlyCapabilityFlags", + "earlyCapabilityFlags must be present" + )); + } let mut dig_product_id_buffer = utils::to_utf16_bytes(value); dig_product_id_buffer.resize(DIG_PRODUCT_ID_SIZE - 2, 0); dig_product_id_buffer.extend_from_slice([0; 2].as_ref()); // UTF-16 null terminator @@ -245,31 +282,67 @@ impl PduEncode for ClientCoreOptionalData { } if let Some(value) = self.connection_type { + if self.dig_product_id.is_none() { + return Err(invalid_message_err!("digProductId", "digProductId must be present")); + } dst.write_u8(value.to_u8().unwrap()); write_padding!(dst, 1); } if let Some(value) = self.server_selected_protocol { + if self.connection_type.is_none() { + return Err(invalid_message_err!("connectionType", "connectionType must be present")); + } dst.write_u32(value.bits()) } if let Some(value) = self.desktop_physical_width { + if self.server_selected_protocol.is_none() { + return Err(invalid_message_err!( + "serverSelectedProtocol", + "serverSelectedProtocol must be present" + )); + } dst.write_u32(value); } if let Some(value) = self.desktop_physical_height { + if self.desktop_physical_width.is_none() { + return Err(invalid_message_err!( + "desktopPhysicalWidth", + "desktopPhysicalWidth must be present" + )); + } dst.write_u32(value); } if let Some(value) = self.desktop_orientation { + if self.desktop_physical_height.is_none() { + return Err(invalid_message_err!( + "desktopPhysicalHeight", + "desktopPhysicalHeight must be present" + )); + } dst.write_u16(value); } if let Some(value) = self.desktop_scale_factor { + if self.desktop_orientation.is_none() { + return Err(invalid_message_err!( + "desktopOrientation", + "desktopOrientation must be present" + )); + } dst.write_u32(value); } if let Some(value) = self.device_scale_factor { + if self.desktop_scale_factor.is_none() { + return Err(invalid_message_err!( + "desktopScaleFactor", + "desktopScaleFactor must be present" + )); + } dst.write_u32(value); } From 83e18b89220cd09e331476fb7cd57c6556c7205a Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 17 Apr 2024 14:18:24 -0700 Subject: [PATCH 72/84] Refactors desktop scale factor support, reverts to minimal fast path size --- Cargo.lock | 1 + crates/ironrdp-client/src/config.rs | 2 +- crates/ironrdp-client/src/gui.rs | 2 + crates/ironrdp-client/src/main.rs | 2 +- crates/ironrdp-client/src/rdp.rs | 35 +++---- crates/ironrdp-connector/src/connection.rs | 12 ++- .../src/connection_activation.rs | 20 +++- crates/ironrdp-connector/src/lib.rs | 3 + crates/ironrdp-displaycontrol/src/client.rs | 16 +--- crates/ironrdp-displaycontrol/src/pdu/mod.rs | 96 ++++++++----------- crates/ironrdp-pdu/src/input/fast_path.rs | 6 +- crates/ironrdp-session/Cargo.toml | 1 + crates/ironrdp-session/src/active_stage.rs | 40 +++++++- crates/ironrdp-web/src/session.rs | 2 + crates/ironrdp/examples/screenshot.rs | 1 + 15 files changed, 134 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3d060043..b443a4770 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1920,6 +1920,7 @@ name = "ironrdp-session" version = "0.1.0" dependencies = [ "ironrdp-connector", + "ironrdp-displaycontrol", "ironrdp-dvc", "ironrdp-error", "ironrdp-graphics", diff --git a/crates/ironrdp-client/src/config.rs b/crates/ironrdp-client/src/config.rs index 713bb0bda..e31cb21ce 100644 --- a/crates/ironrdp-client/src/config.rs +++ b/crates/ironrdp-client/src/config.rs @@ -296,7 +296,7 @@ impl Config { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT, }, - desktop_scale_factor: 100, // todo: make a default + desktop_scale_factor: 0, // Default to 0 per FreeRDP bitmap, client_build: semver::Version::parse(env!("CARGO_PKG_VERSION")) .map(|version| version.major * 100 + version.minor * 10 + version.patch) diff --git a/crates/ironrdp-client/src/gui.rs b/crates/ironrdp-client/src/gui.rs index 1ce020f4a..3b01291ea 100644 --- a/crates/ironrdp-client/src/gui.rs +++ b/crates/ironrdp-client/src/gui.rs @@ -233,6 +233,8 @@ impl GuiContext { // TODO: is there something we should handle here? } Event::UserEvent(RdpOutputEvent::Image { buffer, width, height }) => { + debug!(width = ?width, height = ?height, "Received image with size"); + debug!(window_physical_size = ?window.inner_size(), "Drawing image to the window with size"); surface .resize( NonZeroU32::new(u32::from(width)).unwrap(), diff --git a/crates/ironrdp-client/src/main.rs b/crates/ironrdp-client/src/main.rs index 6a7d5916b..63ffee0ca 100644 --- a/crates/ironrdp-client/src/main.rs +++ b/crates/ironrdp-client/src/main.rs @@ -19,7 +19,7 @@ fn main() -> anyhow::Result<()> { debug!("GUI context initialized"); let window_size = gui.window().inner_size(); - config.connector.desktop_scale_factor = (gui.window().scale_factor() * 100.0) as u32; + config.connector.desktop_scale_factor = 0; // TODO: should this be `(gui.window().scale_factor() * 100.0) as u32`? config.connector.desktop_size.width = u16::try_from(window_size.width).unwrap(); config.connector.desktop_size.height = u16::try_from(window_size.height).unwrap(); diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 2bd965acf..fad7945a8 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -2,13 +2,11 @@ use ironrdp::cliprdr::backend::{ClipboardMessage, CliprdrBackendFactory}; use ironrdp::connector::connection_activation::ConnectionActivationState; use ironrdp::connector::{ConnectionResult, ConnectorResult}; use ironrdp::displaycontrol::client::DisplayControlClient; -use ironrdp::dvc::DrdynvcClient; use ironrdp::graphics::image_processing::PixelFormat; use ironrdp::pdu::input::fast_path::FastPathInputEvent; use ironrdp::pdu::write_buf::WriteBuf; use ironrdp::session::image::DecodedImage; use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDisconnectReason, SessionResult}; -use ironrdp::svc::SvcProcessorMessages; use ironrdp::{cliprdr, connector, rdpdr, rdpsnd, session}; use ironrdp_tokio::single_sequence_step_read; use rdpdr::NoopRdpdrBackend; @@ -188,34 +186,26 @@ async fn active_session( let input_event = input_event.ok_or_else(|| session::general_err!("GUI is stopped"))?; match input_event { - RdpInputEvent::Resize { mut width, mut height, mut scale_factor, mut physical_width, mut physical_height } => { + RdpInputEvent::Resize { mut width, mut height, .. } => { // Find the last resize event while let Ok(newer_event) = input_event_receiver.try_recv() { - if let RdpInputEvent::Resize { width: newer_width, height: newer_height, scale_factor: newer_scale_factor, physical_width: newer_physical_width, physical_height: newer_physical_height } = newer_event { + if let RdpInputEvent::Resize { + width: newer_width, + height: newer_height, + .. + } = newer_event { width = newer_width; height = newer_height; - scale_factor = newer_scale_factor; - physical_width = newer_physical_width; - physical_height = newer_physical_height; } } info!(width, height, "resize event"); - if let Some((display_client, channel_id)) = active_stage.get_dvc_processor::() { - if let Some(channel_id) = channel_id { - let svc_messages = display_client.encode_single_primary_monitor(channel_id, width.into(), height.into(), scale_factor, physical_width, physical_height) - .map_err(|e| session::custom_err!("DisplayControl", e))?; - let frame = active_stage.process_svc_processor_messages(SvcProcessorMessages::::new(svc_messages))?; - vec![ActiveStageOutput::ResponseFrame(frame)] - } else { - // TODO: could add a mechanism that withholds the resize event until the channel is created rather than reconnecting - debug!("Display Control Virtual Channel is not yet connected, reconnecting with new size"); - return Ok(RdpControlFlow::ReconnectWithNewSize { width, height }) - } + if let Some(response_frame) = active_stage.encode_resize(width, height, None, None) { + vec![ActiveStageOutput::ResponseFrame(response_frame?)] } else { // TODO(#271): use the "auto-reconnect cookie": https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/15b0d1c9-2891-4adb-a45e-deb4aeeeab7c - debug!("Display Control Virtual Channel is not available, reconnecting with new size"); + debug!("Reconnecting with new size"); return Ok(RdpControlFlow::ReconnectWithNewSize { width, height }) } }, @@ -330,10 +320,10 @@ async fn active_session( pointer_software_rendering, } = connection_activation.state { - debug!("Deactivation-Reactivation Sequence completed"); + debug!(?desktop_size, "Deactivation-Reactivation Sequence completed"); + // Update image size with the new desktop size. image = DecodedImage::new(PixelFormat::RgbA32, desktop_size.width, desktop_size.height); - // Create a new [`FastPathProcessor`] with potentially updated - // io/user channel ids. + // Update the active stage with the new channel IDs and pointer settings. active_stage.set_fastpath_processor( fast_path::ProcessorBuilder { io_channel_id, @@ -343,6 +333,7 @@ async fn active_session( } .build(), ); + active_stage.set_no_server_pointer(no_server_pointer); break 'activation_seq; } } diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index a8cb3936b..f03c304c8 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -625,7 +625,7 @@ fn create_gcc_blocks<'a>( ClientGccBlocks { core: ClientCoreData { - version: RdpVersion::V5_PLUS, + version: RdpVersion::V10_12, desktop_width: config.desktop_size.width, desktop_height: config.desktop_size.height, color_depth: ColorDepth::Bpp8, // ignored because we use the optional core data below @@ -660,15 +660,19 @@ fn create_gcc_blocks<'a>( dig_product_id: Some(config.dig_product_id.clone()), connection_type: Some(ConnectionType::Lan), server_selected_protocol: Some(selected_protocol), - desktop_physical_width: Some(0), - desktop_physical_height: Some(0), + desktop_physical_width: Some(0), // 0 per FreeRDP + desktop_physical_height: Some(0), // 0 per FreeRDP desktop_orientation: if config.desktop_size.width > config.desktop_size.height { Some(MonitorOrientation::Landscape as u16) } else { Some(MonitorOrientation::Portrait as u16) }, desktop_scale_factor: Some(config.desktop_scale_factor), - device_scale_factor: Some(100), + device_scale_factor: if config.desktop_scale_factor >= 100 && config.desktop_scale_factor <= 500 { + Some(100) + } else { + Some(0) + }, }, }, security: ClientSecurityData { diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs index 9f0fd9bfa..464658ca8 100644 --- a/crates/ironrdp-connector/src/connection_activation.rs +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -19,7 +19,7 @@ use crate::{legacy, Config, ConnectionFinalizationSequence, ConnectorResult, Des #[derive(Debug, Clone)] pub struct ConnectionActivationSequence { pub state: ConnectionActivationState, - pub config: Config, + config: Config, } impl ConnectionActivationSequence { @@ -127,6 +127,15 @@ impl Sequence for ConnectionActivationSequence { } } + // At this point we have already sent a requested desktop size to the server -- either as a part of the + // [`TS_UD_CS_CORE`] (on initial connection) or the [`DISPLAYCONTROL_MONITOR_LAYOUT`] (on resize event). + // + // The server is therefore responding with a desktop size here, which will be close to the requested size but + // may be slightly different due to server-side constraints. We should use this negotiated size for the rest of + // the session. + // + // [TS_UD_CS_CORE]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/00f1da4a-ee9c-421a-852f-c19f92343d73 + // [DISPLAYCONTROL_MONITOR_LAYOUT]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c let desktop_size = capability_sets .iter() .find_map(|c| match c { @@ -142,7 +151,7 @@ impl Sequence for ConnectionActivationSequence { }); let client_confirm_active = rdp::headers::ShareControlPdu::ClientConfirmActive( - create_client_confirm_active(&self.config, capability_sets), + create_client_confirm_active(&self.config, capability_sets, desktop_size), ); debug!(message = ?client_confirm_active, "Send"); @@ -249,6 +258,7 @@ const DEFAULT_POINTER_CACHE_SIZE: u16 = 32; fn create_client_confirm_active( config: &Config, mut server_capability_sets: Vec, + desktop_size: DesktopSize, ) -> rdp::capability_sets::ClientConfirmActive { use ironrdp_pdu::rdp::capability_sets::*; @@ -276,8 +286,8 @@ fn create_client_confirm_active( }), CapabilitySet::Bitmap(Bitmap { pref_bits_per_pix: 32, - desktop_width: config.desktop_size.width, - desktop_height: config.desktop_size.height, + desktop_width: desktop_size.width, + desktop_height: desktop_size.height, // This is required to be true in order for the Microsoft::Windows::RDS::DisplayControl DVC to work. desktop_resize_flag: true, drawing_flags, @@ -362,7 +372,7 @@ fn create_client_confirm_active( .any(|c| matches!(&c, CapabilitySet::MultiFragmentUpdate(_))) { server_capability_sets.push(CapabilitySet::MultiFragmentUpdate(MultifragmentUpdate { - max_request_size: 1024, + max_request_size: 8 * 1024 * 1024, // 8 MB })); } diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index aa9e1a1c1..724fdf47e 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -72,6 +72,9 @@ impl Credentials { pub struct Config { /// The initial desktop size to request pub desktop_size: DesktopSize, + /// The initial desktop scale factor to request. + /// + /// This becomes the `desktop_scale_factor` in the [`TS_UD_CS_CORE`](gcc::ClientCoreOptionalData) structure. pub desktop_scale_factor: u32, /// TLS + Graphical login (legacy) /// diff --git a/crates/ironrdp-displaycontrol/src/client.rs b/crates/ironrdp-displaycontrol/src/client.rs index 548b14cca..e967c4581 100644 --- a/crates/ironrdp-displaycontrol/src/client.rs +++ b/crates/ironrdp-displaycontrol/src/client.rs @@ -42,18 +42,12 @@ impl DisplayControlClient { channel_id: u32, width: u32, height: u32, - scale_factor: u32, - physical_width: u32, - physical_height: u32, + scale_factor: Option, + physical_dims: Option<(u32, u32)>, ) -> PduResult> { - let pdu: DisplayControlPdu = DisplayControlMonitorLayout::new_single_primary_monitor( - width, - height, - scale_factor, - physical_width, - physical_height, - )? - .into(); + let pdu: DisplayControlPdu = + DisplayControlMonitorLayout::new_single_primary_monitor(width, height, scale_factor, physical_dims)?.into(); + debug!(?pdu, "Sending monitor layout"); encode_dvc_messages(channel_id, vec![Box::new(pdu)], ChannelFlags::empty()) } } diff --git a/crates/ironrdp-displaycontrol/src/pdu/mod.rs b/crates/ironrdp-displaycontrol/src/pdu/mod.rs index 9fc9c3c1c..2daec7ecc 100644 --- a/crates/ironrdp-displaycontrol/src/pdu/mod.rs +++ b/crates/ironrdp-displaycontrol/src/pdu/mod.rs @@ -236,24 +236,38 @@ impl DisplayControlMonitorLayout { } /// Creates a new [`DisplayControlMonitorLayout`] with a single primary monitor - /// with the given `width` and `height`. + /// with the given `width` and `height`. `width` and `height` MUST be >= 200 and <= 8192, + /// and if `width` is odd, it will be adjusted to the nearest even number by subtracting 1. + /// + /// - If `scale_factor` is provided, it MUST be in the valid range (100..=500 percent). + /// - If `physical_dims` are provided, they MUST be in the valid range (10..=10000 millimeters). pub fn new_single_primary_monitor( width: u32, height: u32, - scale_factor: u32, - physical_width: u32, - physical_height: u32, + scale_factor: Option, + physical_dims: Option<(u32, u32)>, ) -> PduResult { - let monitors = - vec![ - MonitorLayoutEntry::new_primary(width, height, scale_factor, physical_width, physical_height)? - .with_orientation(if width > height { - MonitorOrientation::Landscape - } else { - MonitorOrientation::Portrait - }), - ]; - Ok(DisplayControlMonitorLayout::new(&monitors).unwrap()) + let entry = MonitorLayoutEntry::new_primary(width, height)?.with_orientation(if width > height { + MonitorOrientation::Landscape + } else { + MonitorOrientation::Portrait + }); + + let entry = if let Some(scale_factor) = scale_factor { + entry + .with_desktop_scale_factor(scale_factor)? + .with_device_scale_factor(DeviceScaleFactor::Scale100Percent) + } else { + entry + }; + + let entry = if let Some((physical_width, physical_height)) = physical_dims { + entry.with_physical_dimensions(physical_width, physical_height)? + } else { + entry + }; + + Ok(DisplayControlMonitorLayout::new(&[entry]).unwrap()) } pub fn monitors(&self) -> &[MonitorLayoutEntry] { @@ -363,13 +377,7 @@ impl MonitorLayoutEntry { /// to the nearest even number by subtracting 1. /// - `desktop_scale_factor` SHOULD be >= 100 and <= 500. If it is not, /// it will be adjusted to the nearest valid value. - fn new_impl( - mut width: u32, - height: u32, - mut desktop_scale_factor: u32, - physical_width: u32, - physical_height: u32, - ) -> PduResult { + fn new_impl(mut width: u32, height: u32) -> PduResult { if width % 2 != 0 { let prev_width = width; width = width.saturating_sub(1); @@ -379,22 +387,6 @@ impl MonitorLayoutEntry { ) } - if desktop_scale_factor < 100 { - warn!( - "Desktop scale factor [{}] is less than 100, adjusting to 100", - desktop_scale_factor - ); - desktop_scale_factor = 100; - } - - if desktop_scale_factor > 500 { - warn!( - "Desktop scale factor [{}] is greater than 500, adjusting to 500", - desktop_scale_factor - ); - desktop_scale_factor = 500; - } - validate_dimensions(width, height)?; Ok(Self { @@ -403,36 +395,24 @@ impl MonitorLayoutEntry { top: 0, width, height, - physical_width, - physical_height, + physical_width: 0, + physical_height: 0, orientation: 0, - desktop_scale_factor, - device_scale_factor: 100, + desktop_scale_factor: 0, + device_scale_factor: 0, }) } /// Creates a new primary monitor layout entry. - pub fn new_primary( - width: u32, - height: u32, - desktop_scale_factor: u32, - physical_width: u32, - physical_height: u32, - ) -> PduResult { - let mut entry = Self::new_impl(width, height, desktop_scale_factor, physical_width, physical_height)?; + pub fn new_primary(width: u32, height: u32) -> PduResult { + let mut entry = Self::new_impl(width, height)?; entry.is_primary = true; Ok(entry) } /// Creates a new secondary monitor layout entry. - pub fn new_secondary( - width: u32, - height: u32, - desktop_scale_factor: u32, - physical_width: u32, - physical_height: u32, - ) -> PduResult { - Self::new_impl(width, height, desktop_scale_factor, physical_width, physical_height) + pub fn new_secondary(width: u32, height: u32) -> PduResult { + Self::new_impl(width, height) } /// Sets the monitor's orientation. (Default is [`MonitorOrientation::Landscape`]) @@ -461,7 +441,7 @@ impl MonitorLayoutEntry { self } - /// Sets the monitor's desktop scale factor in percent. (Default is `100`) + /// Sets the monitor's desktop scale factor in percent. /// /// NOTE: As specified in [MS-RDPEDISP], if the desktop scale factor is not in the valid range /// (100..=500 percent), the monitor desktop scale factor is considered invalid and should be ignored. diff --git a/crates/ironrdp-pdu/src/input/fast_path.rs b/crates/ironrdp-pdu/src/input/fast_path.rs index 7590ccfa7..6686f0ac6 100644 --- a/crates/ironrdp-pdu/src/input/fast_path.rs +++ b/crates/ironrdp-pdu/src/input/fast_path.rs @@ -34,7 +34,7 @@ impl PduEncode for FastPathInputHeader { header.set_bits(6..8, self.flags.bits()); dst.write_u8(header); - per::write_long_length(dst, cast_length!("len", self.data_length + self.size())?); + per::write_length(dst, cast_length!("len", self.data_length + self.size())?); if self.num_events > 15 { dst.write_u8(self.num_events); } @@ -48,7 +48,9 @@ impl PduEncode for FastPathInputHeader { fn size(&self) -> usize { let num_events_length = if self.num_events < 16 { 0 } else { 1 }; - Self::FIXED_PART_SIZE + per::sizeof_long_length() + num_events_length + Self::FIXED_PART_SIZE + + per::sizeof_length(self.data_length as u16 + num_events_length as u16 + 1) + + num_events_length } } diff --git a/crates/ironrdp-session/Cargo.toml b/crates/ironrdp-session/Cargo.toml index 11995b539..a4a178880 100644 --- a/crates/ironrdp-session/Cargo.toml +++ b/crates/ironrdp-session/Cargo.toml @@ -22,4 +22,5 @@ ironrdp-dvc.workspace = true ironrdp-error.workspace = true ironrdp-graphics.workspace = true ironrdp-pdu = { workspace = true, features = ["std"] } +ironrdp-displaycontrol.workspace = true tracing.workspace = true diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index 5bcb32447..4ed314053 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -2,7 +2,8 @@ use std::rc::Rc; use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::ConnectionResult; -use ironrdp_dvc::{DvcProcessor, DynamicChannelId}; +use ironrdp_displaycontrol::client::DisplayControlClient; +use ironrdp_dvc::{DrdynvcClient, DvcProcessor, DynamicChannelId}; use ironrdp_graphics::pointer::DecodedPointer; use ironrdp_pdu::geometry::InclusiveRectangle; use ironrdp_pdu::input::fast_path::{FastPathInput, FastPathInputEvent}; @@ -151,6 +152,10 @@ impl ActiveStage { self.fast_path_processor = processor; } + pub fn set_no_server_pointer(&mut self, no_server_pointer: bool) { + self.no_server_pointer = no_server_pointer; + } + /// Encodes client-side graceful shutdown request. Note that upon sending this request, /// client should wait for server's ShutdownDenied PDU before closing the connection. /// @@ -190,6 +195,39 @@ impl ActiveStage { ) -> SessionResult> { self.x224_processor.process_svc_processor_messages(messages) } + + pub fn encode_resize( + &mut self, + width: u16, + height: u16, + scale_factor: Option, + physical_dims: Option<(u32, u32)>, + ) -> Option>> { + if let Some((display_client, channel_id)) = self.get_dvc_processor::() { + if let Some(channel_id) = channel_id { + let svc_messages = match display_client.encode_single_primary_monitor( + channel_id, + width.into(), + height.into(), + scale_factor, + physical_dims, + ) { + Ok(messages) => messages, + Err(e) => return Some(Err(SessionError::pdu(e))), + }; + + return Some( + self.process_svc_processor_messages(SvcProcessorMessages::::new(svc_messages)), + ); + } else { + debug!("Could not encode a resize: Display Control Virtual Channel is not yet connected"); + } + } else { + debug!("Could not encode a resize: Display Control Virtual Channel is not available"); + } + + None + } } #[derive(Debug)] diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index f2f675ea0..43c0e8839 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -643,6 +643,7 @@ impl Session { } .build(), ); + active_stage.set_no_server_pointer(no_server_pointer); break 'activation_seq; } } @@ -802,6 +803,7 @@ fn build_config( autologon: false, pointer_software_rendering: false, performance_flags: PerformanceFlags::default(), + desktop_scale_factor: 0, } } diff --git a/crates/ironrdp/examples/screenshot.rs b/crates/ironrdp/examples/screenshot.rs index f38bf88f3..3bbca4289 100644 --- a/crates/ironrdp/examples/screenshot.rs +++ b/crates/ironrdp/examples/screenshot.rs @@ -217,6 +217,7 @@ fn build_config(username: String, password: String, domain: Option) -> c autologon: false, pointer_software_rendering: true, performance_flags: PerformanceFlags::default(), + desktop_scale_factor: 0, } } From 7ec004815cf408230a76c9e9ace0927b8be9ead8 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 24 Apr 2024 15:35:02 -0700 Subject: [PATCH 73/84] Cleaning up the get_dvc_processor api The get_dvc_processor api was a bit confusing, returning a `Option<(&T, Option)>` with a list of semantics for what None meant for the whole value and the `DynamicChannelId`. This change converts what was previously `DynamicVirtualChannel` to be named `DynamicVirtualChannelInternal`, and then adds a new public type named `DynamicVirtualChannel` which wraps the internal type and provides some more straightforward semantics via its function names. --- crates/ironrdp-client/src/gui.rs | 4 +- crates/ironrdp-client/src/rdp.rs | 2 +- crates/ironrdp-dvc/src/client.rs | 16 ++--- crates/ironrdp-dvc/src/lib.rs | 83 +++++++++++++++++----- crates/ironrdp-session/src/active_stage.rs | 22 ++++-- crates/ironrdp-session/src/x224/mod.rs | 4 +- ffi/src/connector/config.rs | 1 + 7 files changed, 94 insertions(+), 38 deletions(-) diff --git a/crates/ironrdp-client/src/gui.rs b/crates/ironrdp-client/src/gui.rs index 3b01291ea..4b4b7c680 100644 --- a/crates/ironrdp-client/src/gui.rs +++ b/crates/ironrdp-client/src/gui.rs @@ -233,8 +233,8 @@ impl GuiContext { // TODO: is there something we should handle here? } Event::UserEvent(RdpOutputEvent::Image { buffer, width, height }) => { - debug!(width = ?width, height = ?height, "Received image with size"); - debug!(window_physical_size = ?window.inner_size(), "Drawing image to the window with size"); + trace!(width = ?width, height = ?height, "Received image with size"); + trace!(window_physical_size = ?window.inner_size(), "Drawing image to the window with size"); surface .resize( NonZeroU32::new(u32::from(width)).unwrap(), diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index fad7945a8..e314874ce 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -201,7 +201,7 @@ async fn active_session( info!(width, height, "resize event"); - if let Some(response_frame) = active_stage.encode_resize(width, height, None, None) { + if let Some(response_frame) = active_stage.encode_resize(width, height, None, Some((width.into(), height.into()))) { // Set physical width and height to the same as the pixel width and heighbbt per FreeRDP vec![ActiveStageOutput::ResponseFrame(response_frame?)] } else { // TODO(#271): use the "auto-reconnect cookie": https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/15b0d1c9-2891-4adb-a45e-deb4aeeeab7c diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 096a4df1f..8ad01f750 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -2,7 +2,7 @@ use crate::pdu::{ CapabilitiesResponsePdu, CapsVersion, ClosePdu, CreateResponsePdu, CreationStatus, DrdynvcClientPdu, DrdynvcServerPdu, }; -use crate::{encode_dvc_messages, DvcProcessor, DynamicChannelId, DynamicChannelSet}; +use crate::{encode_dvc_messages, DvcProcessor, DynamicChannelSet, DynamicVirtualChannel}; use alloc::vec::Vec; use core::any::TypeId; use core::fmt; @@ -61,17 +61,13 @@ impl DrdynvcClient { self } - pub fn get_dynamic_channel_by_type_id(&self) -> Option<(&T, Option)> + pub fn get_dynamic_channel_by_type_id(&self) -> Option> where T: DvcProcessor, { - self.dynamic_channels - .get_by_type_id(TypeId::of::()) - .and_then(|(channel, channel_id)| { - channel - .channel_processor_downcast_ref() - .map(|channel| (channel as &T, channel_id)) - }) + Some(DynamicVirtualChannel::::new( + self.dynamic_channels.get_by_type_id(TypeId::of::())?, + )) } fn create_capabilities_response(&mut self) -> SvcMessage { @@ -128,7 +124,7 @@ impl SvcProcessor for DrdynvcClient { self.dynamic_channels .attach_channel_id(channel_name.clone(), channel_id); let dynamic_channel = self.dynamic_channels.get_by_channel_name_mut(&channel_name).unwrap(); - (CreationStatus::OK, dynamic_channel.start(channel_id)?) + (CreationStatus::OK, dynamic_channel.start()?) } else { (CreationStatus::NO_LISTENER, Vec::new()) }; diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 8f7600746..8625e9136 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -98,21 +98,67 @@ pub fn encode_dvc_messages( Ok(res) } -pub struct DynamicVirtualChannel { +pub struct DynamicVirtualChannel<'a, T: DvcProcessor> { + internal: &'a DynamicVirtualChannelInternal, + _marker: core::marker::PhantomData, +} + +impl<'a, T: DvcProcessor> DynamicVirtualChannel<'a, T> { + pub(crate) fn new(internal: &'a DynamicVirtualChannelInternal) -> Self { + Self { + internal, + _marker: core::marker::PhantomData, + } + } + + pub fn is_open(&self) -> bool { + self.internal.is_open() + } + + pub fn channel_id(&self) -> PduResult { + self.internal + .channel_id + .ok_or_else(|| other_err!("DynamicVirtualChannel::channel_id", "channel ID not set")) + } + + pub fn channel_processor_downcast_ref(&self) -> PduResult<&T> { + self.internal.channel_processor_downcast_ref().ok_or_else(|| { + other_err!( + "DynamicVirtualChannel::channel_processor_downcast_ref", + "downcast failed" + ) + }) + } +} + +struct DynamicVirtualChannelInternal { channel_processor: Box, complete_data: CompleteData, + /// The channel ID assigned by the server. + /// + /// This field is `None` until the server assigns a channel ID. + channel_id: Option, } -impl DynamicVirtualChannel { +impl DynamicVirtualChannelInternal { fn new(handler: T) -> Self { Self { channel_processor: Box::new(handler), complete_data: CompleteData::new(), + channel_id: None, } } - fn start(&mut self, channel_id: DynamicChannelId) -> PduResult> { - self.channel_processor.start(channel_id) + fn is_open(&self) -> bool { + self.channel_id.is_some() + } + + fn start(&mut self) -> PduResult> { + if let Some(channel_id) = self.channel_id { + self.channel_processor.start(channel_id) + } else { + Err(other_err!("DynamicVirtualChannel::start", "channel ID not set")) + } } fn process(&mut self, pdu: DrdynvcDataPdu) -> PduResult> { @@ -135,7 +181,7 @@ impl DynamicVirtualChannel { } struct DynamicChannelSet { - channels: BTreeMap, + channels: BTreeMap, name_to_channel_id: BTreeMap, channel_id_to_name: BTreeMap, type_id_to_name: BTreeMap, @@ -152,34 +198,35 @@ impl DynamicChannelSet { } } - fn insert(&mut self, channel: T) -> Option { + fn insert(&mut self, channel: T) -> Option { let name = channel.channel_name().to_owned(); self.type_id_to_name.insert(TypeId::of::(), name.clone()); - self.channels.insert(name, DynamicVirtualChannel::new(channel)) + self.channels.insert(name, DynamicVirtualChannelInternal::new(channel)) } fn attach_channel_id(&mut self, name: DynamicChannelName, id: DynamicChannelId) -> Option { self.channel_id_to_name.insert(id, name.clone()); - self.name_to_channel_id.insert(name, id) + self.name_to_channel_id.insert(name.clone(), id); + let dvc = self.get_by_channel_name_mut(&name)?; + dvc.channel_id = Some(id); + Some(id) } - fn get_by_type_id(&self, type_id: TypeId) -> Option<(&DynamicVirtualChannel, Option)> { - self.type_id_to_name.get(&type_id).and_then(|name| { - self.channels - .get(name) - .map(|channel| (channel, self.name_to_channel_id.get(name).copied())) - }) + fn get_by_type_id(&self, type_id: TypeId) -> Option<&DynamicVirtualChannelInternal> { + self.type_id_to_name + .get(&type_id) + .and_then(|name| self.channels.get(name)) } - fn get_by_channel_name(&self, name: &DynamicChannelName) -> Option<&DynamicVirtualChannel> { + fn get_by_channel_name(&self, name: &DynamicChannelName) -> Option<&DynamicVirtualChannelInternal> { self.channels.get(name) } - fn get_by_channel_name_mut(&mut self, name: &DynamicChannelName) -> Option<&mut DynamicVirtualChannel> { + fn get_by_channel_name_mut(&mut self, name: &DynamicChannelName) -> Option<&mut DynamicVirtualChannelInternal> { self.channels.get_mut(name) } - fn get_by_channel_id_mut(&mut self, id: &DynamicChannelId) -> Option<&mut DynamicVirtualChannel> { + fn get_by_channel_id_mut(&mut self, id: &DynamicChannelId) -> Option<&mut DynamicVirtualChannelInternal> { self.channel_id_to_name .get(id) .and_then(|name| self.channels.get_mut(name)) @@ -195,7 +242,7 @@ impl DynamicChannelSet { } #[inline] - fn values(&self) -> impl Iterator { + fn values(&self) -> impl Iterator { self.channels.values() } } diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index 4ed314053..f5a25b77c 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::ConnectionResult; use ironrdp_displaycontrol::client::DisplayControlClient; -use ironrdp_dvc::{DrdynvcClient, DvcProcessor, DynamicChannelId}; +use ironrdp_dvc::{DrdynvcClient, DvcProcessor, DynamicVirtualChannel}; use ironrdp_graphics::pointer::DecodedPointer; use ironrdp_pdu::geometry::InclusiveRectangle; use ironrdp_pdu::input::fast_path::{FastPathInput, FastPathInputEvent}; @@ -183,7 +183,7 @@ impl ActiveStage { self.x224_processor.get_svc_processor_mut() } - pub fn get_dvc_processor(&mut self) -> Option<(&T, Option)> { + pub fn get_dvc(&mut self) -> Option> { self.x224_processor.get_dvc_processor() } @@ -196,6 +196,10 @@ impl ActiveStage { self.x224_processor.process_svc_processor_messages(messages) } + /// Fully encodes a resize request for sending over the Display Control Virtual Channel. + /// + /// If the Display Control Virtual Channel is not available, or not yet connected, this method + /// will return `None`. pub fn encode_resize( &mut self, width: u16, @@ -203,9 +207,17 @@ impl ActiveStage { scale_factor: Option, physical_dims: Option<(u32, u32)>, ) -> Option>> { - if let Some((display_client, channel_id)) = self.get_dvc_processor::() { - if let Some(channel_id) = channel_id { - let svc_messages = match display_client.encode_single_primary_monitor( + if let Some(dvc) = self.get_dvc::() { + if dvc.is_open() { + let display_control = match dvc.channel_processor_downcast_ref() { + Ok(display_control) => display_control, + Err(e) => return Some(Err(SessionError::pdu(e))), + }; + let channel_id = match dvc.channel_id() { + Ok(channel_id) => channel_id, + Err(e) => return Some(Err(SessionError::pdu(e))), + }; + let svc_messages = match display_control.encode_single_primary_monitor( channel_id, width.into(), height.into(), diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index 752ab90b2..4c24105f1 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -1,6 +1,6 @@ use ironrdp_connector::connection_activation::ConnectionActivationSequence; use ironrdp_connector::legacy::SendDataIndicationCtx; -use ironrdp_dvc::DynamicChannelId; +use ironrdp_dvc::DynamicVirtualChannel; use ironrdp_dvc::{DrdynvcClient, DvcProcessor}; use ironrdp_pdu::mcs::{DisconnectProviderUltimatum, DisconnectReason, McsMessage}; use ironrdp_pdu::rdp::headers::ShareDataPdu; @@ -72,7 +72,7 @@ impl Processor { process_svc_messages(messages.into(), channel_id, self.user_channel_id) } - pub fn get_dvc_processor(&self) -> Option<(&T, Option)> { + pub fn get_dvc_processor(&self) -> Option> { self.get_svc_processor::()? .get_dynamic_channel_by_type_id::() } diff --git a/ffi/src/connector/config.rs b/ffi/src/connector/config.rs index c3804b279..dc55f0ca9 100644 --- a/ffi/src/connector/config.rs +++ b/ffi/src/connector/config.rs @@ -188,6 +188,7 @@ pub mod ffi { autologon: self.autologon.unwrap_or(false), pointer_software_rendering: self.pointer_software_rendering.unwrap_or(false), performance_flags: self.performance_flags.ok_or("performance flag is missing")?, + desktop_scale_factor: 0, }; Ok(Box::new(Config(inner_config))) From 347a2d9201f5e2b2d8e7f828c48dc7ad612c5c0f Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 24 Apr 2024 15:40:13 -0700 Subject: [PATCH 74/84] Makes `max_unacknowledged_frame_count: 20`, see https://github.com/Devolutions/IronRDP/issues/447 --- crates/ironrdp-connector/src/connection_activation.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs index 42b705ed7..06c23f68c 100644 --- a/crates/ironrdp-connector/src/connection_activation.rs +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -365,7 +365,10 @@ fn create_client_confirm_active( })), }])), CapabilitySet::FrameAcknowledge(FrameAcknowledge { - max_unacknowledged_frame_count: 2, + // TODO: Revert this to 2 per FreeRDP. + // This is a temporary hack to fix a resize bug, see: + // https://github.com/Devolutions/IronRDP/issues/447 + max_unacknowledged_frame_count: 20, }), ]); From 8793b165b4467062dd0df675054186316dcc6cce Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 24 Apr 2024 16:04:00 -0700 Subject: [PATCH 75/84] small tweaks --- crates/ironrdp-displaycontrol/src/pdu/mod.rs | 2 -- crates/ironrdp-dvc/src/client.rs | 2 +- crates/ironrdp-session/src/active_stage.rs | 2 +- crates/ironrdp-session/src/x224/mod.rs | 5 ++--- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/ironrdp-displaycontrol/src/pdu/mod.rs b/crates/ironrdp-displaycontrol/src/pdu/mod.rs index 2daec7ecc..e4c8ec6b5 100644 --- a/crates/ironrdp-displaycontrol/src/pdu/mod.rs +++ b/crates/ironrdp-displaycontrol/src/pdu/mod.rs @@ -375,8 +375,6 @@ impl MonitorLayoutEntry { /// - `width` and `height` MUST be >= 200 and <= 8192. /// - `width` SHOULD be even. If it is odd, it will be adjusted /// to the nearest even number by subtracting 1. - /// - `desktop_scale_factor` SHOULD be >= 100 and <= 500. If it is not, - /// it will be adjusted to the nearest valid value. fn new_impl(mut width: u32, height: u32) -> PduResult { if width % 2 != 0 { let prev_width = width; diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 8ad01f750..1b14f7e56 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -61,7 +61,7 @@ impl DrdynvcClient { self } - pub fn get_dynamic_channel_by_type_id(&self) -> Option> + pub fn get_dvc_by_type_id(&self) -> Option> where T: DvcProcessor, { diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index f5a25b77c..28ea04d89 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -184,7 +184,7 @@ impl ActiveStage { } pub fn get_dvc(&mut self) -> Option> { - self.x224_processor.get_dvc_processor() + self.x224_processor.get_dvc() } /// Completes user's SVC request with data, required to sent it over the network and returns diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index 4c24105f1..f9e885bcc 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -72,9 +72,8 @@ impl Processor { process_svc_messages(messages.into(), channel_id, self.user_channel_id) } - pub fn get_dvc_processor(&self) -> Option> { - self.get_svc_processor::()? - .get_dynamic_channel_by_type_id::() + pub fn get_dvc(&self) -> Option> { + self.get_svc_processor::()?.get_dvc_by_type_id::() } /// Processes a received PDU. Returns a vector of [`ProcessorOutput`] that must be processed From 1fd4b38c34777fa52d94dd56e2a112efde4ae62e Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 25 Apr 2024 11:10:58 -0700 Subject: [PATCH 76/84] Gets rid of unneeded DynamicVirtualChannel/DynamicVirtualChannelInternal dichotomy --- crates/ironrdp-dvc/src/client.rs | 6 +- crates/ironrdp-dvc/src/lib.rs | 67 ++++++---------------- crates/ironrdp-session/src/active_stage.rs | 14 ++--- crates/ironrdp-session/src/x224/mod.rs | 2 +- 4 files changed, 26 insertions(+), 63 deletions(-) diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 1b14f7e56..6f7a13186 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -61,13 +61,11 @@ impl DrdynvcClient { self } - pub fn get_dvc_by_type_id(&self) -> Option> + pub fn get_dvc_by_type_id(&self) -> Option<&DynamicVirtualChannel> where T: DvcProcessor, { - Some(DynamicVirtualChannel::::new( - self.dynamic_channels.get_by_type_id(TypeId::of::())?, - )) + self.dynamic_channels.get_by_type_id(TypeId::of::()) } fn create_capabilities_response(&mut self) -> SvcMessage { diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 8625e9136..1dc500ab3 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -98,40 +98,7 @@ pub fn encode_dvc_messages( Ok(res) } -pub struct DynamicVirtualChannel<'a, T: DvcProcessor> { - internal: &'a DynamicVirtualChannelInternal, - _marker: core::marker::PhantomData, -} - -impl<'a, T: DvcProcessor> DynamicVirtualChannel<'a, T> { - pub(crate) fn new(internal: &'a DynamicVirtualChannelInternal) -> Self { - Self { - internal, - _marker: core::marker::PhantomData, - } - } - - pub fn is_open(&self) -> bool { - self.internal.is_open() - } - - pub fn channel_id(&self) -> PduResult { - self.internal - .channel_id - .ok_or_else(|| other_err!("DynamicVirtualChannel::channel_id", "channel ID not set")) - } - - pub fn channel_processor_downcast_ref(&self) -> PduResult<&T> { - self.internal.channel_processor_downcast_ref().ok_or_else(|| { - other_err!( - "DynamicVirtualChannel::channel_processor_downcast_ref", - "downcast failed" - ) - }) - } -} - -struct DynamicVirtualChannelInternal { +pub struct DynamicVirtualChannel { channel_processor: Box, complete_data: CompleteData, /// The channel ID assigned by the server. @@ -140,7 +107,7 @@ struct DynamicVirtualChannelInternal { channel_id: Option, } -impl DynamicVirtualChannelInternal { +impl DynamicVirtualChannel { fn new(handler: T) -> Self { Self { channel_processor: Box::new(handler), @@ -149,10 +116,18 @@ impl DynamicVirtualChannelInternal { } } - fn is_open(&self) -> bool { + pub fn is_open(&self) -> bool { self.channel_id.is_some() } + pub fn channel_id(&self) -> Option { + self.channel_id + } + + pub fn channel_processor_downcast_ref(&self) -> Option<&T> { + self.channel_processor.as_any().downcast_ref() + } + fn start(&mut self) -> PduResult> { if let Some(channel_id) = self.channel_id { self.channel_processor.start(channel_id) @@ -174,14 +149,10 @@ impl DynamicVirtualChannelInternal { fn channel_name(&self) -> &str { self.channel_processor.channel_name() } - - fn channel_processor_downcast_ref(&self) -> Option<&T> { - self.channel_processor.as_any().downcast_ref() - } } struct DynamicChannelSet { - channels: BTreeMap, + channels: BTreeMap, name_to_channel_id: BTreeMap, channel_id_to_name: BTreeMap, type_id_to_name: BTreeMap, @@ -198,10 +169,10 @@ impl DynamicChannelSet { } } - fn insert(&mut self, channel: T) -> Option { + fn insert(&mut self, channel: T) -> Option { let name = channel.channel_name().to_owned(); self.type_id_to_name.insert(TypeId::of::(), name.clone()); - self.channels.insert(name, DynamicVirtualChannelInternal::new(channel)) + self.channels.insert(name, DynamicVirtualChannel::new(channel)) } fn attach_channel_id(&mut self, name: DynamicChannelName, id: DynamicChannelId) -> Option { @@ -212,21 +183,21 @@ impl DynamicChannelSet { Some(id) } - fn get_by_type_id(&self, type_id: TypeId) -> Option<&DynamicVirtualChannelInternal> { + fn get_by_type_id(&self, type_id: TypeId) -> Option<&DynamicVirtualChannel> { self.type_id_to_name .get(&type_id) .and_then(|name| self.channels.get(name)) } - fn get_by_channel_name(&self, name: &DynamicChannelName) -> Option<&DynamicVirtualChannelInternal> { + fn get_by_channel_name(&self, name: &DynamicChannelName) -> Option<&DynamicVirtualChannel> { self.channels.get(name) } - fn get_by_channel_name_mut(&mut self, name: &DynamicChannelName) -> Option<&mut DynamicVirtualChannelInternal> { + fn get_by_channel_name_mut(&mut self, name: &DynamicChannelName) -> Option<&mut DynamicVirtualChannel> { self.channels.get_mut(name) } - fn get_by_channel_id_mut(&mut self, id: &DynamicChannelId) -> Option<&mut DynamicVirtualChannelInternal> { + fn get_by_channel_id_mut(&mut self, id: &DynamicChannelId) -> Option<&mut DynamicVirtualChannel> { self.channel_id_to_name .get(id) .and_then(|name| self.channels.get_mut(name)) @@ -242,7 +213,7 @@ impl DynamicChannelSet { } #[inline] - fn values(&self) -> impl Iterator { + fn values(&self) -> impl Iterator { self.channels.values() } } diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index 28ea04d89..a50a4ba03 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -183,8 +183,8 @@ impl ActiveStage { self.x224_processor.get_svc_processor_mut() } - pub fn get_dvc(&mut self) -> Option> { - self.x224_processor.get_dvc() + pub fn get_dvc(&mut self) -> Option<&DynamicVirtualChannel> { + self.x224_processor.get_dvc::() } /// Completes user's SVC request with data, required to sent it over the network and returns @@ -209,14 +209,8 @@ impl ActiveStage { ) -> Option>> { if let Some(dvc) = self.get_dvc::() { if dvc.is_open() { - let display_control = match dvc.channel_processor_downcast_ref() { - Ok(display_control) => display_control, - Err(e) => return Some(Err(SessionError::pdu(e))), - }; - let channel_id = match dvc.channel_id() { - Ok(channel_id) => channel_id, - Err(e) => return Some(Err(SessionError::pdu(e))), - }; + let display_control = dvc.channel_processor_downcast_ref::()?; + let channel_id = dvc.channel_id().unwrap(); // Safe to unwrap, as we checked if the channel is open let svc_messages = match display_control.encode_single_primary_monitor( channel_id, width.into(), diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index f9e885bcc..86a64dfc6 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -72,7 +72,7 @@ impl Processor { process_svc_messages(messages.into(), channel_id, self.user_channel_id) } - pub fn get_dvc(&self) -> Option> { + pub fn get_dvc(&self) -> Option<&DynamicVirtualChannel> { self.get_svc_processor::()?.get_dvc_by_type_id::() } From a07b8ec67a04653b168e1bd24051563d61995fb7 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 25 Apr 2024 11:30:51 -0700 Subject: [PATCH 77/84] Adds documentation to MonitorLayoutEntry public create methods --- crates/ironrdp-displaycontrol/src/pdu/mod.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/ironrdp-displaycontrol/src/pdu/mod.rs b/crates/ironrdp-displaycontrol/src/pdu/mod.rs index e4c8ec6b5..1f8ad72a3 100644 --- a/crates/ironrdp-displaycontrol/src/pdu/mod.rs +++ b/crates/ironrdp-displaycontrol/src/pdu/mod.rs @@ -401,14 +401,22 @@ impl MonitorLayoutEntry { }) } - /// Creates a new primary monitor layout entry. + /// Creates a new primary [`MonitorLayoutEntry`]. + /// + /// - `width` and `height` MUST be >= 200 and <= 8192. + /// - `width` SHOULD be even. If it is odd, it will be adjusted + /// to the nearest even number by subtracting 1. pub fn new_primary(width: u32, height: u32) -> PduResult { let mut entry = Self::new_impl(width, height)?; entry.is_primary = true; Ok(entry) } - /// Creates a new secondary monitor layout entry. + /// Creates a new secondary [`MonitorLayoutEntry`]. + /// + /// - `width` and `height` MUST be >= 200 and <= 8192. + /// - `width` SHOULD be even. If it is odd, it will be adjusted + /// to the nearest even number by subtracting 1. pub fn new_secondary(width: u32, height: u32) -> PduResult { Self::new_impl(width, height) } From 6a7c4f8405c0c87f118daa97b211093910ee3718 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 25 Apr 2024 11:30:59 -0700 Subject: [PATCH 78/84] return old id --- crates/ironrdp-dvc/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index 1dc500ab3..730b9ed03 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -179,8 +179,9 @@ impl DynamicChannelSet { self.channel_id_to_name.insert(id, name.clone()); self.name_to_channel_id.insert(name.clone(), id); let dvc = self.get_by_channel_name_mut(&name)?; + let old_id = dvc.channel_id; dvc.channel_id = Some(id); - Some(id) + old_id } fn get_by_type_id(&self, type_id: TypeId) -> Option<&DynamicVirtualChannel> { From bda1a96175175ea58f02e04fea89d12bc8a6d411 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 25 Apr 2024 12:47:37 -0700 Subject: [PATCH 79/84] Removes automatic display adjustment and adds MonitorLayoutEntry::adjust_display_size --- crates/ironrdp-client/src/rdp.rs | 7 +- crates/ironrdp-displaycontrol/src/client.rs | 11 +++ crates/ironrdp-displaycontrol/src/pdu/mod.rs | 72 ++++++++++++++++---- crates/ironrdp-session/src/active_stage.rs | 15 +++- 4 files changed, 87 insertions(+), 18 deletions(-) diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index e314874ce..50c3b82fe 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -2,6 +2,7 @@ use ironrdp::cliprdr::backend::{ClipboardMessage, CliprdrBackendFactory}; use ironrdp::connector::connection_activation::ConnectionActivationState; use ironrdp::connector::{ConnectionResult, ConnectorResult}; use ironrdp::displaycontrol::client::DisplayControlClient; +use ironrdp::displaycontrol::pdu::MonitorLayoutEntry; use ironrdp::graphics::image_processing::PixelFormat; use ironrdp::pdu::input::fast_path::FastPathInputEvent; use ironrdp::pdu::write_buf::WriteBuf; @@ -200,13 +201,15 @@ async fn active_session( } info!(width, height, "resize event"); + let (width, height) = MonitorLayoutEntry::adjust_display_size(width.into(), height.into()); + debug!(width, height, "Adjusted display size"); - if let Some(response_frame) = active_stage.encode_resize(width, height, None, Some((width.into(), height.into()))) { // Set physical width and height to the same as the pixel width and heighbbt per FreeRDP + if let Some(response_frame) = active_stage.encode_resize(width, height, None, Some((width, height))) { // Set physical width and height to the same as the pixel width and heighbbt per FreeRDP vec![ActiveStageOutput::ResponseFrame(response_frame?)] } else { // TODO(#271): use the "auto-reconnect cookie": https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/15b0d1c9-2891-4adb-a45e-deb4aeeeab7c debug!("Reconnecting with new size"); - return Ok(RdpControlFlow::ReconnectWithNewSize { width, height }) + return Ok(RdpControlFlow::ReconnectWithNewSize { width: width.try_into().unwrap(), height: height.try_into().unwrap() }) } }, RdpInputEvent::FastPath(events) => { diff --git a/crates/ironrdp-displaycontrol/src/client.rs b/crates/ironrdp-displaycontrol/src/client.rs index e967c4581..75b086a46 100644 --- a/crates/ironrdp-displaycontrol/src/client.rs +++ b/crates/ironrdp-displaycontrol/src/client.rs @@ -37,6 +37,17 @@ impl DisplayControlClient { /// Builds a [`DisplayControlPdu::MonitorLayout`] with a single primary monitor /// with the given `width` and `height`, and wraps it as an [`SvcMessage`]. + /// + /// Per [2.2.2.2.1]: + /// - The `width` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels, and MUST NOT be an odd value. + /// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels. + /// - The `scale_factor` MUST be ignored if it is less than 100 percent or greater than 500 percent. + /// - The `physical_dims` (width, height) MUST be ignored if either is less than 10 mm or greater than 10,000 mm. + /// + /// Use [`crate::pdu::MonitorLayoutEntry::adjust_display_size`] to adjust `width` and `height` before calling this function + /// to ensure the display size is within the valid range. + /// + /// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c pub fn encode_single_primary_monitor( &self, channel_id: u32, diff --git a/crates/ironrdp-displaycontrol/src/pdu/mod.rs b/crates/ironrdp-displaycontrol/src/pdu/mod.rs index 1f8ad72a3..6df6aeec8 100644 --- a/crates/ironrdp-displaycontrol/src/pdu/mod.rs +++ b/crates/ironrdp-displaycontrol/src/pdu/mod.rs @@ -236,11 +236,17 @@ impl DisplayControlMonitorLayout { } /// Creates a new [`DisplayControlMonitorLayout`] with a single primary monitor - /// with the given `width` and `height`. `width` and `height` MUST be >= 200 and <= 8192, - /// and if `width` is odd, it will be adjusted to the nearest even number by subtracting 1. /// - /// - If `scale_factor` is provided, it MUST be in the valid range (100..=500 percent). - /// - If `physical_dims` are provided, they MUST be in the valid range (10..=10000 millimeters). + /// Per [2.2.2.2.1]: + /// - The `width` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels, and MUST NOT be an odd value. + /// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels. + /// - The `scale_factor` MUST be ignored if it is less than 100 percent or greater than 500 percent. + /// - The `physical_dims` (width, height) MUST be ignored if either is less than 10 mm or greater than 10,000 mm. + /// + /// Use [`MonitorLayoutEntry::adjust_display_size`] to adjust `width` and `height` before calling this function + /// to ensure the display size is within the valid range. + /// + /// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c pub fn new_single_primary_monitor( width: u32, height: u32, @@ -372,9 +378,11 @@ impl MonitorLayoutEntry { /// Creates a new [`MonitorLayoutEntry`]. /// - /// - `width` and `height` MUST be >= 200 and <= 8192. - /// - `width` SHOULD be even. If it is odd, it will be adjusted - /// to the nearest even number by subtracting 1. + /// Per [2.2.2.2.1]: + /// - The `width` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels, and MUST NOT be an odd value. + /// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels. + /// + /// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c fn new_impl(mut width: u32, height: u32) -> PduResult { if width % 2 != 0 { let prev_width = width; @@ -401,22 +409,58 @@ impl MonitorLayoutEntry { }) } + /// Adjusts the display size to be within the valid range. + /// + /// Per [2.2.2.2.1]: + /// - The `width` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels, and MUST NOT be an odd value. + /// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels. + /// + /// Functions that create [`MonitorLayoutEntry`] should typically use this function to adjust the display size first. + /// + /// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c + pub fn adjust_display_size(width: u32, height: u32) -> (u32, u32) { + fn constrain(value: u32) -> u32 { + if value < 200 { + 200 + } else if value > 8192 { + 8192 + } else { + value + } + } + + let mut width = width; + if width % 2 != 0 { + width = width.saturating_sub(1); + } + + (constrain(width), constrain(height)) + } + /// Creates a new primary [`MonitorLayoutEntry`]. /// - /// - `width` and `height` MUST be >= 200 and <= 8192. - /// - `width` SHOULD be even. If it is odd, it will be adjusted - /// to the nearest even number by subtracting 1. + /// Per [2.2.2.2.1]: + /// - The `width` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels, and MUST NOT be an odd value. + /// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels. + /// + /// Use [`MonitorLayoutEntry::adjust_display_size`] before calling this function to ensure the display size is within the valid range. + /// + /// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c pub fn new_primary(width: u32, height: u32) -> PduResult { let mut entry = Self::new_impl(width, height)?; entry.is_primary = true; Ok(entry) } - /// Creates a new secondary [`MonitorLayoutEntry`]. + /// Creates a new primary [`MonitorLayoutEntry`]. + /// + /// Per [2.2.2.2.1]: + /// - The `width` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels, and MUST NOT be an odd value. + /// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels. + /// + /// Use [`MonitorLayoutEntry::adjust_display_size`] before calling this function to ensure the display size is within the valid range. /// - /// - `width` and `height` MUST be >= 200 and <= 8192. - /// - `width` SHOULD be even. If it is odd, it will be adjusted - /// to the nearest even number by subtracting 1. + /// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c pub fn new_secondary(width: u32, height: u32) -> PduResult { Self::new_impl(width, height) } diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index a50a4ba03..770bec9ef 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -200,10 +200,21 @@ impl ActiveStage { /// /// If the Display Control Virtual Channel is not available, or not yet connected, this method /// will return `None`. + /// + /// Per [2.2.2.2.1]: + /// - The `width` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels, and MUST NOT be an odd value. + /// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels. + /// - The `scale_factor` MUST be ignored if it is less than 100 percent or greater than 500 percent. + /// - The `physical_dims` (width, height) MUST be ignored if either is less than 10 mm or greater than 10,000 mm. + /// + /// Use [`ironrdp_displaycontrol::pdu::MonitorLayoutEntry::adjust_display_size`] to adjust `width` and `height` before calling this function + /// to ensure the display size is within the valid range. + /// + /// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c pub fn encode_resize( &mut self, - width: u16, - height: u16, + width: u32, + height: u32, scale_factor: Option, physical_dims: Option<(u32, u32)>, ) -> Option>> { From 4d878fbcf6608dcb19161ea62e2f36cffd8a28eb Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 25 Apr 2024 12:52:36 -0700 Subject: [PATCH 80/84] reverting rdpversion --- crates/ironrdp-connector/src/connection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index f03c304c8..8c11f1c5b 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -625,7 +625,7 @@ fn create_gcc_blocks<'a>( ClientGccBlocks { core: ClientCoreData { - version: RdpVersion::V10_12, + version: RdpVersion::V5_PLUS, desktop_width: config.desktop_size.width, desktop_height: config.desktop_size.height, color_depth: ColorDepth::Bpp8, // ignored because we use the optional core data below From 1644ff717ae8294f9f155b4e510bbf9024aa2467 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 25 Apr 2024 13:04:59 -0700 Subject: [PATCH 81/84] add TODO --- crates/ironrdp-displaycontrol/src/client.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ironrdp-displaycontrol/src/client.rs b/crates/ironrdp-displaycontrol/src/client.rs index 75b086a46..b53885304 100644 --- a/crates/ironrdp-displaycontrol/src/client.rs +++ b/crates/ironrdp-displaycontrol/src/client.rs @@ -56,6 +56,7 @@ impl DisplayControlClient { scale_factor: Option, physical_dims: Option<(u32, u32)>, ) -> PduResult> { + // TODO: prevent resolution with values greater than max monitor area received in caps. let pdu: DisplayControlPdu = DisplayControlMonitorLayout::new_single_primary_monitor(width, height, scale_factor, physical_dims)?.into(); debug!(?pdu, "Sending monitor layout"); From a40adbbe542033e7561722067fc705704783e66b Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 25 Apr 2024 13:08:12 -0700 Subject: [PATCH 82/84] fixing lints --- crates/ironrdp-session/src/active_stage.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ironrdp-session/src/active_stage.rs b/crates/ironrdp-session/src/active_stage.rs index 770bec9ef..12000c80a 100644 --- a/crates/ironrdp-session/src/active_stage.rs +++ b/crates/ironrdp-session/src/active_stage.rs @@ -224,8 +224,8 @@ impl ActiveStage { let channel_id = dvc.channel_id().unwrap(); // Safe to unwrap, as we checked if the channel is open let svc_messages = match display_control.encode_single_primary_monitor( channel_id, - width.into(), - height.into(), + width, + height, scale_factor, physical_dims, ) { From ab2a8a38e121ec487e2b4db830678cc0620b455a Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Fri, 26 Apr 2024 16:20:48 -0500 Subject: [PATCH 83/84] Update crates/ironrdp-connector/src/connection_activation.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: BenoĆ®t Cortier --- crates/ironrdp-connector/src/connection_activation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs index 06c23f68c..708931465 100644 --- a/crates/ironrdp-connector/src/connection_activation.rs +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -365,7 +365,7 @@ fn create_client_confirm_active( })), }])), CapabilitySet::FrameAcknowledge(FrameAcknowledge { - // TODO: Revert this to 2 per FreeRDP. + // FIXME(#447): Revert this to 2 per FreeRDP. // This is a temporary hack to fix a resize bug, see: // https://github.com/Devolutions/IronRDP/issues/447 max_unacknowledged_frame_count: 20, From 09daceb089408e825be55a32c55c7237e052b36a Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Fri, 26 Apr 2024 14:23:33 -0700 Subject: [PATCH 84/84] Removing unneeded Result --- crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs | 12 ++++++------ crates/ironrdp-server/src/encoder/rfx.rs | 4 ++-- crates/ironrdp-testsuite-core/tests/pdu/rfx.rs | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs b/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs index a0faf0c81..98c650f6c 100644 --- a/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs +++ b/crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs @@ -170,8 +170,8 @@ pub struct RfxChannel { pub struct RfxChannelWidth(i16); impl RfxChannelWidth { - pub fn new(value: i16) -> Result { - Ok(Self(value)) + pub fn new(value: i16) -> Self { + Self(value) } pub fn as_u16(self) -> u16 { @@ -189,8 +189,8 @@ impl RfxChannelWidth { pub struct RfxChannelHeight(i16); impl RfxChannelHeight { - pub fn new(value: i16) -> Result { - Ok(Self(value)) + pub fn new(value: i16) -> Self { + Self(value) } pub fn as_u16(self) -> u16 { @@ -212,10 +212,10 @@ impl PduBufferParsing<'_> for RfxChannel { } let width = buffer.read_i16::()?; - let width = RfxChannelWidth::new(width)?; + let width = RfxChannelWidth::new(width); let height = buffer.read_i16::()?; - let height = RfxChannelHeight::new(height)?; + let height = RfxChannelHeight::new(height); Ok(Self { width, height }) } diff --git a/crates/ironrdp-server/src/encoder/rfx.rs b/crates/ironrdp-server/src/encoder/rfx.rs index 989339faa..3a8a981f5 100644 --- a/crates/ironrdp-server/src/encoder/rfx.rs +++ b/crates/ironrdp-server/src/encoder/rfx.rs @@ -37,8 +37,8 @@ impl RfxEncoder { }; let context = rfx::Headers::Context(context); let channels = rfx::ChannelsPdu(vec![RfxChannel { - width: RfxChannelWidth::new(width).map_err(|e| custom_err!("width", e))?, - height: RfxChannelHeight::new(height).map_err(|e| custom_err!("height", e))?, + width: RfxChannelWidth::new(width), + height: RfxChannelHeight::new(height), }]); let channels = rfx::Headers::Channels(channels); let version = rfx::CodecVersionsPdu; diff --git a/crates/ironrdp-testsuite-core/tests/pdu/rfx.rs b/crates/ironrdp-testsuite-core/tests/pdu/rfx.rs index a621af286..7d183e107 100644 --- a/crates/ironrdp-testsuite-core/tests/pdu/rfx.rs +++ b/crates/ironrdp-testsuite-core/tests/pdu/rfx.rs @@ -253,8 +253,8 @@ const FRAME_END_PDU: FrameEndPdu = FrameEndPdu; lazy_static::lazy_static! { static ref CHANNELS_PDU: ChannelsPdu = ChannelsPdu(vec![ - RfxChannel { width: RfxChannelWidth::new(64).unwrap(), height: RfxChannelHeight::new(64).unwrap() }, - RfxChannel { width: RfxChannelWidth::new(32).unwrap(), height: RfxChannelHeight::new(32).unwrap() } + RfxChannel { width: RfxChannelWidth::new(64), height: RfxChannelHeight::new(64) }, + RfxChannel { width: RfxChannelWidth::new(32), height: RfxChannelHeight::new(32) } ]); static ref REGION_PDU: RegionPdu = RegionPdu { rectangles: vec![