diff --git a/Cargo.lock b/Cargo.lock index 7c79cecd3..d3f9f463a 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", @@ -1769,7 +1770,10 @@ dependencies = [ name = "ironrdp-displaycontrol" version = "0.1.0" dependencies = [ + "ironrdp-dvc", "ironrdp-pdu", + "ironrdp-svc", + "tracing", ] [[package]] @@ -1899,6 +1903,7 @@ dependencies = [ "ironrdp-acceptor", "ironrdp-ainput", "ironrdp-cliprdr", + "ironrdp-displaycontrol", "ironrdp-dvc", "ironrdp-graphics", "ironrdp-pdu", @@ -1913,8 +1918,8 @@ dependencies = [ name = "ironrdp-session" version = "0.1.0" dependencies = [ - "bitflags 2.4.2", "ironrdp-connector", + "ironrdp-dvc", "ironrdp-error", "ironrdp-graphics", "ironrdp-pdu", @@ -1946,6 +1951,7 @@ dependencies = [ "ironrdp-cliprdr-format", "ironrdp-connector", "ironrdp-displaycontrol", + "ironrdp-dvc", "ironrdp-fuzzing", "ironrdp-graphics", "ironrdp-input", diff --git a/Cargo.toml b/Cargo.toml index a79ce9f7c..9895d9bd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,15 +51,22 @@ 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" [profile.dev] opt-level = 1 diff --git a/crates/ironrdp-ainput/Cargo.toml b/crates/ironrdp-ainput/Cargo.toml index 542a29842..6ef58e3e8 100644 --- a/crates/ironrdp-ainput/Cargo.toml +++ b/crates/ironrdp-ainput/Cargo.toml @@ -16,8 +16,9 @@ doctest = false test = false [dependencies] +ironrdp-dvc.workspace = true ironrdp-pdu.workspace = true bitflags.workspace = true -num-derive = "0.4" -num-traits = "0.2" +num-derive.workspace = true # TODO: remove +num-traits.workspace = true # TODO: remove diff --git a/crates/ironrdp-ainput/src/lib.rs b/crates/ironrdp-ainput/src/lib.rs index fa50a1fa4..e13bec0b2 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 _}; @@ -136,6 +137,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-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 46f0db754..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)); @@ -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-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-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-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-displaycontrol/src/client.rs b/crates/ironrdp-displaycontrol/src/client.rs new file mode 100644 index 000000000..54a1d6480 --- /dev/null +++ b/crates/ironrdp-displaycontrol/src/client.rs @@ -0,0 +1,50 @@ +use crate::{ + pdu::{DisplayControlMonitorLayout, DisplayControlPdu, MonitorLayoutEntry}, + CHANNEL_NAME, +}; +use ironrdp_dvc::{encode_dvc_messages, DvcClientProcessor, DvcMessage, DvcProcessor}; +use ironrdp_pdu::PduResult; +use ironrdp_svc::{impl_as_any, ChannelFlags, SvcMessage}; +use tracing::debug; + +/// 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 pdu: DisplayControlPdu = DisplayControlMonitorLayout::new(&monitors)?.into(); + encode_dvc_messages(channel_id, vec![Box::new(pdu)], ChannelFlags::empty()) + } +} + +impl Default for DisplayControlClient { + fn default() -> Self { + Self::new() + } +} 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-displaycontrol/src/server.rs b/crates/ironrdp-displaycontrol/src/server.rs new file mode 100644 index 000000000..dc1796caf --- /dev/null +++ b/crates/ironrdp-displaycontrol/src/server.rs @@ -0,0 +1,40 @@ +use ironrdp_dvc::{DvcMessage, DvcProcessor, DvcServerProcessor}; +use ironrdp_pdu::{decode, PduResult}; +use ironrdp_svc::impl_as_any; +use tracing::debug; + +use crate::{ + pdu::{DisplayControlCapabilities, 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 = DisplayControlCapabilities::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::new()) + } +} + +impl DvcServerProcessor for DisplayControlServer {} diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 3e5b8f42d..096a4df1f 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -1,20 +1,18 @@ -use alloc::borrow::ToOwned; -use alloc::boxed::Box; -use alloc::collections::BTreeMap; -use alloc::string::String; +use crate::pdu::{ + CapabilitiesResponsePdu, CapsVersion, ClosePdu, CreateResponsePdu, CreationStatus, DrdynvcClientPdu, + DrdynvcServerPdu, +}; +use crate::{encode_dvc_messages, DvcProcessor, DynamicChannelId, DynamicChannelSet}; use alloc::vec::Vec; -use core::any::Any; +use core::any::TypeId; use core::fmt; - use ironrdp_pdu as pdu; - -use ironrdp_svc::{impl_as_any, CompressionCondition, SvcClientProcessor, SvcMessage, SvcProcessor}; -use pdu::cursor::{ReadCursor, WriteCursor}; +use ironrdp_svc::{impl_as_any, ChannelFlags, CompressionCondition, SvcClientProcessor, SvcMessage, SvcProcessor}; +use pdu::cursor::ReadCursor; use pdu::gcc::ChannelName; -use pdu::rdp::vc; -use pdu::{decode, dvc, PduEncode, PduResult}; - -use crate::DvcProcessor; +use pdu::other_err; +use pdu::PduDecode as _; +use pdu::PduResult; pub trait DvcClientProcessor: DvcProcessor {} @@ -22,7 +20,9 @@ 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, } impl fmt::Debug for DrdynvcClient { @@ -45,7 +45,8 @@ impl DrdynvcClient { pub fn new() -> Self { Self { - dynamic_channels: BTreeMap::new(), + dynamic_channels: DynamicChannelSet::new(), + cap_handshake_done: false, } } @@ -54,12 +55,31 @@ impl DrdynvcClient { #[must_use] pub fn with_dynamic_channel(mut self, channel: T) -> Self where - T: DvcClientProcessor + 'static, + T: DvcProcessor + 'static, { - let channel_name = channel.channel_name().to_owned(); - self.dynamic_channels.insert(channel_name, Box::new(channel)); + self.dynamic_channels.insert(channel); self } + + 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_id)| { + channel + .channel_processor_downcast_ref() + .map(|channel| (channel as &T, channel_id)) + }) + } + + fn create_capabilities_response(&mut self) -> SvcMessage { + 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) + } } impl_as_any!(DrdynvcClient); @@ -80,168 +100,76 @@ 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:?}"); - 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) => { + DrdynvcServerPdu::Create(create_request) => { debug!("Got DVC Create Request PDU: {create_request:?}"); - - // 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_name = create_request.channel_name; + let channel_id = create_request.channel_id; + + 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 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(); + (CreationStatus::OK, dynamic_channel.start(channel_id)?) + } else { + (CreationStatus::NO_LISTENER, Vec::new()) + }; + + 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)); + + // If this DVC has start messages, send them. + if !start_messages.is_empty() { + responses.extend(encode_dvc_messages(channel_id, start_messages, ChannelFlags::empty())?); + } } - 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 = 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) => { - 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 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::Data(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, - // )?; - // } + 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(data)?; + + responses.extend(encode_dvc_messages(channel_id, messages, ChannelFlags::empty())?); } } - Err(ironrdp_pdu::other_err!( - "DRDYNVC", - "ironrdp-dvc::DrdynvcClient implementation is not yet ready" - )) - } - - fn is_drdynvc(&self) -> bool { - true + Ok(responses) } } impl SvcClientProcessor for DrdynvcClient {} -struct DynamicChannelCtx<'a> { - dvc_pdu: vc::dvc::ServerPdu, - 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(); - - // [ vc::ChannelPduHeader | … - let channel_header: vc::ChannelPduHeader = decode(user_data)?; - debug_assert_eq!(user_data_len, channel_header.length as usize); - - // … | dvc::ServerPdu | … - let mut cursor = ReadCursor::new(user_data); - let dvc_pdu = vc::dvc::ServerPdu::decode(&mut cursor, user_data_len)?; - - // … | DvcData ] - let dvc_data = cursor.remaining(); - - 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 ba20e20dc..b90e5f3f4 100644 --- a/crates/ironrdp-dvc/src/complete_data.rs +++ b/crates/ironrdp-dvc/src/complete_data.rs @@ -1,5 +1,8 @@ use alloc::vec::Vec; use core::cmp; +use ironrdp_pdu::{cast_length, invalid_message_err, PduResult}; + +use crate::pdu::{DataFirstPdu, DataPdu, DrdynvcDataPdu}; #[derive(Debug, PartialEq)] pub(crate) struct CompleteData { @@ -15,57 +18,61 @@ 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: DrdynvcDataPdu) -> PduResult>> { + match pdu { + 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, 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) } } - pub(crate) 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) - } else { - // message is fragmented so need to reassemble it - match self.data.len().checked_add(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 - } - 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(); + return Ok(Some(data.data)); + } - None - } + // 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) } - } - _ => { - error!("DVC message size overflow occurred"); - None } } + _ => Err(invalid_message_err!("DVC message", "data", "overflow occurred")), } } } diff --git a/crates/ironrdp-dvc/src/lib.rs b/crates/ironrdp-dvc/src/lib.rs index ced6247c8..4e52bdc30 100644 --- a/crates/ironrdp-dvc/src/lib.rs +++ b/crates/ironrdp-dvc/src/lib.rs @@ -1,21 +1,23 @@ #![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] extern crate tracing; extern crate alloc; +use alloc::string::String; +use core::any::TypeId; +use pdu::DrdynvcDataPdu; + use alloc::boxed::Box; +use alloc::collections::BTreeMap; 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 pdu::write_buf::WriteBuf; -use pdu::{assert_obj_safe, PduEncode, PduResult}; +pub use ironrdp_pdu; +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; @@ -26,7 +28,13 @@ pub use client::*; mod server; pub use server::*; -pub type DvcMessages = Vec>; +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 +/// (being split into multiple of such PDUs if necessary). +pub trait DvcPduEncode: PduEncode + Send {} +pub type DvcMessage = Box; /// A type that is a Dynamic Virtual Channel (DVC) /// @@ -34,14 +42,163 @@ 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 { + /// The name of the channel, e.g. "Microsoft::Windows::RDS::DisplayControl" fn channel_name(&self) -> &str; - fn start(&mut self, _channel_id: u32) -> PduResult; + /// 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; + fn process(&mut self, channel_id: u32, payload: &[u8]) -> PduResult>; fn close(&mut self, _channel_id: u32) {} } assert_obj_safe!(DvcProcessor); + +pub fn encode_dvc_messages( + channel_id: u32, + messages: Vec, + flags: ironrdp_svc::ChannelFlags, +) -> PduResult> { + let mut res = Vec::new(); + for msg in messages { + let total_length = msg.size(); + let needs_splitting = total_length >= DrdynvcDataPdu::MAX_DATA_SIZE; + + let msg = encode_vec(msg.as_ref())?; + let mut off = 0; + + while off < total_length { + let first = off == 0; + let rem = total_length.checked_sub(off).unwrap(); + 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"))?; + + let pdu = if needs_splitting && first { + pdu::DrdynvcDataPdu::DataFirst(pdu::DataFirstPdu::new( + channel_id, + cast_length!("total_length", total_length)?, + msg[off..end].to_vec(), + )) + } else { + pdu::DrdynvcDataPdu::Data(pdu::DataPdu::new(channel_id, msg[off..end].to_vec())) + }; + + let svc = SvcMessage::from(pdu).with_flags(flags); + + res.push(svc); + off = end; + } + } + + 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: DrdynvcDataPdu) -> PduResult> { + let channel_id = pdu.channel_id(); + 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 { + Ok(Vec::new()) + } + } + + fn channel_name(&self) -> &str { + self.channel_processor.channel_name() + } + + fn channel_processor_downcast_ref(&self) -> Option<&T> { + self.channel_processor.as_any().downcast_ref() + } +} + +struct DynamicChannelSet { + channels: BTreeMap, + 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)) + } + + 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) + } + + fn get_by_type_id(&self, type_id: TypeId) -> Option<(&DynamicVirtualChannel, Option)> { + self.type_id_to_name.get(&type_id).and_then(|name| { + self.channels + .get(name) + .map(|channel| (channel, self.name_to_channel_id.get(name).copied())) + }) + } + + fn get_by_channel_name(&self, name: &DynamicChannelName) -> Option<&DynamicVirtualChannel> { + self.channels.get(name) + } + + fn get_by_channel_name_mut(&mut self, name: &DynamicChannelName) -> Option<&mut DynamicVirtualChannel> { + self.channels.get_mut(name) + } + + fn get_by_channel_id_mut(&mut self, id: &DynamicChannelId) -> Option<&mut DynamicVirtualChannel> { + self.channel_id_to_name + .get(id) + .and_then(|name| self.channels.get_mut(name)) + } + + 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] + 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..9720ca035 --- /dev/null +++ b/crates/ironrdp-dvc/src/pdu.rs @@ -0,0 +1,849 @@ +use alloc::format; +use core::fmt; + +use crate::{DynamicChannelId, String, Vec}; +use ironrdp_pdu::{ + cast_length, + cursor::{ReadCursor, WriteCursor}, + ensure_fixed_part_size, ensure_size, invalid_message_err, unsupported_pdu_err, + 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; + +/// Dynamic Virtual Channel PDU's that are sent by both client and server. +#[derive(Debug, PartialEq)] +pub enum DrdynvcDataPdu { + DataFirst(DataFirstPdu), + Data(DataPdu), +} + +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, + 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(_) => DataFirstPdu::name(), + DrdynvcDataPdu::Data(_) => DataPdu::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, PartialEq)] +pub enum DrdynvcClientPdu { + Capabilities(CapabilitiesResponsePdu), + Create(CreateResponsePdu), + Close(ClosePdu), + Data(DrdynvcDataPdu), +} + +impl PduEncode for DrdynvcClientPdu { + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + match self { + 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 { + DrdynvcClientPdu::Capabilities(_) => CapabilitiesResponsePdu::name(), + DrdynvcClientPdu::Create(_) => CreateResponsePdu::name(), + DrdynvcClientPdu::Data(pdu) => pdu.name(), + DrdynvcClientPdu::Close(_) => ClosePdu::name(), + } + } + + fn size(&self) -> usize { + match self { + DrdynvcClientPdu::Capabilities(_) => CapabilitiesResponsePdu::size(), + DrdynvcClientPdu::Create(pdu) => pdu.size(), + DrdynvcClientPdu::Data(pdu) => pdu.size(), + DrdynvcClientPdu::Close(pdu) => pdu.size(), + } + } +} + +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, PartialEq)] +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(_) => CreateRequestPdu::name(), + DrdynvcServerPdu::Close(_) => ClosePdu::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, PartialEq)] +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. + fn new(cb_id_val: u32, sp_val: u32, cmd: Cmd) -> Self { + Self { + cb_id: FieldType::for_val(cb_id_val), + sp: FieldType::for_val(sp_val), + cmd, + } + } + + 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)); + Ok(()) + } + + fn decode(src: &mut ReadCursor<'_>) -> PduResult { + ensure_size!(in: src, size: Self::size()); + let byte = src.read_u8(); + let cmd = Cmd::try_from(byte >> 4)?; + let sp = FieldType::from((byte >> 2) & 0b11); + let cb_id = FieldType::from(byte & 0b11); + Ok(Self { cb_id, sp, cmd }) + } + + fn size() -> usize { + Self::FIXED_PART_SIZE + } +} + +/// [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(Debug, Copy, Clone, PartialEq)] +enum Cmd { + Create = 0x01, + DataFirst = 0x02, + Data = 0x03, + Close = 0x04, + Capability = 0x05, + DataFirstCompressed = 0x06, + DataCompressed = 0x07, + SoftSyncRequest = 0x08, + 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 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(cmd: Cmd) -> Self { + format!("{:?}", 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, PartialEq)] +pub struct DataFirstPdu { + header: Header, + 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. + pub length: u32, + /// Data is just the data to be sent in this PDU. + pub 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: u32, data: Vec) -> Self { + Self { + header: Header::new(channel_id, total_length, Cmd::DataFirst), + channel_id, + length: total_length, + data, + } + } + + #[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); + 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_val(self.channel_id, dst)?; + self.header + .sp + .encode_val(cast_length!("DataFirstPdu::Length", self.length)?, dst)?; + dst.write_slice(&self.data); + Ok(()) + } + + fn name() -> &'static str { + "DYNVC_DATA_FIRST" + } + + fn size(&self) -> usize { + checked_sum_or_panic(&[ + Header::size(), + self.header.cb_id.size_of_val(), + self.header.sp.size_of_val(), + self.data.len(), + ]) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct FieldType(u8); + +impl FieldType { + 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 { + 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(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")), + } + } + + /// 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, + _ => 0, + } + } + + fn for_val(value: u32) -> Self { + if u8::try_from(value).is_ok() { + FieldType::U8 + } else if u16::try_from(value).is_ok() { + FieldType::U16 + } else { + FieldType::U32 + } + } +} + +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, PartialEq)] +pub struct DataPdu { + header: Header, + pub channel_id: DynamicChannelId, + pub data: Vec, +} + +impl DataPdu { + pub fn new(channel_id: DynamicChannelId, data: Vec) -> Self { + Self { + header: Header::new(channel_id, 0, Cmd::Data), + channel_id, + data, + } + } + + 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_val(self.channel_id, dst)?; + dst.write_slice(&self.data); + Ok(()) + } + + fn name() -> &'static str { + "DYNVC_DATA" + } + + fn size(&self) -> usize { + checked_sum_or_panic(&[ + Header::size(), + self.header.cb_id.size_of_val(), // ChannelId + 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, PartialEq)] +pub struct CreateResponsePdu { + header: Header, + pub channel_id: DynamicChannelId, + pub 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() -> &'static str { + "DYNVC_CREATE_RSP" + } + + 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)?; + 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_val(self.channel_id, dst)?; + self.creation_status.encode(dst)?; + 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 { + checked_sum_or_panic(&[Header::size(), Self::headerless_size(&self.header)]) + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +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 + } +} + +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, PartialEq)] +pub struct ClosePdu { + header: Header, + pub channel_id: DynamicChannelId, +} + +impl ClosePdu { + pub fn new(channel_id: DynamicChannelId) -> Self { + Self { + header: Header::new(channel_id, 0, Cmd::Close), + channel_id, + } + } + + #[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)?; + 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_val(self.channel_id, dst)?; + Ok(()) + } + + fn name() -> &'static str { + "DYNVC_CLOSE" + } + + fn headerless_size(header: &Header) -> usize { + header.cb_id.size_of_val() + } + + fn size(&self) -> usize { + checked_sum_or_panic(&[Header::size(), Self::headerless_size(&self.header)]) + } +} + +/// 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, PartialEq)] +pub struct CapabilitiesResponsePdu { + header: Header, + version: CapsVersion, +} + +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), + version, + } + } + + 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())?; + Ok(Self { header, 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() -> &'static str { + "DYNVC_CAPS_RSP" + } + + fn size() -> usize { + Self::FIXED_PART_SIZE + } +} + +#[repr(u16)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum CapsVersion { + V1 = 0x0001, + V2 = 0x0002, + V3 = 0x0003, +} + +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); + Ok(()) + } + + fn size() -> usize { + Self::FIXED_PART_SIZE + } +} + +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, PartialEq)] +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, 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 }, + CapsVersion::V3 => Self::V3 { header, charges }, + } + } + + 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 { .. } => 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, PartialEq)] +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: 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 { + 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() -> &'static str { + "DYNVC_CREATE_REQ" + } + + fn headerless_fixed_part_size(header: &Header) -> usize { + header.cb_id.size_of_val() // ChannelId + } + + fn size(&self) -> usize { + 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/server.rs b/crates/ironrdp-dvc/src/server.rs index 66af87d41..70d4cfe91 100644 --- a/crates/ironrdp-dvc/src/server.rs +++ b/crates/ironrdp-dvc/src/server.rs @@ -1,26 +1,18 @@ -use alloc::borrow::ToOwned; +use crate::pdu::{ + CapabilitiesRequestPdu, CapsVersion, CreateRequestPdu, CreationStatus, DrdynvcClientPdu, DrdynvcServerPdu, +}; +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 slab::Slab; - use ironrdp_pdu as pdu; - use ironrdp_svc::{impl_as_any, ChannelFlags, CompressionCondition, SvcMessage, SvcProcessor, SvcServerProcessor}; -use pdu::cursor::{ReadCursor, WriteCursor}; -use pdu::dvc::{CreateRequestPdu, DataFirstPdu, DataPdu}; +use pdu::cursor::ReadCursor; use pdu::gcc::ChannelName; -use pdu::rdp::vc; -use pdu::write_buf::WriteBuf; -use pdu::{cast_length, custom_err, encode_vec, invalid_message_err, other_err, PduEncode}; -use pdu::{dvc, PduResult}; - -use crate::{CompleteData, DvcMessages, DvcProcessor}; - -const DATA_MAX_SIZE: usize = 1590; +use pdu::PduDecode as _; +use pdu::PduResult; +use pdu::{cast_length, custom_err, invalid_message_err}; +use slab::Slab; pub trait DvcServerProcessor: DvcProcessor {} @@ -35,7 +27,7 @@ enum ChannelState { struct DynamicChannel { state: ChannelState, processor: Box, - data: CompleteData, + complete_data: CompleteData, } impl DynamicChannel { @@ -46,7 +38,7 @@ impl DynamicChannel { Self { state: ChannelState::Closed, processor: Box::new(processor), - data: CompleteData::new(), + complete_data: CompleteData::new(), } } } @@ -118,47 +110,47 @@ 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, None); + 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; let msg = c.processor.start(create_resp.channel_id)?; - resp.extend(encode_dvc_data(id, msg)?); + resp.extend(encode_dvc_messages(id, msg, 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 { @@ -166,27 +158,19 @@ impl SvcProcessor for DrdynvcServer { } c.state = ChannelState::Closed; } - dvc::ClientPdu::DataFirst(data) => { - let c = self.channel_by_id(data.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)?); - } - } - dvc::ClientPdu::Data(data) => { - let c = self.channel_by_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.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)? { + let msg = c.processor.process(channel_id, &complete)?; + resp.extend(encode_dvc_messages( + channel_id, + msg, + ironrdp_svc::ChannelFlags::SHOW_PROTOCOL, + )?); } } } @@ -197,54 +181,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> { - // … | dvc::ClientPdu | … - let mut cur = ReadCursor::new(user_data); - let dvc_pdu = vc::dvc::ClientPdu::decode(&mut cur, user_data.len())?; - - // … | DvcData ] - let dvc_data = cur.remaining(); - - 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 { +fn as_svc_msg_with_flag(pdu: DrdynvcServerPdu) -> PduResult { Ok(SvcMessage::from(pdu).with_flags(ChannelFlags::SHOW_PROTOCOL)) } - -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)?; - vc::dvc::ServerPdu::DataFirst(DataFirstPdu::new(channel_id, total_size, DATA_MAX_SIZE)) - } else { - vc::dvc::ServerPdu::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 = encode_vec(&pdu)?; - 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-graphics/Cargo.toml b/crates/ironrdp-graphics/Cargo.toml index 21850254b..2f9657cca 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 = "0.4" -num-traits = "0.2" +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 [dev-dependencies] diff --git a/crates/ironrdp-pdu/Cargo.toml b/crates/ironrdp-pdu/Cargo.toml index 726138e19..49acf44dc 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 = "9.0" thiserror.workspace = true md5 = { package = "md-5", version = "0.10" } num-bigint = "0.4" -num-derive = "0.4" +num-derive.workspace = true # TODO: remove num-integer = "0.1" -num-traits = "0.2" +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 in favor of https://doc.rust-lang.org/std/sync/struct.OnceLock.html diff --git a/crates/ironrdp-pdu/src/cursor.rs b/crates/ironrdp-pdu/src/cursor.rs index ee46c014a..697ccc762 100644 --- a/crates/ironrdp-pdu/src/cursor.rs +++ b/crates/ironrdp-pdu/src/cursor.rs @@ -83,6 +83,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 { 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/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/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-pdu/src/rdp/vc/dvc.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs index 9a483e7ce..4fcc11bbb 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc.rs +++ b/crates/ironrdp-pdu/src/rdp/vc/dvc.rs @@ -1,274 +1 @@ -use bit_field::BitField; -use num_derive::{FromPrimitive, ToPrimitive}; -use num_traits::{FromPrimitive, ToPrimitive}; - -use crate::cursor::{ReadCursor, WriteCursor}; -use crate::{decode_cursor, PduDecode, PduEncode, PduResult}; - -#[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 { - const NAME: &'static str = "DvcServerPdu"; -} - -impl PduEncode for ServerPdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - match self { - ServerPdu::CapabilitiesRequest(caps_request) => caps_request.encode(dst)?, - ServerPdu::CreateRequest(create_request) => create_request.encode(dst)?, - ServerPdu::DataFirst(data_first) => data_first.encode(dst)?, - ServerPdu::Data(data) => data.encode(dst)?, - ServerPdu::CloseRequest(close_request) => close_request.encode(dst)?, - }; - - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - match self { - ServerPdu::CapabilitiesRequest(caps_request) => caps_request.size(), - ServerPdu::CreateRequest(create_request) => create_request.size(), - ServerPdu::DataFirst(data_first) => data_first.size(), - ServerPdu::Data(data) => data.size(), - ServerPdu::CloseRequest(close_request) => close_request.size(), - } - } -} - -impl ServerPdu { - pub fn decode(src: &mut ReadCursor<'_>, mut dvc_data_size: usize) -> PduResult { - let dvc_header: Header = decode_cursor(src)?; - let channel_id_type = FieldType::from_u8(dvc_header.channel_id_type) - .ok_or_else(|| invalid_message_err!("DvcHeader", "invalid channel ID type"))?; - - dvc_data_size -= HEADER_SIZE; - - let res = match dvc_header.pdu_type { - PduType::Capabilities => ServerPdu::CapabilitiesRequest(CapabilitiesRequestPdu::decode(src)?), - PduType::Create => ServerPdu::CreateRequest(CreateRequestPdu::decode(src, channel_id_type, dvc_data_size)?), - PduType::DataFirst => { - let data_length_type = FieldType::from_u8(dvc_header.pdu_dependent) - .ok_or_else(|| invalid_message_err!("DvcHeader", "data length type"))?; - - ServerPdu::DataFirst(DataFirstPdu::decode( - src, - channel_id_type, - data_length_type, - dvc_data_size, - )?) - } - PduType::Data => ServerPdu::Data(DataPdu::decode(src, channel_id_type, dvc_data_size)?), - PduType::Close => ServerPdu::CloseRequest(ClosePdu::decode(src, channel_id_type)?), - }; - - Ok(res) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ClientPdu { - CapabilitiesResponse(CapabilitiesResponsePdu), - CreateResponse(CreateResponsePdu), - DataFirst(DataFirstPdu), - Data(DataPdu), - CloseResponse(ClosePdu), -} - -impl ClientPdu { - const NAME: &'static str = "ClientPdu"; - - pub fn decode(src: &mut ReadCursor<'_>, mut dvc_data_size: usize) -> PduResult { - let dvc_header = Header::decode(src)?; - let channel_id_type = FieldType::from_u8(dvc_header.channel_id_type) - .ok_or_else(|| invalid_message_err!("DvcHeader", "invalid channel ID type"))?; - - dvc_data_size -= HEADER_SIZE; - - let res = match dvc_header.pdu_type { - PduType::Capabilities => ClientPdu::CapabilitiesResponse(CapabilitiesResponsePdu::decode(src)?), - PduType::Create => ClientPdu::CreateResponse(CreateResponsePdu::decode(src, channel_id_type)?), - PduType::DataFirst => { - let data_length_type = FieldType::from_u8(dvc_header.pdu_dependent) - .ok_or_else(|| invalid_message_err!("DvcHeader", "data length type"))?; - - ClientPdu::DataFirst(DataFirstPdu::decode( - src, - channel_id_type, - data_length_type, - dvc_data_size, - )?) - } - PduType::Data => ClientPdu::Data(DataPdu::decode(src, channel_id_type, dvc_data_size)?), - PduType::Close => ClientPdu::CloseResponse(ClosePdu::decode(src, channel_id_type)?), - }; - - Ok(res) - } -} - -impl PduEncode for ClientPdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - match self { - ClientPdu::CapabilitiesResponse(caps_request) => caps_request.encode(dst)?, - ClientPdu::CreateResponse(create_request) => create_request.encode(dst)?, - ClientPdu::DataFirst(data_first) => data_first.encode(dst)?, - ClientPdu::Data(data) => data.encode(dst)?, - ClientPdu::CloseResponse(close_response) => close_response.encode(dst)?, - }; - - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - match self { - ClientPdu::CapabilitiesResponse(caps_request) => caps_request.size(), - ClientPdu::CreateResponse(create_request) => create_request.size(), - ClientPdu::DataFirst(data_first) => data_first.size(), - ClientPdu::Data(data) => data.size(), - ClientPdu::CloseResponse(close_response) => close_response.size(), - } - } -} - -#[repr(u8)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] -pub enum FieldType { - U8 = 0x00, - U16 = 0x01, - U32 = 0x02, -} - -impl FieldType { - pub fn read_according_to_type(self, src: &mut ReadCursor<'_>) -> PduResult { - ensure_size!(in: src, size: self.size()); - - let value = match self { - FieldType::U8 => u32::from(src.read_u8()), - FieldType::U16 => u32::from(src.read_u16()), - FieldType::U32 => src.read_u32(), - }; - - Ok(value) - } - - pub fn write_according_to_type(self, dst: &mut WriteCursor<'_>, value: u32) -> PduResult<()> { - ensure_size!(in: dst, size: self.size()); - - match self { - FieldType::U8 => dst.write_u8(value as u8), - FieldType::U16 => dst.write_u16(value as u16), - FieldType::U32 => dst.write_u32(value), - }; - - Ok(()) - } - - pub fn size(self) -> usize { - match self { - FieldType::U8 => 1, - FieldType::U16 => 2, - FieldType::U32 => 4, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -struct Header { - channel_id_type: u8, // 2 bit - pdu_dependent: u8, // 2 bit - pdu_type: PduType, // 4 bit -} - -impl Header { - const NAME: &'static str = "DvcHeader"; - - const FIXED_PART_SIZE: usize = 1 /* header */; -} - -impl PduEncode for Header { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_fixed_part_size!(in: dst); - - 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()); - dst.write_u8(dvc_header); - - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - Self::FIXED_PART_SIZE - } -} - -impl<'de> PduDecode<'de> for Header { - fn decode(src: &mut ReadCursor<'de>) -> PduResult { - ensure_fixed_part_size!(in: src); - - let dvc_header = src.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_else(|| invalid_message_err!("DvcHeader", "invalid Cmd"))?; - - Ok(Self { - channel_id_type, - pdu_dependent, - pdu_type, - }) - } -} 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 344751d18..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities.rs +++ /dev/null @@ -1,162 +0,0 @@ -#[cfg(test)] -mod tests; - -use num_derive::{FromPrimitive, ToPrimitive}; -use num_traits::{FromPrimitive, ToPrimitive}; - -use super::{Header, PduType, HEADER_SIZE, UNUSED_U8}; -use crate::cursor::{ReadCursor, WriteCursor}; -use crate::{PduDecode, PduEncode, PduResult}; - -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 CapabilitiesRequestPdu { - const NAME: &'static str = "CapabilitiesRequestPdu"; - - const FIXED_PART_SIZE: usize = HEADER_SIZE + DVC_CAPABILITIES_PAD_SIZE + DVC_CAPABILITIES_VERSION_SIZE; -} - -impl PduEncode for CapabilitiesRequestPdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_size!(in: dst, size: self.size()); - - let dvc_header = Header { - channel_id_type: UNUSED_U8, - pdu_dependent: UNUSED_U8, - pdu_type: PduType::Capabilities, - }; - dvc_header.encode(dst)?; - dst.write_u8(UNUSED_U8); - - match self { - CapabilitiesRequestPdu::V1 => dst.write_u16(CapsVersion::V1.to_u16().unwrap()), - CapabilitiesRequestPdu::V2 { charges } => { - dst.write_u16(CapsVersion::V2.to_u16().unwrap()); - for charge in charges.iter() { - dst.write_u16(*charge); - } - } - CapabilitiesRequestPdu::V3 { charges } => { - dst.write_u16(CapsVersion::V3.to_u16().unwrap()); - for charge in charges.iter() { - dst.write_u16(*charge); - } - } - } - - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - let charges_length = match self { - CapabilitiesRequestPdu::V1 => 0, - CapabilitiesRequestPdu::V2 { charges } | CapabilitiesRequestPdu::V3 { charges } => { - charges.len() * DVC_CAPABILITIES_CHARGE_SIZE - } - }; - - Self::FIXED_PART_SIZE + charges_length - } -} - -impl<'de> PduDecode<'de> for CapabilitiesRequestPdu { - fn decode(src: &mut ReadCursor<'de>) -> PduResult { - ensure_size!(in: src, size: Self::FIXED_PART_SIZE - HEADER_SIZE); - - let _pad = src.read_u8(); - let version = CapsVersion::from_u16(src.read_u16()) - .ok_or_else(|| invalid_message_err!("DvcCapabilities", "invalid version"))?; - - match version { - CapsVersion::V1 => Ok(Self::V1), - CapsVersion::V2 => { - let mut charges = [0; DVC_CAPABILITIES_CHARGE_COUNT]; - for c in charges.iter_mut() { - *c = src.read_u16(); - } - Ok(Self::V2 { charges }) - } - CapsVersion::V3 => { - let mut charges = [0; DVC_CAPABILITIES_CHARGE_COUNT]; - for c in charges.iter_mut() { - *c = src.read_u16(); - } - Ok(Self::V3 { charges }) - } - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CapabilitiesResponsePdu { - pub version: CapsVersion, -} - -impl CapabilitiesResponsePdu { - const NAME: &'static str = "CapabilitiesResponsePdu"; - - const FIXED_PART_SIZE: usize = HEADER_SIZE + DVC_CAPABILITIES_PAD_SIZE + DVC_CAPABILITIES_VERSION_SIZE; -} - -impl PduEncode for CapabilitiesResponsePdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_fixed_part_size!(in: dst); - - let dvc_header = Header { - channel_id_type: UNUSED_U8, - pdu_dependent: UNUSED_U8, - pdu_type: PduType::Capabilities, - }; - dvc_header.encode(dst)?; - dst.write_u8(UNUSED_U8); - dst.write_u16(self.version.to_u16().unwrap()); - - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - Self::FIXED_PART_SIZE - } -} - -impl<'de> PduDecode<'de> for CapabilitiesResponsePdu { - fn decode(src: &mut ReadCursor<'de>) -> PduResult { - ensure_size!(in: src, size: Self::FIXED_PART_SIZE - HEADER_SIZE); - - let _pad = src.read_u8(); - let version = CapsVersion::from_u16(src.read_u16()) - .ok_or_else(|| invalid_message_err!("DvcCapabilities", "invalid version"))?; - - Ok(Self { version }) - } -} 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 dedea0b15..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/capabilities/tests.rs +++ /dev/null @@ -1,123 +0,0 @@ -use lazy_static::lazy_static; - -use super::*; -use crate::{decode, encode_vec, PduErrorKind}; - -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 = [0x00, 0x01, 0x01]; - match decode::(buffer_with_invalid_caps_version.as_ref()) { - Err(e) if matches!(e.kind(), PduErrorKind::InvalidMessage { .. }) => (), - 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(), - decode::(&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 buffer = encode_vec(&dvc_capabilities_request_pdu_v1).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.size(); - - assert_eq!(expected_buf_len, len); -} - -#[test] -fn from_buffer_correct_parses_dvc_capabilities_request_pdu_v2() { - assert_eq!( - DVC_CAPABILITIES_REQUEST_V2.clone(), - decode::(&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 buffer = encode_vec(&dvc_capabilities_request_pdu_v2).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.size(); - - 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 = [0x00, 0x01, 0x01]; - match decode::(buffer_with_invalid_caps_version.as_ref()) { - Err(e) if matches!(e.kind(), PduErrorKind::InvalidMessage { .. }) => (), - res => panic!("Expected InvalidDvcCapabilitiesVersion error, got: {res:?}"), - }; -} - -#[test] -fn from_buffer_correct_parses_dvc_capabilities_response() { - assert_eq!( - DVC_CAPABILITIES_RESPONSE.clone(), - decode::(&DVC_CAPABILITIES_RESPONSE_BUFFER[1..]).unwrap(), - ); -} - -#[test] -fn to_buffer_correct_serializes_dvc_capabilities_response() { - let capabilities_response = DVC_CAPABILITIES_RESPONSE.clone(); - - let buffer = encode_vec(&capabilities_response).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.size(); - - assert_eq!(expected_buf_len, len); -} 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 da859bc87..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/close.rs +++ /dev/null @@ -1,49 +0,0 @@ -#[cfg(test)] -mod tests; - -use super::{FieldType, Header, PduType, HEADER_SIZE, UNUSED_U8}; -use crate::cursor::{ReadCursor, WriteCursor}; -use crate::{PduEncode, PduResult}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ClosePdu { - pub channel_id_type: FieldType, - pub channel_id: u32, -} - -impl ClosePdu { - const NAME: &'static str = "DvcClosePdu"; - - pub(crate) fn decode(src: &mut ReadCursor<'_>, channel_id_type: FieldType) -> PduResult { - let channel_id = channel_id_type.read_according_to_type(src)?; - - Ok(Self { - channel_id_type, - channel_id, - }) - } -} - -impl PduEncode for ClosePdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_size!(in: dst, size: self.size()); - - let dvc_header = Header { - channel_id_type: self.channel_id_type as u8, - pdu_dependent: UNUSED_U8, - pdu_type: PduType::Close, - }; - dvc_header.encode(dst)?; - self.channel_id_type.write_according_to_type(dst, self.channel_id)?; - - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - HEADER_SIZE + self.channel_id_type.size() - } -} 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 a5019c031..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/close/tests.rs +++ /dev/null @@ -1,41 +0,0 @@ -use lazy_static::lazy_static; - -use super::*; -use crate::encode_vec; - -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() { - let mut cur = ReadCursor::new(&DVC_CLOSE_BUFFER[1..]); - assert_eq!(DVC_CLOSE.clone(), ClosePdu::decode(&mut cur, FieldType::U16).unwrap(),); -} - -#[test] -fn to_buffer_correct_serializes_dvc_close_pdu() { - let close = DVC_CLOSE.clone(); - - let buffer = encode_vec(&close).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.size(); - - 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 deleted file mode 100644 index 918d8891c..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/create.rs +++ /dev/null @@ -1,123 +0,0 @@ -#[cfg(test)] -mod tests; - -use super::{FieldType, Header, PduType, HEADER_SIZE, UNUSED_U8}; -use crate::cursor::{ReadCursor, WriteCursor}; -use crate::{utils, PduEncode, PduResult}; - -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 { - const NAME: &'static str = "DvcCreateRequestPdu"; - - pub fn new(channel_id: u32, channel_name: String) -> Self { - Self { - channel_id_type: FieldType::U32, - channel_id, - channel_name, - } - } - - pub(crate) fn decode( - src: &mut ReadCursor<'_>, - channel_id_type: FieldType, - mut data_size: usize, - ) -> PduResult { - let channel_id = channel_id_type.read_according_to_type(src)?; - - data_size -= channel_id_type.size(); - ensure_size!(in: src, size: data_size); - let channel_name = utils::decode_string(src.read_slice(data_size), utils::CharacterSet::Ansi, false)?; - - Ok(Self { - channel_id_type, - channel_id, - channel_name, - }) - } -} - -impl PduEncode for CreateRequestPdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_size!(in: dst, size: self.size()); - - 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.encode(dst)?; - self.channel_id_type.write_according_to_type(dst, self.channel_id)?; - dst.write_slice(self.channel_name.as_ref()); - dst.write_u8(0); - - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - HEADER_SIZE + self.channel_id_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 { - const NAME: &'static str = "DvcCreateResponsePdu"; - - pub(crate) fn decode(src: &mut ReadCursor<'_>, channel_id_type: FieldType) -> PduResult { - let channel_id = channel_id_type.read_according_to_type(src)?; - - ensure_size!(in: src, size: 4); - let creation_status = src.read_u32(); - - Ok(Self { - channel_id_type, - channel_id, - creation_status, - }) - } -} - -impl PduEncode for CreateResponsePdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ensure_size!(in: dst, size: self.size()); - - let dvc_header = Header { - channel_id_type: self.channel_id_type as u8, - pdu_dependent: UNUSED_U8, - pdu_type: PduType::Create, - }; - dvc_header.encode(dst)?; - self.channel_id_type.write_according_to_type(dst, self.channel_id)?; - dst.write_u32(self.creation_status); - - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - HEADER_SIZE + self.channel_id_type.size() + DVC_CREATION_STATUS_SIZE - } -} 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 d0d4159b1..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/create/tests.rs +++ /dev/null @@ -1,89 +0,0 @@ -use lazy_static::lazy_static; - -use super::*; -use crate::encode_vec; - -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() { - let mut cur = ReadCursor::new(&DVC_CREATE_REQUEST_BUFFER[1..]); - assert_eq!( - DVC_CREATE_REQUEST.clone(), - CreateRequestPdu::decode( - &mut cur, - 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 buffer = encode_vec(&create_request).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.size(); - - assert_eq!(expected_buf_len, len); -} - -#[test] -fn from_buffer_correct_parses_dvc_create_response_pdu() { - let mut cur = ReadCursor::new(&DVC_CREATE_RESPONSE_BUFFER[1..]); - assert_eq!( - DVC_CREATE_RESPONSE.clone(), - CreateResponsePdu::decode(&mut cur, FieldType::U8).unwrap(), - ); -} - -#[test] -fn to_buffer_correct_serializes_dvc_create_response_pdu() { - let create_response = DVC_CREATE_RESPONSE.clone(); - - let buffer = encode_vec(&create_response).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.size(); - - 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 deleted file mode 100644 index 416f7af67..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/data.rs +++ /dev/null @@ -1,68 +0,0 @@ -#[cfg(test)] -mod tests; - -use super::{FieldType, Header, PduType, HEADER_SIZE, PDU_WITH_DATA_MAX_SIZE, UNUSED_U8}; -use crate::cursor::{ReadCursor, WriteCursor}; -use crate::{PduEncode, PduResult}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DataPdu { - pub channel_id_type: FieldType, - pub channel_id: u32, - pub data_size: usize, -} - -impl DataPdu { - const NAME: &'static str = "DvcDataPdu"; - - pub fn new(channel_id: u32, data_size: usize) -> Self { - Self { - channel_id_type: FieldType::U32, - channel_id, - data_size, - } - } - - pub(crate) fn decode( - src: &mut ReadCursor<'_>, - channel_id_type: FieldType, - mut data_size: usize, - ) -> PduResult { - let channel_id = channel_id_type.read_according_to_type(src)?; - data_size -= channel_id_type.size(); - - let expected_max_data_size = PDU_WITH_DATA_MAX_SIZE - (HEADER_SIZE + channel_id_type.size()); - - if data_size > expected_max_data_size { - Err(invalid_message_err!("DvcDataPdu", "invalid message size")) - } else { - Ok(Self { - channel_id_type, - channel_id, - data_size, - }) - } - } -} - -impl PduEncode for DataPdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - let dvc_header = Header { - channel_id_type: self.channel_id_type as u8, - pdu_dependent: UNUSED_U8, - pdu_type: PduType::Data, - }; - dvc_header.encode(dst)?; - self.channel_id_type.write_according_to_type(dst, self.channel_id)?; - - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - HEADER_SIZE + self.channel_id_type.size() - } -} 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 b7d3f9e60..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/data/tests.rs +++ /dev/null @@ -1,70 +0,0 @@ -use lazy_static::lazy_static; - -use super::*; -use crate::{encode_vec, PduErrorKind}; - -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() { - let mut cur = ReadCursor::new(DVC_INVALID_DATA_MESSAGE_BUFFER.as_ref()); - match DataPdu::decode(&mut cur, FieldType::U8, PDU_WITH_DATA_MAX_SIZE) { - Err(e) if matches!(e.kind(), PduErrorKind::InvalidMessage { .. }) => (), - res => panic!("Expected InvalidDvcMessageSize error, got: {res:?}"), - }; -} - -#[test] -fn from_buffer_correct_parses_dvc_data_pdu() { - let mut cur = ReadCursor::new(&DVC_FULL_DATA_BUFFER[1..]); - assert_eq!( - DVC_DATA.clone(), - DataPdu::decode( - &mut cur, - 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 buffer = encode_vec(&data).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.size(); - - assert_eq!(expected_buf_len, len); -} 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 446c38a4e..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first.rs +++ /dev/null @@ -1,85 +0,0 @@ -#[cfg(test)] -mod tests; - -use super::{FieldType, Header, PduType, HEADER_SIZE, PDU_WITH_DATA_MAX_SIZE}; -use crate::cursor::{ReadCursor, WriteCursor}; -use crate::{PduEncode, PduResult}; - -#[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 { - const NAME: &'static str = "DvcDataFirstPdu"; - - 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, - } - } -} - -impl PduEncode for DataFirstPdu { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - 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.encode(dst)?; - self.channel_id_type.write_according_to_type(dst, self.channel_id)?; - self.total_data_size_type - .write_according_to_type(dst, self.total_data_size)?; - - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - HEADER_SIZE + self.channel_id_type.size() + self.total_data_size_type.size() - } -} - -impl DataFirstPdu { - pub(crate) fn decode( - src: &mut ReadCursor<'_>, - channel_id_type: FieldType, - total_data_size_type: FieldType, - mut data_size: usize, - ) -> PduResult { - let channel_id = channel_id_type.read_according_to_type(src)?; - let total_data_size = total_data_size_type.read_according_to_type(src)?; - - data_size -= channel_id_type.size() + total_data_size_type.size(); - if data_size > total_data_size as usize { - return Err(not_enough_bytes_err!(total_data_size as usize, data_size)); - } - - let expected_max_data_size = - PDU_WITH_DATA_MAX_SIZE - (HEADER_SIZE + channel_id_type.size() + total_data_size_type.size()); - - if data_size > expected_max_data_size { - Err(invalid_message_err!("DvcDataFirst", "invalid message size")) - } else { - Ok(Self { - channel_id_type, - channel_id, - total_data_size_type, - total_data_size, - data_size, - }) - } - } -} 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 a0ce362a7..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/display.rs +++ /dev/null @@ -1,353 +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 -/// -/// [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 -/// -/// [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-pdu/src/rdp/vc/dvc/tests.rs b/crates/ironrdp-pdu/src/rdp/vc/dvc/tests.rs deleted file mode 100644 index 299d743bf..000000000 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/tests.rs +++ /dev/null @@ -1,150 +0,0 @@ -use lazy_static::lazy_static; - -use super::*; -use crate::{decode, encode_vec, PduErrorKind}; - -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 decode::
([invalid_header].as_ref()) { - Err(_) => (), - res => panic!("Expected InvalidDvcPduType error, got: {res:?}"), - }; -} - -#[test] -fn from_buffer_correct_parses_dvc_header() { - assert_eq!( - *DYNAMIC_CHANNEL_HEADER, - decode::
(DVC_HEADER_BUFFER.as_ref()).unwrap(), - ); -} - -#[test] -fn to_buffer_correct_serializes_dvc_header() { - let channel_header = &*DYNAMIC_CHANNEL_HEADER; - - let buffer = encode_vec(channel_header).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; - let expected_buf_len = DVC_HEADER_BUFFER.len(); - - let len = channel_header.size(); - - assert_eq!(expected_buf_len, len); -} - -#[test] -fn from_buffer_parsing_for_client_dvc_pdu_with_invalid_id_length_type_fails() { - let mut cur = ReadCursor::new(DVC_HEADER_WITH_INVALID_ID_LENGTH_TYPE_BUFFER.as_ref()); - match ClientPdu::decode(&mut cur, HEADER_SIZE) { - Err(e) if matches!(e.kind(), PduErrorKind::InvalidMessage { .. }) => (), - res => panic!("Expected InvalidDVChannelIdLength error, got: {res:?}"), - }; -} - -#[test] -fn from_buffer_parsing_for_server_dvc_pdu_with_invalid_id_length_type_fails() { - let mut cur = ReadCursor::new(DVC_HEADER_WITH_INVALID_ID_LENGTH_TYPE_BUFFER.as_ref()); - match ServerPdu::decode(&mut cur, HEADER_SIZE) { - Err(e) if matches!(e.kind(), PduErrorKind::InvalidMessage { .. }) => (), - res => panic!("Expected InvalidDVChannelIdLength error, got: {res:?}"), - }; -} - -#[test] -fn from_buffer_according_to_type_u8_test() { - let mut cur = ReadCursor::new(TEST_BUFFER.as_ref()); - let channel_id = FieldType::U8.read_according_to_type(&mut cur).unwrap(); - let expected_channel_id = 0x01; - - assert_eq!(expected_channel_id, channel_id); -} - -#[test] -fn from_buffer_according_to_type_u16_test() { - let mut cur = ReadCursor::new(TEST_BUFFER.as_ref()); - let channel_id = FieldType::U16.read_according_to_type(&mut cur).unwrap(); - let expected_channel_id = 0x0201; - - assert_eq!(expected_channel_id, channel_id); -} - -#[test] -fn from_buffer_according_to_type_u32_test() { - let mut cur = ReadCursor::new(TEST_BUFFER.as_ref()); - let channel_id = FieldType::U32.read_according_to_type(&mut cur).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![0; FieldType::U8.size()]; - let mut cur = WriteCursor::new(&mut buffer); - FieldType::U8.write_according_to_type(&mut cur, 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![0; FieldType::U16.size()]; - let mut cur = WriteCursor::new(&mut buffer); - FieldType::U16.write_according_to_type(&mut cur, 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![0; FieldType::U32.size()]; - let mut cur = WriteCursor::new(&mut buffer); - FieldType::U32.write_according_to_type(&mut cur, 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.size(); - assert_eq!(1, length); -} - -#[test] -fn get_length_according_to_type_u16_test() { - let length = FieldType::U16.size(); - assert_eq!(2, length); -} - -#[test] -fn get_length_according_to_type_u32_test() { - let length = FieldType::U32.size(); - assert_eq!(4, length); -} 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-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-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 1d489b77c..ccc4b784d 100644 --- a/crates/ironrdp-server/src/server.rs +++ b/crates/ironrdp-server/src/server.rs @@ -5,13 +5,14 @@ use anyhow::{bail, Result}; 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; use ironrdp_pdu::mcs::SendDataRequest; use ironrdp_pdu::rdp::capability_sets::{CapabilitySet, CmdFlags, GeneralExtraFlags}; use ironrdp_pdu::{self, decode, mcs, nego, rdp, Action, 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; @@ -42,49 +43,18 @@ impl RdpServerSecurity { } } -struct 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>>, } +impl_as_any!(AInputHandler); + impl dvc::DvcProcessor for AInputHandler { fn channel_name(&self) -> &str { 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()); @@ -94,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)? { @@ -104,7 +74,7 @@ impl dvc::DvcProcessor for AInputHandler { } } - Ok(vec![]) + Ok(Vec::new()) } } @@ -218,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 { @@ -471,7 +441,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-session/Cargo.toml b/crates/ironrdp-session/Cargo.toml index f2d4ef174..11995b539 100644 --- a/crates/ironrdp-session/Cargo.toml +++ b/crates/ironrdp-session/Cargo.toml @@ -16,10 +16,9 @@ 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 +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 d04a579fc..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 { @@ -22,13 +21,11 @@ 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, connection_result.io_channel_id, - connection_result.graphics_config, - graphics_handler, connection_result.connection_activation, ); @@ -163,11 +160,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/legacy.rs b/crates/ironrdp-session/src/legacy.rs index 0fd35c13e..9d4ab5899 100644 --- a/crates/ironrdp-session/src/legacy.rs +++ b/crates/ironrdp-session/src/legacy.rs @@ -1,91 +1,4 @@ -use ironrdp_connector::encode_send_data_request; -use ironrdp_connector::legacy::SendDataIndicationCtx; -use ironrdp_pdu::cursor::{ReadCursor, WriteCursor}; -use ironrdp_pdu::rdp::vc; -use ironrdp_pdu::write_buf::WriteBuf; -use ironrdp_pdu::{decode, PduEncode, PduResult}; - -use crate::{SessionError, SessionErrorExt, 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.size() + 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 DvcMessage<'_> { - const NAME: &'static str = "DvcMessage"; -} - -impl PduEncode for DvcMessage<'_> { - fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - ironrdp_pdu::ensure_size!(in: dst, size: self.size()); - - self.channel_header.encode(dst)?; - self.dvc_pdu.encode(dst)?; - dst.write_slice(self.dvc_data); - Ok(()) - } - - fn name(&self) -> &'static str { - Self::NAME - } - - fn size(&self) -> usize { - self.channel_header.size() + self.dvc_pdu.size() + 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 user_data = ctx.user_data; - - // [ vc::ChannelPduHeader | … - let channel_header: vc::ChannelPduHeader = decode(user_data).map_err(SessionError::pdu)?; - let dvc_data_len = user_data.len(); - debug_assert_eq!(dvc_data_len, channel_header.length as usize); - - // … | dvc::ServerPdu | … - let mut cur = ReadCursor::new(user_data); - let dvc_pdu = vc::dvc::ServerPdu::decode(&mut cur, dvc_data_len).map_err(SessionError::pdu)?; - - // … | DvcData ] - let dvc_data = cur.remaining(); - - 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 1d6de9dce..000000000 --- a/crates/ironrdp-session/src/x224/display.rs +++ /dev/null @@ -1,15 +0,0 @@ -use ironrdp_pdu::decode; -use ironrdp_pdu::dvc::display::ServerPdu; - -use super::DynamicChannelDataHandler; -use crate::{SessionError, SessionErrorExt, SessionResult}; - -pub(crate) struct Handler; - -impl DynamicChannelDataHandler for Handler { - fn process_complete_data(&mut self, complete_data: Vec) -> SessionResult>> { - let gfx_pdu: ServerPdu = decode(&complete_data).map_err(SessionError::pdu)?; - debug!("Got Display PDU: {:?}", gfx_pdu); - Ok(None) - } -} 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) -} diff --git a/crates/ironrdp-session/src/x224/mod.rs b/crates/ironrdp-session/src/x224/mod.rs index f95bd0548..1c4aaf6a4 100644 --- a/crates/ironrdp-session/src/x224/mod.rs +++ b/crates/ironrdp-session/src/x224/mod.rs @@ -1,28 +1,15 @@ -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_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}; 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 { @@ -38,16 +25,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,33 +36,17 @@ 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)| { - if channel.is_drdynvc() { - static_channels.get_channel_id_by_type_id(type_id) - } else { - None - } - }); - 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, } } - 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()) @@ -108,6 +72,11 @@ impl Processor { process_svc_messages(messages.into(), channel_id, self.user_channel_id) } + pub fn get_dvc_processor(&self) -> Option<(&T, Option)> { + 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> { @@ -117,9 +86,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) @@ -194,203 +160,6 @@ 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(()) - } - /// 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 = @@ -410,156 +179,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-svc/src/lib.rs b/crates/ironrdp-svc/src/lib.rs index 2e428083e..ea14a7123 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 { @@ -135,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() } @@ -233,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 @@ -241,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); diff --git a/crates/ironrdp-testsuite-core/Cargo.toml b/crates/ironrdp-testsuite-core/Cargo.toml index 669e14208..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 = "1.4" +lazy_static.workspace = true # TODO: remove in favor of https://doc.rust-lang.org/std/sync/struct.OnceLock.html paste = "1" [dev-dependencies] @@ -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..9a398e8b1 --- /dev/null +++ b/crates/ironrdp-testsuite-core/tests/dvc/capabilities.rs @@ -0,0 +1,57 @@ +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]; + +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] +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..c7279de1a --- /dev/null +++ b/crates/ironrdp-testsuite-core/tests/dvc/close.rs @@ -0,0 +1,27 @@ +use super::*; + +const CHANNEL_ID: u32 = 0x0303; +const ENCODED: [u8; 3] = [0x41, 0x03, 0x03]; + +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] +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..9740ffe05 --- /dev/null +++ b/crates/ironrdp-testsuite-core/tests/dvc/create.rs @@ -0,0 +1,37 @@ +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]; + +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] +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..93d40462f --- /dev/null +++ b/crates/ironrdp-testsuite-core/tests/dvc/data.rs @@ -0,0 +1,37 @@ +use super::*; + +const CHANNEL_ID: u32 = 0x03; +const PREFIX: [u8; 2] = [0x30, 0x03]; +const DATA: [u8; 12] = [0x71; 12]; + +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 + }) +} + +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] +fn encodes_data() { + test_encodes(decoded_client(), encoded()); + test_encodes(decoded_server(), encoded()); +} diff --git a/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first/tests.rs b/crates/ironrdp-testsuite-core/tests/dvc/data_first.rs similarity index 66% rename from crates/ironrdp-pdu/src/rdp/vc/dvc/data_first/tests.rs rename to crates/ironrdp-testsuite-core/tests/dvc/data_first.rs index f518dd4dc..81c7605cb 100644 --- a/crates/ironrdp-pdu/src/rdp/vc/dvc/data_first/tests.rs +++ b/crates/ironrdp-testsuite-core/tests/dvc/data_first.rs @@ -1,25 +1,15 @@ -use lazy_static::lazy_static; - use super::*; -use crate::rdp::vc::dvc::ClientPdu; -use crate::{encode_vec, PduErrorKind}; - -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 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]; -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 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, @@ -106,125 +96,89 @@ 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); - - 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()); - +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 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_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(), - }); + }) } -#[test] -fn from_buffer_parsing_for_dvc_data_first_pdu_with_invalid_message_size_fails() { - let mut cur = ReadCursor::new(DVC_INVALID_DATA_MESSAGE_BUFFER.as_ref()); - match DataFirstPdu::decode(&mut cur, FieldType::U8, FieldType::U16, PDU_WITH_DATA_MAX_SIZE) { - Err(e) if matches!(e.kind(), PduErrorKind::InvalidMessage { .. }) => (), - res => panic!("Expected InvalidMessage error, got: {res:?}"), - }; +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), + )) + }) } -#[test] -fn from_buffer_parsing_for_dvc_data_first_pdu_with_invalid_total_message_size_fails() { - let mut cur = ReadCursor::new(DVC_DATA_FIRST_WITH_INVALID_TOTAL_MESSAGE_SIZE_BUFFER.as_ref()); - match DataFirstPdu::decode( - &mut cur, - FieldType::U8, - FieldType::U8, - DVC_DATA_FIRST_WITH_INVALID_TOTAL_MESSAGE_SIZE_BUFFER_SIZE, - ) { - Err(e) if matches!(e.kind(), PduErrorKind::NotEnoughBytes { .. }) => (), - res => panic!("Expected NotEnoughBytes error, got: {res:?}"), - }; +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), + )) + }) } -#[test] -fn from_buffer_correct_parses_dvc_data_first_pdu() { - let mut cur = ReadCursor::new(&DVC_FULL_DATA_FIRST_BUFFER[1..]); - assert_eq!( - *DVC_DATA_FIRST, - DataFirstPdu::decode( - &mut cur, - FieldType::U8, - FieldType::U16, - DVC_FULL_DATA_FIRST_BUFFER_SIZE - DVC_TEST_HEADER_SIZE - ) - .unwrap(), - ); +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 + }) } -#[test] -fn to_buffer_correct_serializes_dvc_data_first_pdu() { - let data_first = &*DVC_DATA_FIRST; - - let buffer = encode_vec(data_first).unwrap(); +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), + )) + }) +} - assert_eq!(DVC_DATA_FIRST_PREFIX.as_ref(), buffer.as_slice()); +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 buffer_length_is_correct_for_dvc_data_first_pdu() { - let data_first = &*DVC_DATA_FIRST; - let expected_buf_len = DVC_DATA_FIRST_PREFIX.len(); - - let len = data_first.size(); - - assert_eq!(expected_buf_len, len); +fn decodes_data_first() { + test_decodes(encoded(), decoded_client()); + test_decodes(encoded(), decoded_server()); } #[test] -fn from_buffer_correct_parses_dvc_server_pdu_with_data_first_where_total_length_equals_to_buffer_length() { - let mut cur = ReadCursor::new(FULL_DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_BUFFER.as_slice()); - assert_eq!( - *DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH, - ClientPdu::decode( - &mut cur, - FULL_DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_BUFFER.len(), - ) - .unwrap(), - ); +fn encodes_data_first() { + test_encodes(decoded_client(), encoded()); + test_encodes(decoded_server(), encoded()); } #[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 buffer = encode_vec(data_first).unwrap(); - - assert_eq!( - DATA_FIRST_WHERE_TOTAL_LENGTH_EQUALS_TO_BUFFER_LENGTH_PREFIX.as_ref(), - buffer.as_slice() - ); +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] -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.size(); - - assert_eq!(expected_buf_len, len); +fn encodes_data_first_edge_case() { + 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..5e243e545 --- /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 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]) { + 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; diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index b230e2d02..f0c7a8884 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() { diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 7bd94fb21..0040c184f 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" @@ -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", ] @@ -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" @@ -597,9 +619,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",