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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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 83be7c8aba0b1e7b8cf899501a0f836900f29dcc Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 21 Mar 2024 13:30:09 -0600 Subject: [PATCH 44/53] 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 f4658d9d0dc0f0650202c135c28c01b66e76679c Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Wed, 20 Mar 2024 12:18:43 -0700 Subject: [PATCH 45/53] 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 46/53] 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 47/53] 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 48/53] 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 49/53] 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 50/53] 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 51/53] 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 52/53] 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 e38f2040c827b864928a6e25f29da8fab67cde7c Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 26 Mar 2024 13:49:14 -0500 Subject: [PATCH 53/53] updates fuzz/Cargo.lock --- fuzz/Cargo.lock | 60 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index dad7bae23..a37ff122a 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -57,9 +57,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "base64ct" @@ -81,9 +81,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bitvec" @@ -195,7 +195,7 @@ checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -206,7 +206,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -227,7 +227,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -275,7 +275,7 @@ dependencies = [ name = "ironrdp-cliprdr" version = "0.1.0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "ironrdp-pdu", "ironrdp-svc", "thiserror", @@ -295,7 +295,20 @@ dependencies = [ name = "ironrdp-displaycontrol" version = "0.1.0" dependencies = [ + "ironrdp-dvc", "ironrdp-pdu", + "ironrdp-svc", + "tracing", +] + +[[package]] +name = "ironrdp-dvc" +version = "0.1.0" +dependencies = [ + "ironrdp-pdu", + "ironrdp-svc", + "slab", + "tracing", ] [[package]] @@ -329,7 +342,7 @@ name = "ironrdp-graphics" version = "0.1.0" dependencies = [ "bit_field", - "bitflags 2.4.2", + "bitflags 2.5.0", "bitvec", "byteorder", "ironrdp-error", @@ -345,7 +358,7 @@ name = "ironrdp-pdu" version = "0.1.0" dependencies = [ "bit_field", - "bitflags 2.4.2", + "bitflags 2.5.0", "byteorder", "der-parser", "ironrdp-error", @@ -365,7 +378,7 @@ dependencies = [ name = "ironrdp-rdpdr" version = "0.1.0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "ironrdp-error", "ironrdp-pdu", "ironrdp-svc", @@ -376,7 +389,7 @@ dependencies = [ name = "ironrdp-svc" version = "0.1.0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "ironrdp-pdu", ] @@ -479,7 +492,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -585,6 +598,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "spki" version = "0.7.3" @@ -608,9 +630,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", @@ -652,7 +674,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -673,7 +695,7 @@ checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -696,7 +718,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -770,5 +792,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ]