From 74e95f692ac6c32a88988c322bce0146eb28ec21 Mon Sep 17 00:00:00 2001 From: Mihnea Buzatu <34985125+mihneabuz@users.noreply.github.com> Date: Fri, 25 Aug 2023 16:35:00 +0300 Subject: [PATCH] feat: initial server support (#167) --- Cargo.lock | 28 + Cargo.toml | 2 + crates/ironrdp-acceptor/Cargo.toml | 22 + crates/ironrdp-acceptor/README.md | 3 + .../src/channel_connection.rs | 168 ++++++ crates/ironrdp-acceptor/src/connection.rs | 494 ++++++++++++++++++ crates/ironrdp-acceptor/src/finalization.rs | 224 ++++++++ crates/ironrdp-acceptor/src/lib.rs | 98 ++++ crates/ironrdp-acceptor/src/util.rs | 43 ++ crates/ironrdp-fuzzing/src/oracles/mod.rs | 20 +- .../ironrdp-graphics/src/image_processing.rs | 18 + .../decoder.rs} | 116 +--- .../src/rdp6/bitmap_stream/encoder.rs | 283 ++++++++++ .../src/rdp6/bitmap_stream/mod.rs | 179 +++++++ crates/ironrdp-graphics/src/rdp6/mod.rs | 4 +- crates/ironrdp-graphics/src/rdp6/rle.rs | 399 +++++++++++++- crates/ironrdp-pdu/src/basic_output/bitmap.rs | 14 +- .../src/basic_output/bitmap/rdp6.rs | 237 +++++---- .../ironrdp-pdu/src/basic_output/fast_path.rs | 8 + crates/ironrdp-server/Cargo.toml | 28 + crates/ironrdp-server/README.md | 21 + crates/ironrdp-server/src/builder.rs | 156 ++++++ crates/ironrdp-server/src/capabilities.rs | 78 +++ crates/ironrdp-server/src/display.rs | 75 +++ crates/ironrdp-server/src/encoder/bitmap.rs | 111 ++++ crates/ironrdp-server/src/encoder/mod.rs | 123 +++++ crates/ironrdp-server/src/handler.rs | 181 +++++++ crates/ironrdp-server/src/lib.rs | 13 + crates/ironrdp-server/src/server.rs | 284 ++++++++++ crates/ironrdp/Cargo.toml | 4 + crates/ironrdp/src/lib.rs | 4 + fuzz/fuzz_targets/bitmap_stream.rs | 3 +- 32 files changed, 3205 insertions(+), 236 deletions(-) create mode 100644 crates/ironrdp-acceptor/Cargo.toml create mode 100644 crates/ironrdp-acceptor/README.md create mode 100644 crates/ironrdp-acceptor/src/channel_connection.rs create mode 100644 crates/ironrdp-acceptor/src/connection.rs create mode 100644 crates/ironrdp-acceptor/src/finalization.rs create mode 100644 crates/ironrdp-acceptor/src/lib.rs create mode 100644 crates/ironrdp-acceptor/src/util.rs rename crates/ironrdp-graphics/src/rdp6/{bitmap_stream.rs => bitmap_stream/decoder.rs} (74%) create mode 100644 crates/ironrdp-graphics/src/rdp6/bitmap_stream/encoder.rs create mode 100644 crates/ironrdp-graphics/src/rdp6/bitmap_stream/mod.rs create mode 100644 crates/ironrdp-server/Cargo.toml create mode 100644 crates/ironrdp-server/README.md create mode 100644 crates/ironrdp-server/src/builder.rs create mode 100644 crates/ironrdp-server/src/capabilities.rs create mode 100644 crates/ironrdp-server/src/display.rs create mode 100644 crates/ironrdp-server/src/encoder/bitmap.rs create mode 100644 crates/ironrdp-server/src/encoder/mod.rs create mode 100644 crates/ironrdp-server/src/handler.rs create mode 100644 crates/ironrdp-server/src/lib.rs create mode 100644 crates/ironrdp-server/src/server.rs diff --git a/Cargo.lock b/Cargo.lock index effd914a..edb9169e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1704,13 +1704,25 @@ checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" name = "ironrdp" version = "0.5.0" dependencies = [ + "ironrdp-acceptor", "ironrdp-connector", "ironrdp-graphics", "ironrdp-input", "ironrdp-pdu", + "ironrdp-server", "ironrdp-session", ] +[[package]] +name = "ironrdp-acceptor" +version = "0.1.0" +dependencies = [ + "ironrdp-async", + "ironrdp-connector", + "ironrdp-pdu", + "tracing", +] + [[package]] name = "ironrdp-async" version = "0.1.0" @@ -1856,6 +1868,22 @@ dependencies = [ "der 0.7.8", ] +[[package]] +name = "ironrdp-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "ironrdp-acceptor", + "ironrdp-graphics", + "ironrdp-pdu", + "ironrdp-tokio", + "tokio", + "tokio-rustls 0.24.0", + "tracing", +] + [[package]] name = "ironrdp-session" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index db0a99e9..17be7374 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ expect-test = "1" ironrdp-async = { version = "0.1", path = "crates/ironrdp-async" } ironrdp-cliprdr = { version = "0.1", path = "crates/ironrdp-cliprdr" } ironrdp-connector = { version = "0.1", path = "crates/ironrdp-connector" } +ironrdp-acceptor = { version = "0.1", path = "crates/ironrdp-acceptor" } ironrdp-error = { version = "0.1", path = "crates/ironrdp-error" } ironrdp-futures = { version = "0.1", path = "crates/ironrdp-futures" } ironrdp-fuzzing = { path = "crates/ironrdp-fuzzing" } @@ -38,6 +39,7 @@ ironrdp-session = { version = "0.1", path = "crates/ironrdp-session" } ironrdp-testsuite-core = { path = "crates/ironrdp-testsuite-core" } ironrdp-tls = { version = "0.1", path = "crates/ironrdp-tls" } ironrdp-tokio = { version = "0.1", path = "crates/ironrdp-tokio" } +ironrdp-server = { version = "0.1", path = "crates/ironrdp-server" } ironrdp = { version = "0.5", path = "crates/ironrdp" } proptest = "1.1.0" rstest = "0.17.0" diff --git a/crates/ironrdp-acceptor/Cargo.toml b/crates/ironrdp-acceptor/Cargo.toml new file mode 100644 index 00000000..2fbd7051 --- /dev/null +++ b/crates/ironrdp-acceptor/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "ironrdp-acceptor" +version = "0.1.0" +readme = "README.md" +description = "" +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +authors.workspace = true +keywords.workspace = true +categories.workspace = true + +[lib] +doctest = false +test = false + +[dependencies] +ironrdp-pdu.workspace = true +ironrdp-connector.workspace = true +ironrdp-async.workspace = true +tracing.workspace = true diff --git a/crates/ironrdp-acceptor/README.md b/crates/ironrdp-acceptor/README.md new file mode 100644 index 00000000..ac6a31fb --- /dev/null +++ b/crates/ironrdp-acceptor/README.md @@ -0,0 +1,3 @@ +# IronRDP Acceptor + +State machine for the server connection acceptance sequence. diff --git a/crates/ironrdp-acceptor/src/channel_connection.rs b/crates/ironrdp-acceptor/src/channel_connection.rs new file mode 100644 index 00000000..e4243768 --- /dev/null +++ b/crates/ironrdp-acceptor/src/channel_connection.rs @@ -0,0 +1,168 @@ +use std::collections::HashSet; + +use ironrdp_connector::{ConnectorError, ConnectorErrorExt, ConnectorResult, Sequence, State, Written}; +use ironrdp_pdu as pdu; +use pdu::mcs; + +#[derive(Debug)] +pub struct ChannelConnectionSequence { + state: ChannelConnectionState, + user_channel_id: u16, + channels: HashSet, +} + +#[derive(Default, Debug)] +pub enum ChannelConnectionState { + #[default] + Consumed, + + WaitErectDomainRequest, + WaitAttachUserRequest, + SendAttachUserConfirm, + WaitChannelJoinRequest { + joined: HashSet, + }, + SendChannelJoinConfirm { + joined: HashSet, + channel_id: u16, + }, + AllJoined, +} + +impl State for ChannelConnectionState { + fn name(&self) -> &'static str { + match self { + Self::Consumed => "Consumed", + Self::WaitErectDomainRequest => "WaitErectDomainRequest", + Self::WaitAttachUserRequest => "WaitAttachUserRequest", + Self::SendAttachUserConfirm => "SendAttachUserConfirm", + Self::WaitChannelJoinRequest { .. } => "WaitChannelJoinRequest", + Self::SendChannelJoinConfirm { .. } => "SendChannelJoinConfirm", + Self::AllJoined { .. } => "AllJoined", + } + } + + fn is_terminal(&self) -> bool { + matches!(self, Self::AllJoined { .. }) + } + + fn as_any(&self) -> &dyn core::any::Any { + self + } +} + +impl Sequence for ChannelConnectionSequence { + fn next_pdu_hint(&self) -> Option<&dyn pdu::PduHint> { + match &self.state { + ChannelConnectionState::Consumed => None, + ChannelConnectionState::WaitErectDomainRequest => Some(&pdu::X224_HINT), + ChannelConnectionState::WaitAttachUserRequest => Some(&pdu::X224_HINT), + ChannelConnectionState::SendAttachUserConfirm => None, + ChannelConnectionState::WaitChannelJoinRequest { .. } => Some(&pdu::X224_HINT), + ChannelConnectionState::SendChannelJoinConfirm { .. } => None, + ChannelConnectionState::AllJoined { .. } => None, + } + } + + fn state(&self) -> &dyn State { + &self.state + } + + fn step(&mut self, input: &[u8], output: &mut Vec) -> ConnectorResult { + let (written, next_state) = match std::mem::take(&mut self.state) { + ChannelConnectionState::WaitErectDomainRequest => { + let erect_domain_request = + ironrdp_pdu::decode::(input).map_err(ConnectorError::pdu)?; + + debug!(message = ?erect_domain_request, "Received"); + + (Written::Nothing, ChannelConnectionState::WaitAttachUserRequest) + } + + ChannelConnectionState::WaitAttachUserRequest => { + let attach_user_request = + ironrdp_pdu::decode::(input).map_err(ConnectorError::pdu)?; + + debug!(message = ?attach_user_request, "Received"); + + (Written::Nothing, ChannelConnectionState::SendAttachUserConfirm) + } + + ChannelConnectionState::SendAttachUserConfirm => { + let attach_user_confirm = mcs::AttachUserConfirm { + result: 0, + initiator_id: self.user_channel_id, + }; + + debug!(message = ?attach_user_confirm, "Send"); + + let written = ironrdp_pdu::encode_buf(&attach_user_confirm, output).map_err(ConnectorError::pdu)?; + + ( + Written::from_size(written)?, + ChannelConnectionState::WaitChannelJoinRequest { joined: HashSet::new() }, + ) + } + + // TODO: support RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN + ChannelConnectionState::WaitChannelJoinRequest { joined } => { + let channel_request = + ironrdp_pdu::decode::(input).map_err(ConnectorError::pdu)?; + + debug!(message = ?channel_request, "Received"); + + let channel_id = channel_request.channel_id; + + ( + Written::Nothing, + ChannelConnectionState::SendChannelJoinConfirm { joined, channel_id }, + ) + } + + ChannelConnectionState::SendChannelJoinConfirm { mut joined, channel_id } => { + let channel_confirm = mcs::ChannelJoinConfirm { + result: 0, + initiator_id: self.user_channel_id, + requested_channel_id: channel_id, + channel_id, + }; + + debug!(message = ?channel_confirm, "Send"); + + let written = ironrdp_pdu::encode_buf(&channel_confirm, output).map_err(ConnectorError::pdu)?; + + joined.insert(channel_id); + + let state = if joined != self.channels { + ChannelConnectionState::WaitChannelJoinRequest { joined } + } else { + ChannelConnectionState::AllJoined {} + }; + + (Written::from_size(written)?, state) + } + + _ => unreachable!(), + }; + + self.state = next_state; + Ok(written) + } +} + +impl ChannelConnectionSequence { + pub fn new(user_channel_id: u16, io_channel_id: u16, other_channels: Vec) -> Self { + Self { + state: ChannelConnectionState::WaitErectDomainRequest, + user_channel_id, + channels: vec![user_channel_id, io_channel_id] + .into_iter() + .chain(other_channels.into_iter()) + .collect(), + } + } + + pub fn is_done(&self) -> bool { + self.state.is_terminal() + } +} diff --git a/crates/ironrdp-acceptor/src/connection.rs b/crates/ironrdp-acceptor/src/connection.rs new file mode 100644 index 00000000..c0f69e9e --- /dev/null +++ b/crates/ironrdp-acceptor/src/connection.rs @@ -0,0 +1,494 @@ +use std::io::Cursor; + +use ironrdp_connector::{ + legacy, ConnectorError, ConnectorErrorExt, ConnectorResult, DesktopSize, Sequence, State, Written, +}; +use ironrdp_pdu as pdu; +use pdu::rdp::capability_sets::CapabilitySet; +use pdu::rdp::headers::ShareControlPdu; +use pdu::{gcc, mcs, nego, rdp, PduParsing}; + +use crate::util::{self, wrap_share_data}; + +use super::channel_connection::ChannelConnectionSequence; +use super::finalization::FinalizationSequence; + +const IO_CHANNEL_ID: u16 = 1003; +const USER_CHANNEL_ID: u16 = 1002; + +pub struct Acceptor { + state: AcceptorState, + security: nego::SecurityProtocol, + io_channel_id: u16, + user_channel_id: u16, + desktop_size: DesktopSize, + server_capabilities: Vec, +} + +#[derive(Debug, Clone)] +pub struct AcceptorResult { + pub channels: Vec<(u16, gcc::Channel)>, + pub capabilities: Vec, +} + +impl Acceptor { + pub fn new(security: nego::SecurityProtocol, desktop_size: DesktopSize, capabilities: Vec) -> Self { + Self { + security, + state: AcceptorState::InitiationWaitRequest, + user_channel_id: USER_CHANNEL_ID, + io_channel_id: IO_CHANNEL_ID, + desktop_size, + server_capabilities: capabilities, + } + } + + pub fn reached_security_upgrade(&self) -> Option { + match self.state { + AcceptorState::SecurityUpgrade { .. } => Some(self.security), + _ => None, + } + } + + pub fn get_result(&mut self) -> Option { + match &self.state { + AcceptorState::Accepted { + channels, + client_capabilities, + } => Some(AcceptorResult { + channels: channels.clone(), + capabilities: client_capabilities.clone(), + }), + + _ => None, + } + } +} + +#[derive(Default, Debug)] +pub enum AcceptorState { + #[default] + Consumed, + + InitiationWaitRequest, + InitiationSendConfirm { + requested_protocol: nego::SecurityProtocol, + }, + SecurityUpgrade { + requested_protocol: nego::SecurityProtocol, + }, + BasicSettingsWaitInitial { + requested_protocol: nego::SecurityProtocol, + }, + BasicSettingsSendResponse { + requested_protocol: nego::SecurityProtocol, + early_capability: Option, + channels: Vec<(u16, gcc::Channel)>, + }, + ChannelConnection { + early_capability: Option, + channels: Vec<(u16, gcc::Channel)>, + connection: ChannelConnectionSequence, + }, + RdpSecurityCommencement { + early_capability: Option, + channels: Vec<(u16, gcc::Channel)>, + }, + SecureSettingsExchange { + early_capability: Option, + channels: Vec<(u16, gcc::Channel)>, + }, + LicensingExchange { + early_capability: Option, + channels: Vec<(u16, gcc::Channel)>, + }, + CapabilitiesSendServer { + early_capability: Option, + channels: Vec<(u16, gcc::Channel)>, + }, + MonitorLayoutSend { + channels: Vec<(u16, gcc::Channel)>, + }, + CapabilitiesWaitConfirm { + channels: Vec<(u16, gcc::Channel)>, + }, + ConnectionFinalization { + finalization: FinalizationSequence, + channels: Vec<(u16, gcc::Channel)>, + client_capabilities: Vec, + }, + Accepted { + channels: Vec<(u16, gcc::Channel)>, + client_capabilities: Vec, + }, +} + +impl State for AcceptorState { + fn name(&self) -> &'static str { + match self { + Self::Consumed => "Consumed", + Self::InitiationWaitRequest => "InitiationWaitRequest", + Self::InitiationSendConfirm { .. } => "InitiationSendConfirm", + Self::SecurityUpgrade { .. } => "SecurityUpgrade", + Self::BasicSettingsWaitInitial { .. } => "BasicSettingsWaitInitial", + Self::BasicSettingsSendResponse { .. } => "BasicSettingsSendResponse", + Self::ChannelConnection { .. } => "ChannelConnection", + Self::RdpSecurityCommencement { .. } => "RdpSecurityCommencement", + Self::SecureSettingsExchange { .. } => "SecureSettingsExchange", + Self::LicensingExchange { .. } => "LicensingExchange", + Self::CapabilitiesSendServer { .. } => "CapabilitiesSendServer", + Self::MonitorLayoutSend { .. } => "MonitorLayoutSend", + Self::CapabilitiesWaitConfirm { .. } => "CapabilitiesWaitConfirm", + Self::ConnectionFinalization { .. } => "ConnectionFinalization", + Self::Accepted { .. } => "Connected", + } + } + + fn is_terminal(&self) -> bool { + matches!(self, Self::Accepted { .. }) + } + + fn as_any(&self) -> &dyn core::any::Any { + self + } +} + +impl Sequence for Acceptor { + fn next_pdu_hint(&self) -> Option<&dyn pdu::PduHint> { + match &self.state { + AcceptorState::Consumed => None, + AcceptorState::InitiationWaitRequest => Some(&pdu::X224_HINT), + AcceptorState::InitiationSendConfirm { .. } => None, + AcceptorState::SecurityUpgrade { .. } => None, + AcceptorState::BasicSettingsWaitInitial { .. } => Some(&pdu::X224_HINT), + AcceptorState::BasicSettingsSendResponse { .. } => None, + AcceptorState::ChannelConnection { connection, .. } => connection.next_pdu_hint(), + AcceptorState::RdpSecurityCommencement { .. } => None, + AcceptorState::SecureSettingsExchange { .. } => Some(&pdu::X224_HINT), + AcceptorState::LicensingExchange { .. } => None, + AcceptorState::CapabilitiesSendServer { .. } => None, + AcceptorState::MonitorLayoutSend { .. } => None, + AcceptorState::CapabilitiesWaitConfirm { .. } => Some(&pdu::X224_HINT), + AcceptorState::ConnectionFinalization { finalization, .. } => finalization.next_pdu_hint(), + AcceptorState::Accepted { .. } => None, + } + } + + fn state(&self) -> &dyn State { + &self.state + } + + fn step(&mut self, input: &[u8], output: &mut Vec) -> ConnectorResult { + let (written, next_state) = match std::mem::take(&mut self.state) { + AcceptorState::InitiationWaitRequest => { + let connection_request = + ironrdp_pdu::decode::(input).map_err(ConnectorError::pdu)?; + + debug!(message = ?connection_request, "Received"); + + ( + Written::Nothing, + AcceptorState::InitiationSendConfirm { + requested_protocol: connection_request.protocol, + }, + ) + } + + AcceptorState::InitiationSendConfirm { requested_protocol } => { + let connection_confirm = nego::ConnectionConfirm::Response { + flags: nego::ResponseFlags::empty(), + protocol: self.security, + }; + + debug!(message = ?connection_confirm, "Send"); + + let written = ironrdp_pdu::encode_buf(&connection_confirm, output).map_err(ConnectorError::pdu)?; + + ( + Written::from_size(written)?, + AcceptorState::SecurityUpgrade { requested_protocol }, + ) + } + + AcceptorState::SecurityUpgrade { requested_protocol } => ( + Written::Nothing, + AcceptorState::BasicSettingsWaitInitial { requested_protocol }, + ), + + AcceptorState::BasicSettingsWaitInitial { requested_protocol } => { + let settings_initial = legacy::decode_x224_packet::(input)?; + + debug!(message = ?settings_initial, "Received"); + + let early_capability = settings_initial + .conference_create_request + .gcc_blocks + .core + .optional_data + .early_capability_flags; + + let channels = settings_initial + .conference_create_request + .gcc_blocks + .network + .map(|network| { + network + .channels + .into_iter() + .enumerate() + .map(|(i, c)| (i as u16 + self.io_channel_id + 1, c)) + .collect() + }) + .unwrap_or(Vec::new()); + + ( + Written::Nothing, + AcceptorState::BasicSettingsSendResponse { + requested_protocol, + early_capability, + channels, + }, + ) + } + + AcceptorState::BasicSettingsSendResponse { + requested_protocol, + early_capability, + channels, + } => { + let channel_ids: Vec = channels.iter().map(|&(i, _)| i).collect(); + let server_blocks = create_gcc_blocks(self.io_channel_id, channel_ids.clone(), requested_protocol); + let settings_response = mcs::ConnectResponse { + conference_create_response: gcc::ConferenceCreateResponse { + user_id: self.user_channel_id, + gcc_blocks: server_blocks, + }, + called_connect_id: 1, + domain_parameters: mcs::DomainParameters::target(), + }; + + debug!(message = ?settings_response, "Send"); + + let written = legacy::encode_x224_packet(&settings_response, output)?; + + ( + Written::from_size(written)?, + AcceptorState::ChannelConnection { + early_capability, + channels, + connection: ChannelConnectionSequence::new( + self.user_channel_id, + self.io_channel_id, + channel_ids, + ), + }, + ) + } + + AcceptorState::ChannelConnection { + early_capability, + channels, + mut connection, + } => { + let written = connection.step(input, output)?; + let state = if connection.is_done() { + AcceptorState::RdpSecurityCommencement { + early_capability, + channels, + } + } else { + AcceptorState::ChannelConnection { + early_capability, + channels, + connection, + } + }; + + (written, state) + } + + AcceptorState::RdpSecurityCommencement { + early_capability, + channels, + .. + } => ( + Written::Nothing, + AcceptorState::SecureSettingsExchange { + early_capability, + channels, + }, + ), + + AcceptorState::SecureSettingsExchange { + early_capability, + channels, + } => { + let data = pdu::decode::(input).map_err(ConnectorError::pdu)?; + + let client_info = rdp::ClientInfoPdu::from_buffer(Cursor::new(data.user_data))?; + + debug!(message = ?client_info, "Received"); + + ( + Written::Nothing, + AcceptorState::LicensingExchange { + early_capability, + channels, + }, + ) + } + + AcceptorState::LicensingExchange { + early_capability, + channels, + } => { + let license = rdp::server_license::InitialServerLicenseMessage::new_status_valid_client_message(); + + debug!(message = ?license, "Send"); + + let written = + util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &license, output)?; + + ( + Written::from_size(written)?, + AcceptorState::CapabilitiesSendServer { + early_capability, + channels, + }, + ) + } + + AcceptorState::CapabilitiesSendServer { + early_capability, + channels, + } => { + let demand_active = rdp::headers::ShareControlHeader { + share_id: 0, + pdu_source: self.io_channel_id, + share_control_pdu: rdp::headers::ShareControlPdu::ServerDemandActive( + rdp::capability_sets::ServerDemandActive { + pdu: rdp::capability_sets::DemandActive { + source_descriptor: "".into(), + capability_sets: self.server_capabilities.clone(), + }, + }, + ), + }; + + debug!(message = ?demand_active, "Send"); + + let written = util::encode_send_data_indication( + self.user_channel_id, + self.io_channel_id, + &demand_active, + output, + )?; + + let layout_flag = gcc::ClientEarlyCapabilityFlags::SUPPORT_MONITOR_LAYOUT_PDU; + let next_state = if early_capability.is_some_and(|c| c.contains(layout_flag)) { + AcceptorState::MonitorLayoutSend { channels } + } else { + AcceptorState::CapabilitiesWaitConfirm { channels } + }; + + (Written::from_size(written)?, next_state) + } + + AcceptorState::MonitorLayoutSend { channels } => { + let monitor_layout = + rdp::headers::ShareDataPdu::MonitorLayout(rdp::finalization_messages::MonitorLayoutPdu { + monitors: vec![gcc::Monitor { + left: 0, + top: 0, + right: self.desktop_size.width as i32, + bottom: self.desktop_size.height as i32, + flags: gcc::MonitorFlags::PRIMARY, + }], + }); + + debug!(message = ?monitor_layout, "Send"); + + let share_data = wrap_share_data(monitor_layout, self.io_channel_id); + + let written = + util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &share_data, output)?; + + ( + Written::from_size(written)?, + AcceptorState::CapabilitiesWaitConfirm { channels }, + ) + } + + AcceptorState::CapabilitiesWaitConfirm { channels } => { + let data = pdu::decode::(input).map_err(ConnectorError::pdu)?; + + let capabilities_confirm = rdp::headers::ShareControlHeader::from_buffer(Cursor::new(data.user_data))?; + + debug!(message = ?capabilities_confirm, "Received"); + + let ShareControlPdu::ClientConfirmActive(confirm) = capabilities_confirm.share_control_pdu else { + return Err(ConnectorError::general("expected client confirm active")); + }; + + ( + Written::Nothing, + AcceptorState::ConnectionFinalization { + channels, + finalization: FinalizationSequence::new(self.user_channel_id, self.io_channel_id), + client_capabilities: confirm.pdu.capability_sets, + }, + ) + } + + AcceptorState::ConnectionFinalization { + mut finalization, + channels, + client_capabilities, + } => { + let written = finalization.step(input, output)?; + let state = if finalization.is_done() { + AcceptorState::Accepted { + channels, + client_capabilities, + } + } else { + AcceptorState::ConnectionFinalization { + finalization, + channels, + client_capabilities, + } + }; + + (written, state) + } + + _ => unreachable!(), + }; + + self.state = next_state; + Ok(written) + } +} + +fn create_gcc_blocks( + io_channel: u16, + channel_ids: Vec, + requested: nego::SecurityProtocol, +) -> gcc::ServerGccBlocks { + pdu::gcc::ServerGccBlocks { + core: gcc::ServerCoreData { + version: gcc::RdpVersion::V5_PLUS, + optional_data: gcc::ServerCoreOptionalData { + client_requested_protocols: Some(requested), + early_capability_flags: None, + }, + }, + security: gcc::ServerSecurityData::no_security(), + network: gcc::ServerNetworkData { + channel_ids, + io_channel, + }, + message_channel: None, + multi_transport_channel: None, + } +} diff --git a/crates/ironrdp-acceptor/src/finalization.rs b/crates/ironrdp-acceptor/src/finalization.rs new file mode 100644 index 00000000..19e4b444 --- /dev/null +++ b/crates/ironrdp-acceptor/src/finalization.rs @@ -0,0 +1,224 @@ +use std::io::Cursor; + +use ironrdp_connector::{ConnectorError, ConnectorErrorExt, ConnectorResult, Sequence, State, Written}; +use ironrdp_pdu as pdu; +use pdu::{rdp, PduParsing}; + +use crate::util::{self, wrap_share_data}; + +#[derive(Debug)] +pub struct FinalizationSequence { + state: FinalizationState, + user_channel_id: u16, + io_channel_id: u16, +} + +#[derive(Default, Debug)] +pub enum FinalizationState { + #[default] + Consumed, + + WaitSynchronize, + WaitControlCooperate, + WaitRequestControl, + WaitFontList, + + SendSynchronizeConfirm, + SendControlCooperateConfirm, + SendGrantedControlConfirm, + SendFontMap, + + Finished, +} + +impl State for FinalizationState { + fn name(&self) -> &'static str { + match self { + Self::Consumed => "Consumed", + Self::WaitSynchronize => "WaitSynchronize", + Self::WaitControlCooperate => "WaitControlCooperate", + Self::WaitRequestControl => "WaitRequestControl", + Self::WaitFontList => "WaitFontList", + Self::SendSynchronizeConfirm => "SendSynchronizeConfirm", + Self::SendControlCooperateConfirm => "SendControlCooperateConfirm", + Self::SendGrantedControlConfirm => "SendGrantedControlConfirm", + Self::SendFontMap => "SendFontMap", + Self::Finished => "Finished", + } + } + + fn is_terminal(&self) -> bool { + matches!(self, Self::Finished { .. }) + } + + fn as_any(&self) -> &dyn core::any::Any { + self + } +} + +impl Sequence for FinalizationSequence { + fn next_pdu_hint(&self) -> Option<&dyn pdu::PduHint> { + match &self.state { + FinalizationState::Consumed => None, + FinalizationState::WaitSynchronize => Some(&pdu::X224Hint), + FinalizationState::WaitControlCooperate => Some(&pdu::X224Hint), + FinalizationState::WaitRequestControl => Some(&pdu::X224Hint), + FinalizationState::WaitFontList => Some(&pdu::X224Hint), + FinalizationState::SendSynchronizeConfirm => None, + FinalizationState::SendControlCooperateConfirm => None, + FinalizationState::SendGrantedControlConfirm => None, + FinalizationState::SendFontMap => None, + FinalizationState::Finished => None, + } + } + + fn state(&self) -> &dyn State { + &self.state + } + + fn step(&mut self, input: &[u8], output: &mut Vec) -> ConnectorResult { + let (written, next_state) = match std::mem::take(&mut self.state) { + FinalizationState::WaitSynchronize => { + let data = pdu::decode::(input).map_err(ConnectorError::pdu)?; + + let synchronize = rdp::headers::ShareControlHeader::from_buffer(Cursor::new(data.user_data))?; + + debug!(message = ?synchronize, "Received"); + + (Written::Nothing, FinalizationState::WaitControlCooperate) + } + + FinalizationState::WaitControlCooperate => { + let data = pdu::decode::(input).map_err(ConnectorError::pdu)?; + + let cooperate = rdp::headers::ShareControlHeader::from_buffer(Cursor::new(data.user_data))?; + + debug!(message = ?cooperate, "Received"); + + (Written::Nothing, FinalizationState::WaitRequestControl) + } + + FinalizationState::WaitRequestControl => { + let data = pdu::decode::(input).map_err(ConnectorError::pdu)?; + + let control = rdp::headers::ShareControlHeader::from_buffer(Cursor::new(data.user_data))?; + + debug!(message = ?control, "Received"); + + (Written::Nothing, FinalizationState::WaitFontList) + } + + FinalizationState::WaitFontList => { + let data = pdu::decode::(input).map_err(ConnectorError::pdu)?; + + let font_list = rdp::headers::ShareControlHeader::from_buffer(Cursor::new(data.user_data))?; + + debug!(message = ?font_list, "Received"); + + (Written::Nothing, FinalizationState::SendSynchronizeConfirm) + } + + FinalizationState::SendSynchronizeConfirm => { + let synchronize_confirm = create_synchronize_confirm(); + + debug!(message = ?synchronize_confirm, "Send"); + + let share_data = wrap_share_data(synchronize_confirm, self.io_channel_id); + let written = + util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &share_data, output)?; + + ( + Written::from_size(written)?, + FinalizationState::SendControlCooperateConfirm, + ) + } + + FinalizationState::SendControlCooperateConfirm => { + let cooperate_confirm = create_cooperate_confirm(); + + debug!(message = ?cooperate_confirm, "Send"); + + let share_data = wrap_share_data(cooperate_confirm, self.io_channel_id); + let written = + util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &share_data, output)?; + + ( + Written::from_size(written)?, + FinalizationState::SendGrantedControlConfirm, + ) + } + + FinalizationState::SendGrantedControlConfirm => { + let control_confirm = create_control_confirm(self.user_channel_id); + + debug!(message = ?control_confirm, "Send"); + + let share_data = wrap_share_data(control_confirm, self.io_channel_id); + let written = + util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &share_data, output)?; + + (Written::from_size(written)?, FinalizationState::SendFontMap) + } + + FinalizationState::SendFontMap => { + let font_map = create_font_map(); + + debug!(message = ?font_map, "Send"); + + let share_data = wrap_share_data(font_map, self.io_channel_id); + let written = + util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &share_data, output)?; + + (Written::from_size(written)?, FinalizationState::Finished) + } + + _ => unreachable!(), + }; + + self.state = next_state; + Ok(written) + } +} + +impl FinalizationSequence { + pub fn new(user_channel_id: u16, io_channel_id: u16) -> Self { + Self { + state: FinalizationState::WaitSynchronize, + user_channel_id, + io_channel_id, + } + } + + pub fn is_done(&self) -> bool { + self.state.is_terminal() + } +} + +fn create_synchronize_confirm() -> rdp::headers::ShareDataPdu { + rdp::headers::ShareDataPdu::Synchronize(rdp::finalization_messages::SynchronizePdu { target_user_id: 0 }) +} + +fn create_cooperate_confirm() -> rdp::headers::ShareDataPdu { + rdp::headers::ShareDataPdu::Control(rdp::finalization_messages::ControlPdu { + action: rdp::finalization_messages::ControlAction::Cooperate, + grant_id: 0, + control_id: 0, + }) +} + +fn create_control_confirm(user_id: u16) -> rdp::headers::ShareDataPdu { + rdp::headers::ShareDataPdu::Control(rdp::finalization_messages::ControlPdu { + action: rdp::finalization_messages::ControlAction::GrantedControl, + grant_id: user_id, + control_id: u32::from(pdu::rdp::capability_sets::SERVER_CHANNEL_ID), + }) +} + +fn create_font_map() -> rdp::headers::ShareDataPdu { + rdp::headers::ShareDataPdu::FontMap(rdp::finalization_messages::FontPdu { + number: 1, // TODO: fields + total_number: 1, + flags: rdp::finalization_messages::SequenceFlags::empty(), + entry_size: 0, + }) +} diff --git a/crates/ironrdp-acceptor/src/lib.rs b/crates/ironrdp-acceptor/src/lib.rs new file mode 100644 index 00000000..f845feba --- /dev/null +++ b/crates/ironrdp-acceptor/src/lib.rs @@ -0,0 +1,98 @@ +#[macro_use] +extern crate tracing; + +use ironrdp_async::{Framed, FramedRead, FramedWrite, StreamWrapper}; +use ironrdp_connector::{custom_err, ConnectorResult, Sequence, Written}; + +mod channel_connection; +mod connection; +mod finalization; +mod util; + +pub use connection::{Acceptor, AcceptorResult}; +pub use ironrdp_connector::DesktopSize; + +pub enum BeginResult +where + S: StreamWrapper, +{ + ShouldUpgrade(S::InnerStream), + Continue(Framed), +} + +pub async fn accept_begin(mut framed: Framed, acceptor: &mut Acceptor) -> ConnectorResult> +where + S: FramedRead + FramedWrite + StreamWrapper, +{ + let mut buf = Vec::new(); + + loop { + if let Some(security) = acceptor.reached_security_upgrade() { + let result = if security.is_empty() { + BeginResult::Continue(framed) + } else { + BeginResult::ShouldUpgrade(framed.into_inner_no_leftover()) + }; + + return Ok(result); + } + + single_accept_state(&mut framed, acceptor, &mut buf).await?; + } +} + +pub async fn accept_finalize( + mut framed: Framed, + acceptor: &mut Acceptor, +) -> ConnectorResult<(Framed, AcceptorResult)> +where + S: FramedRead + FramedWrite, +{ + let mut buf = Vec::new(); + + loop { + if let Some(result) = acceptor.get_result() { + return Ok((framed, result)); + } + + single_accept_state(&mut framed, acceptor, &mut buf).await?; + } +} + +async fn single_accept_state( + framed: &mut Framed, + acceptor: &mut Acceptor, + buf: &mut Vec, +) -> ConnectorResult +where + S: FramedRead + FramedWrite, +{ + let written = if let Some(next_pdu_hint) = acceptor.next_pdu_hint() { + debug!( + acceptor.state = acceptor.state().name(), + hint = ?next_pdu_hint, + "Wait for PDU" + ); + + let pdu = framed + .read_by_hint(next_pdu_hint) + .await + .map_err(|e| custom_err!("read frame by hint", e))?; + + trace!(length = pdu.len(), "PDU received"); + + acceptor.step(&pdu, buf)? + } else { + acceptor.step_no_input(buf)? + }; + + if let Some(len) = written.size() { + trace!(length = len, "Send response"); + framed + .write_all(&buf[..len]) + .await + .map_err(|e| custom_err!("write all", e))?; + } + + Ok(written) +} diff --git a/crates/ironrdp-acceptor/src/util.rs b/crates/ironrdp-acceptor/src/util.rs new file mode 100644 index 00000000..b4a0c272 --- /dev/null +++ b/crates/ironrdp-acceptor/src/util.rs @@ -0,0 +1,43 @@ +use std::borrow::Cow; + +use ironrdp_connector::{ConnectorError, ConnectorErrorExt, ConnectorResult}; +use ironrdp_pdu::{rdp, PduParsing}; + +pub fn encode_send_data_indication( + initiator_id: u16, + channel_id: u16, + user_msg: &T, + buf: &mut Vec, +) -> ConnectorResult +where + T: PduParsing, + ConnectorError: From, +{ + let user_data_len = user_msg.buffer_length(); + let mut user_data = Vec::with_capacity(user_data_len); + + user_msg.to_buffer(&mut user_data)?; + + let pdu = ironrdp_pdu::mcs::SendDataIndication { + initiator_id, + channel_id, + user_data: Cow::Owned(user_data), + }; + + let written = ironrdp_pdu::encode_buf(&pdu, buf).map_err(ConnectorError::pdu)?; + + Ok(written) +} + +pub fn wrap_share_data(pdu: rdp::headers::ShareDataPdu, io_channel_id: u16) -> rdp::headers::ShareControlHeader { + rdp::headers::ShareControlHeader { + share_id: 0, + pdu_source: io_channel_id, + share_control_pdu: rdp::headers::ShareControlPdu::Data(rdp::headers::ShareDataHeader { + share_data_pdu: pdu, + stream_priority: rdp::headers::StreamPriority::Undefined, + compression_flags: rdp::headers::CompressionFlags::empty(), + compression_type: rdp::client_info::CompressionType::K8, + }), + } +} diff --git a/crates/ironrdp-fuzzing/src/oracles/mod.rs b/crates/ironrdp-fuzzing/src/oracles/mod.rs index f53ddb3c..c305ead5 100644 --- a/crates/ironrdp-fuzzing/src/oracles/mod.rs +++ b/crates/ironrdp-fuzzing/src/oracles/mod.rs @@ -86,7 +86,25 @@ pub fn rle_decompress_bitmap(input: BitmapInput) { let _ = ironrdp_graphics::rle::decompress_8_bpp(input.src, &mut out, input.width, input.height); } -pub fn rdp6_decode_bitmap_stream_to_rgb24(input: BitmapInput) { +pub fn rdp6_encode_bitmap_stream(input: &BitmapInput) { + use ironrdp_graphics::rdp6::{BitmapStreamEncoder, RgbAChannels, RgbChannels}; + + let mut out = vec![0; input.src.len() * 2]; + + let _ = BitmapStreamEncoder::new(input.width.into(), input.height.into()).encode_bitmap::( + input.src, + out.as_mut_slice(), + false, + ); + + let _ = BitmapStreamEncoder::new(input.width.into(), input.height.into()).encode_bitmap::( + input.src, + out.as_mut_slice(), + true, + ); +} + +pub fn rdp6_decode_bitmap_stream_to_rgb24(input: &BitmapInput) { use ironrdp_graphics::rdp6::BitmapStreamDecoder; let mut out = Vec::new(); diff --git a/crates/ironrdp-graphics/src/image_processing.rs b/crates/ironrdp-graphics/src/image_processing.rs index 7b34053b..a415d9ea 100644 --- a/crates/ironrdp-graphics/src/image_processing.rs +++ b/crates/ironrdp-graphics/src/image_processing.rs @@ -88,6 +88,24 @@ pub enum PixelFormat { RgbX32 = 537_069_704, } +impl TryFrom for PixelFormat { + type Error = (); + + fn try_from(value: u32) -> Result { + match value { + 536_971_400 => Ok(PixelFormat::ARgb32), + 536_938_632 => Ok(PixelFormat::XRgb32), + 537_036_936 => Ok(PixelFormat::ABgr32), + 537_004_168 => Ok(PixelFormat::XBgr32), + 537_168_008 => Ok(PixelFormat::BgrA32), + 537_135_240 => Ok(PixelFormat::BgrX32), + 537_102_472 => Ok(PixelFormat::RgbA32), + 537_069_704 => Ok(PixelFormat::RgbX32), + _ => Err(()), + } + } +} + impl PixelFormat { pub const fn bytes_per_pixel(self) -> u8 { match self { diff --git a/crates/ironrdp-graphics/src/rdp6/bitmap_stream.rs b/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs similarity index 74% rename from crates/ironrdp-graphics/src/rdp6/bitmap_stream.rs rename to crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs index c2355164..ac622fc1 100644 --- a/crates/ironrdp-graphics/src/rdp6/bitmap_stream.rs +++ b/crates/ironrdp-graphics/src/rdp6/bitmap_stream/decoder.rs @@ -1,16 +1,16 @@ -use ironrdp_pdu::bitmap::rdp6::{BitmapStream as BitmapStreamPdu, ColorPlanes}; +use ironrdp_pdu::bitmap::rdp6::{BitmapStream as BitmapStreamPdu, ColorPlaneDefinition}; use ironrdp_pdu::{decode, PduError}; use thiserror::Error; use crate::color_conversion::Rgb; -use crate::rdp6::rle::{decompress_8bpp_plane, RleError}; +use crate::rdp6::rle::{decompress_8bpp_plane, RleDecodeError}; #[derive(Debug, Error)] pub enum BitmapDecodeError { #[error("Failed to decode RDP6 bitmap stream PDU: {0}")] Pdu(#[from] PduError), #[error("Failed to perform RLE decompression of RDP6 bitmap stream: {0}")] - Rle(#[from] RleError), + Rle(#[from] RleDecodeError), #[error("Color plane data size provided in PDU is not sufficient to reconstruct the bitmap")] InvalidUncompressedDataSize, } @@ -78,7 +78,7 @@ impl<'a> BitmapStreamDecoderImpl<'a> { } fn decompress_planes(&'a self, aux_buffer: &'a mut Vec) -> Result<&'a [u8], BitmapDecodeError> { - let planes = if self.bitmap.enable_rle_compression { + let planes = if self.bitmap.header.enable_rle_compression { // We don't care for the previous content, just resize it to fit the data aux_buffer.resize(self.uncompressed_planes_size, 0); let uncompressed_planes_buffer = &mut aux_buffer[..self.uncompressed_planes_size]; @@ -87,7 +87,7 @@ impl<'a> BitmapStreamDecoderImpl<'a> { let mut src_offset = 0; // Decompress Alpha plane - if self.bitmap.use_alpha { + if self.bitmap.header.use_alpha { // Decompress alpha alpha, but discard it (always 0xFF) src_offset += decompress_8bpp_plane( &compressed[src_offset..], @@ -124,7 +124,11 @@ impl<'a> BitmapStreamDecoderImpl<'a> { &uncompressed_planes_buffer[..self.uncompressed_planes_size] } else { // Discard alpha plane - let color_planes_offset = if self.bitmap.use_alpha { self.full_plane_size } else { 0 }; + let color_planes_offset = if self.bitmap.header.use_alpha { + self.full_plane_size + } else { + 0 + }; let expected_data_size = color_planes_offset + self.uncompressed_planes_size; @@ -194,12 +198,12 @@ impl<'a> BitmapStreamDecoderImpl<'a> { // Reserve enough space for decoded RGB channels data dst.reserve(self.image_height * self.image_width * 3); - match self.bitmap.color_planes { - ColorPlanes::Argb { .. } => { + match self.bitmap.header.color_plane_definition { + ColorPlaneDefinition::Argb => { let color_planes = self.decompress_planes(aux_buffer)?; self.write_argb_planes_to_rgb24(color_planes, dst); } - ColorPlanes::AYCoCg { + ColorPlaneDefinition::AYCoCg { color_loss_level, use_chroma_subsampling, .. @@ -207,7 +211,7 @@ impl<'a> BitmapStreamDecoderImpl<'a> { let params: AYCoCgParams = AYCoCgParams { color_loss_level, chroma_subsampling: use_chroma_subsampling, - alpha: self.bitmap.use_alpha, + alpha: self.bitmap.header.use_alpha, }; let color_planes = self.decompress_planes(aux_buffer)?; self.write_aycocg_planes_to_rgb24(params, color_planes, dst); @@ -261,95 +265,3 @@ impl BitmapStreamDecoder { decoder.decode(dst, &mut self.planes_buffer) } } - -#[cfg(test)] -mod tests { - use super::*; - - fn assert_decoded_image(pdu: &[u8], expected_bmp: &[u8], width: usize, height: usize) { - let expected_bmp = bmp::from_reader(&mut std::io::Cursor::new(expected_bmp)).unwrap(); - let mut expected_buffer = vec![0; width * height * 3]; - for (idx, (x, y)) in expected_bmp.coordinates().enumerate() { - let pixel = expected_bmp.get_pixel(x, y); - - let offset = idx * 3; - expected_buffer[offset] = pixel.r; - expected_buffer[offset + 1] = pixel.g; - expected_buffer[offset + 2] = pixel.b; - } - - let mut actual = Vec::new(); - - BitmapStreamDecoder::default() - .decode_bitmap_stream_to_rgb24(pdu, &mut actual, width, height) - .unwrap(); - - assert_eq!(actual.as_slice(), expected_buffer.as_slice()); - } - - #[test] - fn decode_32x64_rgb_raw() { - // RGB (No alpha), no RLE - assert_decoded_image( - include_bytes!("test_assets/32x64_rgb_raw.bin"), - include_bytes!("test_assets/32x64_rgb_raw.bmp"), - 32, - 64, - ); - } - - #[test] - fn decode_64x24_argb_rle() { - // ARGB (With alpha), RLE - assert_decoded_image( - include_bytes!("test_assets/64x24_argb_rle.bin"), - include_bytes!("test_assets/64x24_argb_rle.bmp"), - 64, - 24, - ); - } - - #[test] - fn decode_64x64_aycocg_rle() { - // AYCoCg (With alpha), RLE, no chroma subsampling - assert_decoded_image( - include_bytes!("test_assets/64x64_aycocg_rle.bin"), - include_bytes!("test_assets/64x64_aycocg_rle.bmp"), - 64, - 64, - ); - } - - #[test] - fn decode_64x64_ycocg_rle_ss() { - // AYCoCg (No alpha), RLE, with chroma subsampling - assert_decoded_image( - include_bytes!("test_assets/64x64_ycocg_rle_ss.bin"), - include_bytes!("test_assets/64x64_ycocg_rle_ss.bmp"), - 64, - 64, - ); - } - - #[test] - fn decode_64x35_ycocg_rle_ss() { - // AYCoCg (No alpha), RLE, with chroma subsampling + odd resolution - assert_decoded_image( - include_bytes!("test_assets/64x35_ycocg_rle_ss.bin"), - include_bytes!("test_assets/64x35_ycocg_rle_ss.bmp"), - 64, - 35, - ); - } - - #[test] - fn decode_64x64_ycocg_raw_ss() { - // AYCoCg (No alpha), no RLE, with chroma subsampling - assert_decoded_image( - include_bytes!("test_assets/64x64_ycocg_raw_ss.bin"), - include_bytes!("test_assets/64x64_ycocg_raw_ss.bmp"), - 64, - 64, - ); - } -} diff --git a/crates/ironrdp-graphics/src/rdp6/bitmap_stream/encoder.rs b/crates/ironrdp-graphics/src/rdp6/bitmap_stream/encoder.rs new file mode 100644 index 00000000..233df66b --- /dev/null +++ b/crates/ironrdp-graphics/src/rdp6/bitmap_stream/encoder.rs @@ -0,0 +1,283 @@ +use ironrdp_pdu::{ + bitmap::rdp6::{BitmapStreamHeader, ColorPlaneDefinition}, + cursor::WriteCursor, + PduError, +}; +use thiserror::Error; + +use crate::rdp6::rle::{compress_8bpp_plane, RleEncodeError}; + +#[derive(Debug, Error)] +pub enum BitmapEncodeError { + #[error("Failed to rle compress")] + Rle(RleEncodeError), + #[error("Failed to encode pdu")] + Pdu(PduError), +} + +pub trait ColorChannels { + const STRIDE: usize; + const R: usize; + const G: usize; + const B: usize; +} + +pub trait AlphaChannel { + const A: usize; +} + +pub trait PixelFormat { + const STRIDE: usize; + + fn r(pixel: &[u8]) -> u8; + fn g(pixel: &[u8]) -> u8; + fn b(pixel: &[u8]) -> u8; +} + +pub trait PixelAlpha: PixelFormat { + fn a(pixel: &[u8]) -> u8; +} + +impl PixelFormat for T +where + T: ColorChannels, +{ + const STRIDE: usize = T::STRIDE; + + fn r(pixel: &[u8]) -> u8 { + pixel[T::R] + } + + fn g(pixel: &[u8]) -> u8 { + pixel[T::G] + } + + fn b(pixel: &[u8]) -> u8 { + pixel[T::B] + } +} + +impl PixelAlpha for T +where + T: ColorChannels + AlphaChannel, +{ + fn a(pixel: &[u8]) -> u8 { + pixel[T::A] + } +} + +pub struct RgbChannels; + +impl ColorChannels for RgbChannels { + const STRIDE: usize = 3; + const R: usize = 0; + const G: usize = 1; + const B: usize = 2; +} + +pub struct ARgbChannels; + +impl ColorChannels for ARgbChannels { + const STRIDE: usize = 4; + const R: usize = 1; + const G: usize = 2; + const B: usize = 3; +} + +impl AlphaChannel for ARgbChannels { + const A: usize = 0; +} + +pub struct RgbAChannels; + +impl ColorChannels for RgbAChannels { + const STRIDE: usize = 4; + const R: usize = 0; + const G: usize = 1; + const B: usize = 2; +} + +impl AlphaChannel for RgbAChannels { + const A: usize = 3; +} + +pub struct ABgrChannels; + +impl ColorChannels for ABgrChannels { + const STRIDE: usize = 4; + const R: usize = 3; + const G: usize = 2; + const B: usize = 1; +} + +impl AlphaChannel for ABgrChannels { + const A: usize = 0; +} + +pub struct BgrAChannels; + +impl ColorChannels for BgrAChannels { + const STRIDE: usize = 4; + const R: usize = 2; + const G: usize = 1; + const B: usize = 0; +} + +impl AlphaChannel for BgrAChannels { + const A: usize = 3; +} + +impl BitmapEncodeError { + fn rle(e: RleEncodeError) -> Self { + Self::Rle(e) + } +} + +pub struct BitmapStreamEncoder { + width: usize, + height: usize, +} + +impl BitmapStreamEncoder { + pub fn new(width: usize, height: usize) -> Self { + Self { width, height } + } + + pub fn encode_channels_stream( + &mut self, + (r, g, b): (R, G, B), + dst: &mut [u8], + rle: bool, + ) -> Result + where + R: Iterator, + G: Iterator, + B: Iterator, + { + let mut cursor = WriteCursor::new(dst); + + let header = BitmapStreamHeader { + enable_rle_compression: rle, + use_alpha: false, + color_plane_definition: ColorPlaneDefinition::Argb, + }; + + ironrdp_pdu::encode_cursor(&header, &mut cursor).map_err(BitmapEncodeError::Pdu)?; + + match rle { + true => { + compress_8bpp_plane(r, &mut cursor, self.width, self.height).map_err(BitmapEncodeError::Rle)?; + compress_8bpp_plane(g, &mut cursor, self.width, self.height).map_err(BitmapEncodeError::Rle)?; + compress_8bpp_plane(b, &mut cursor, self.width, self.height).map_err(BitmapEncodeError::Rle)?; + } + + false => { + let remaining = cursor.remaining().len(); + let needed = self.width * self.height * 3 + 1; + if needed > remaining { + return Err(BitmapEncodeError::Pdu( + ::not_enough_bytes("BitmapStreamData", remaining, needed), + )); + } + + for byte in r.chain(g).chain(b) { + cursor.write_u8(byte); + } + cursor.write_u8(0u8); + } + }; + + Ok(cursor.pos()) + } + + pub fn encode_pixels_stream<'a, I, F>( + &mut self, + data: I, + dst: &mut [u8], + rle: bool, + ) -> Result + where + F: PixelFormat, + I: Iterator + Clone, + { + let r = data.clone().map(F::r); + let g = data.clone().map(F::g); + let b = data.map(F::b); + + self.encode_channels_stream((r, g, b), dst, rle) + } + + pub fn encode_bitmap(&mut self, src: &[u8], dst: &mut [u8], rle: bool) -> Result + where + F: PixelFormat, + { + let r = src.chunks_exact(F::STRIDE).map(F::r); + let g = src.chunks_exact(F::STRIDE).map(F::g); + let b = src.chunks_exact(F::STRIDE).map(F::b); + + self.encode_channels_stream((r, g, b), dst, rle) + } +} + +impl BitmapStreamEncoder { + pub fn encode_channels_stream_alpha( + &mut self, + (r, g, b, a): (R, G, B, A), + dst: &mut [u8], + rle: bool, + ) -> Result + where + R: Iterator, + G: Iterator, + B: Iterator, + A: Iterator, + { + let mut cursor = WriteCursor::new(dst); + + let header = BitmapStreamHeader { + enable_rle_compression: rle, + use_alpha: false, + color_plane_definition: ColorPlaneDefinition::Argb, + }; + + ironrdp_pdu::encode_cursor(&header, &mut cursor).map_err(BitmapEncodeError::Pdu)?; + + match rle { + true => { + compress_8bpp_plane(a, &mut cursor, self.width, self.height).map_err(BitmapEncodeError::rle)?; + compress_8bpp_plane(r, &mut cursor, self.width, self.height).map_err(BitmapEncodeError::rle)?; + compress_8bpp_plane(g, &mut cursor, self.width, self.height).map_err(BitmapEncodeError::rle)?; + compress_8bpp_plane(b, &mut cursor, self.width, self.height).map_err(BitmapEncodeError::rle)?; + } + + false => { + let remaining = cursor.remaining().len(); + let needed = self.width * self.height * 4 + 1; + if needed > remaining { + return Err(BitmapEncodeError::Pdu( + ::not_enough_bytes("BitmapStreamData", remaining, needed), + )); + } + + for byte in a.chain(r).chain(g).chain(b) { + cursor.write_u8(byte); + } + cursor.write_u8(0u8); + } + }; + + Ok(cursor.pos()) + } + + pub fn encode_bitmap_alpha(&mut self, src: &[u8], dst: &mut [u8], rle: bool) -> Result + where + F: PixelFormat + PixelAlpha, + { + let r = src.chunks_exact(F::STRIDE).map(F::r); + let g = src.chunks_exact(F::STRIDE).map(F::g); + let b = src.chunks_exact(F::STRIDE).map(F::b); + let a = src.chunks_exact(F::STRIDE).map(F::a); + + self.encode_channels_stream_alpha((r, g, b, a), dst, rle) + } +} diff --git a/crates/ironrdp-graphics/src/rdp6/bitmap_stream/mod.rs b/crates/ironrdp-graphics/src/rdp6/bitmap_stream/mod.rs new file mode 100644 index 00000000..90f0f54c --- /dev/null +++ b/crates/ironrdp-graphics/src/rdp6/bitmap_stream/mod.rs @@ -0,0 +1,179 @@ +mod decoder; +mod encoder; + +pub use decoder::*; +pub use encoder::*; + +#[cfg(test)] +mod tests { + use super::*; + + fn buffer_from_bmp(bmp_image: &[u8], width: usize, height: usize) -> Vec { + let expected_bmp = bmp::from_reader(&mut std::io::Cursor::new(bmp_image)).unwrap(); + + let mut expected_buffer = vec![0; width * height * 3]; + for (idx, (x, y)) in expected_bmp.coordinates().enumerate() { + let pixel = expected_bmp.get_pixel(x, y); + + let offset = idx * 3; + expected_buffer[offset] = pixel.r; + expected_buffer[offset + 1] = pixel.g; + expected_buffer[offset + 2] = pixel.b; + } + + expected_buffer + } + + fn assert_decoded_image(pdu: &[u8], expected_bmp: &[u8], width: usize, height: usize) { + let expected_buffer = buffer_from_bmp(expected_bmp, width, height); + + let mut actual = Vec::new(); + BitmapStreamDecoder::default() + .decode_bitmap_stream_to_rgb24(pdu, &mut actual, width, height) + .unwrap(); + + assert_eq!(actual.as_slice(), expected_buffer.as_slice()); + } + + #[test] + fn decode_32x64_rgb_raw() { + // RGB (No alpha), no RLE + assert_decoded_image( + include_bytes!("../test_assets/32x64_rgb_raw.bin"), + include_bytes!("../test_assets/32x64_rgb_raw.bmp"), + 32, + 64, + ); + } + + #[test] + fn decode_64x24_argb_rle() { + // ARGB (With alpha), RLE + assert_decoded_image( + include_bytes!("../test_assets/64x24_argb_rle.bin"), + include_bytes!("../test_assets/64x24_argb_rle.bmp"), + 64, + 24, + ); + } + + #[test] + fn decode_64x64_aycocg_rle() { + // AYCoCg (With alpha), RLE, no chroma subsampling + assert_decoded_image( + include_bytes!("../test_assets/64x64_aycocg_rle.bin"), + include_bytes!("../test_assets/64x64_aycocg_rle.bmp"), + 64, + 64, + ); + } + + #[test] + fn decode_64x64_ycocg_rle_ss() { + // AYCoCg (No alpha), RLE, with chroma subsampling + assert_decoded_image( + include_bytes!("../test_assets/64x64_ycocg_rle_ss.bin"), + include_bytes!("../test_assets/64x64_ycocg_rle_ss.bmp"), + 64, + 64, + ); + } + + #[test] + fn decode_64x35_ycocg_rle_ss() { + // AYCoCg (No alpha), RLE, with chroma subsampling + odd resolution + assert_decoded_image( + include_bytes!("../test_assets/64x35_ycocg_rle_ss.bin"), + include_bytes!("../test_assets/64x35_ycocg_rle_ss.bmp"), + 64, + 35, + ); + } + + #[test] + fn decode_64x64_ycocg_raw_ss() { + // AYCoCg (No alpha), no RLE, with chroma subsampling + assert_decoded_image( + include_bytes!("../test_assets/64x64_ycocg_raw_ss.bin"), + include_bytes!("../test_assets/64x64_ycocg_raw_ss.bmp"), + 64, + 64, + ); + } + + fn assert_encoded_image(expected_pdu: &[u8], bmp: &[u8], width: usize, height: usize, rle: bool) { + let image = buffer_from_bmp(bmp, width, height); + + let mut pdu = vec![0; width * height * 4 + 2]; + let written = BitmapStreamEncoder::new(width, height) + .encode_bitmap::(&image, &mut pdu, rle) + .unwrap(); + + // last byte is padding when !rle + assert_eq!(&pdu[0..written - 1], &expected_pdu[0..written - 1]); + } + + fn encode_decode_test(bmp: &[u8], width: usize, height: usize, rle: bool) { + let image = buffer_from_bmp(bmp, width, height); + + let mut pdu = vec![0; width * height * 4 + 2]; + let written = BitmapStreamEncoder::new(width, height) + .encode_bitmap::(&image, &mut pdu, rle) + .unwrap(); + + let mut actual = Vec::new(); + BitmapStreamDecoder::default() + .decode_bitmap_stream_to_rgb24(&pdu[..written], &mut actual, width, height) + .unwrap(); + + assert_eq!(&image.as_slice(), &actual.as_slice()); + } + + #[test] + fn encode_32x64_rgb_raw() { + // RGB (No alpha), no RLE + assert_encoded_image( + include_bytes!("../test_assets/32x64_rgb_raw.bin"), + include_bytes!("../test_assets/32x64_rgb_raw.bmp"), + 32, + 64, + false, + ); + } + + #[test] + fn encode_decode_32x64_rgb_raw() { + // RGB (No alpha), no RLE + encode_decode_test(include_bytes!("../test_assets/32x64_rgb_raw.bmp"), 32, 64, false); + } + + #[test] + fn encode_decode_32x64_rgb_rle() { + // RGB (No alpha), with RLE + encode_decode_test(include_bytes!("../test_assets/32x64_rgb_raw.bmp"), 32, 64, true); + } + + #[test] + fn encode_decode_64x24_rgb_raw() { + // RGB (No alpha), no RLE + encode_decode_test(include_bytes!("../test_assets/64x24_argb_rle.bmp"), 32, 64, false); + } + + #[test] + fn encode_decode_64x24_rgb_rle() { + // RGB (No alpha), with RLE + encode_decode_test(include_bytes!("../test_assets/64x24_argb_rle.bmp"), 32, 64, true); + } + + #[test] + fn encode_decode_64x64_rgb_raw() { + // RGB (No alpha), no RLE + encode_decode_test(include_bytes!("../test_assets/64x64_aycocg_rle.bmp"), 64, 64, false); + } + + #[test] + fn encode_decode_64x64_rgb_rle() { + // RGB (No alpha), with RLE + encode_decode_test(include_bytes!("../test_assets/64x64_aycocg_rle.bmp"), 64, 64, true); + } +} diff --git a/crates/ironrdp-graphics/src/rdp6/mod.rs b/crates/ironrdp-graphics/src/rdp6/mod.rs index 7f99d1ba..2379e8e7 100644 --- a/crates/ironrdp-graphics/src/rdp6/mod.rs +++ b/crates/ironrdp-graphics/src/rdp6/mod.rs @@ -3,5 +3,5 @@ pub(crate) mod bitmap_stream; pub(crate) mod rle; -pub use bitmap_stream::{BitmapDecodeError, BitmapStreamDecoder}; -pub use rle::RleError; +pub use bitmap_stream::*; +pub use rle::{RleDecodeError, RleEncodeError}; diff --git a/crates/ironrdp-graphics/src/rdp6/rle.rs b/crates/ironrdp-graphics/src/rdp6/rle.rs index 44adf8cb..cf6bed7e 100644 --- a/crates/ironrdp-graphics/src/rdp6/rle.rs +++ b/crates/ironrdp-graphics/src/rdp6/rle.rs @@ -1,6 +1,7 @@ use std::io::{Read, Write}; use byteorder::ReadBytesExt; +use ironrdp_pdu::cursor::WriteCursor; use thiserror::Error; /// Maximum possible segment size is 47 (run_length = 2, raw_bytes_count = 15), which is treated as @@ -8,7 +9,7 @@ use thiserror::Error; const MAX_DECODED_SEGMENT_SIZE: usize = 47; #[derive(Debug, Error)] -pub enum RleError { +pub enum RleDecodeError { #[error("Failed to read RLE-compressed data: {0}")] ReadCompressedData(#[source] std::io::Error), @@ -22,6 +23,15 @@ pub enum RleError { SegmentDoNotFitScanline, } +#[derive(Debug, Error)] +pub enum RleEncodeError { + #[error("Not enough data to compress")] + NotEnoughBytes, + + #[error("Destination buffer is too small")] + BufferTooSmall, +} + /// RLE-encoded color plane decoder implementation for RDP6 bitmap stream #[derive(Debug)] struct RlePlaneDecoder { @@ -47,11 +57,11 @@ impl RlePlaneDecoder { } } - fn decompress_next_segment(&mut self, mut src: &[u8]) -> Result { - let control_byte = src.read_u8().map_err(RleError::ReadCompressedData)?; + fn decompress_next_segment(&mut self, mut src: &[u8]) -> Result { + let control_byte = src.read_u8().map_err(RleDecodeError::ReadCompressedData)?; if control_byte == 0 { - return Err(RleError::InvalidSegmentHeader); + return Err(RleDecodeError::InvalidSegmentHeader); } let rle_bytes_field = control_byte & 0x0F; @@ -66,7 +76,7 @@ impl RlePlaneDecoder { self.decoded_data_len = raw_bytes_count + run_length; src.read_exact(&mut self.decoded_data[..raw_bytes_count]) - .map_err(RleError::ReadCompressedData)?; + .map_err(RleDecodeError::ReadCompressedData)?; if raw_bytes_count > 0 { // save last decoded byte for the next segments decoding @@ -79,7 +89,7 @@ impl RlePlaneDecoder { } /// Decodes single RLE-encoded scanline, without performing delta transformation - fn decode_scanline(&mut self, src: &[u8], mut dst: &mut [u8]) -> Result { + fn decode_scanline(&mut self, src: &[u8], mut dst: &mut [u8]) -> Result { let mut decoded_columns = 0; let mut read_bytes = 0; @@ -89,11 +99,11 @@ impl RlePlaneDecoder { read_bytes += self.decompress_next_segment(&src[read_bytes..])?; if decoded_columns + self.decoded_data_len > self.width { - return Err(RleError::SegmentDoNotFitScanline); + return Err(RleDecodeError::SegmentDoNotFitScanline); } dst.write_all(&self.decoded_data[..self.decoded_data_len]) - .map_err(RleError::WriteDecompressedData)?; + .map_err(RleDecodeError::WriteDecompressedData)?; decoded_columns += self.decoded_data_len; } @@ -122,7 +132,7 @@ impl RlePlaneDecoder { }); } - pub fn decode(mut self, src: &[u8], dst: &mut [u8]) -> Result { + pub fn decode(mut self, src: &[u8], dst: &mut [u8]) -> Result { let mut read_bytes = 0; read_bytes += self.decode_scanline(src, dst)?; @@ -147,18 +157,214 @@ impl RlePlaneDecoder { /// Size of data written to dst buffer is exactly equal to `width * height`. /// /// Returns number of bytes consumed from src buffer. -pub fn decompress_8bpp_plane( - src: &[u8], - dst: &mut [u8], - width: impl Into, - height: impl Into, -) -> Result { - let width = width.into(); - let height = height.into(); - +pub fn decompress_8bpp_plane(src: &[u8], dst: &mut [u8], width: usize, height: usize) -> Result { RlePlaneDecoder::new(width, height).decode(src, dst) } +struct RleEncoderScanlineIterator { + inner: std::iter::Enumerate, + width: usize, + prev_scanline: Vec, +} + +impl RleEncoderScanlineIterator { + fn new(width: usize, inner: I) -> Self { + Self { + width, + inner: inner.enumerate(), + prev_scanline: vec![0; width], + } + } + + fn delta_value(&self, prev: u8, next: u8) -> u8 { + let mut result = (next as i16 - prev as i16) as u8; + + // bit magic from 3.1.9.2.1 of [MS-RDPEGDI]. + if result < 128 { + result <<= 1; + } else { + result = (255u8.wrapping_sub(result) << 1).wrapping_add(1); + } + + result + } +} + +impl> Iterator for RleEncoderScanlineIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + let Some((idx, mut next)) = self.inner.next() else { + return None; + }; + + let prev = std::mem::replace(&mut self.prev_scanline[idx % self.width], next); + if idx >= self.width { + next = self.delta_value(prev, next); + } + + Some(next) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +#[derive(Debug)] +struct RlePlaneEncoder { + width: usize, + height: usize, +} + +macro_rules! ensure_size { + (dst: $buf:ident, size: $expected:expr) => {{ + let available = $buf.len(); + let needed = $expected; + if !(available >= needed) { + return None; + } + }}; +} + +impl RlePlaneEncoder { + pub fn new(width: usize, height: usize) -> Self { + Self { width, height } + } + + pub fn encode( + &self, + mut src: impl Iterator, + dst: &mut WriteCursor<'_>, + ) -> Result { + let mut written = 0; + + for _ in 0..self.height { + written += self.encode_scanline((&mut src).take(self.width), dst)?; + } + + Ok(written) + } + + fn encode_scanline( + &self, + mut src: impl Iterator, + dst: &mut WriteCursor<'_>, + ) -> Result { + let mut written = 0; + let first = src.next().ok_or(RleEncodeError::NotEnoughBytes)?; + + let mut raw = vec![first]; + let mut seq = (first, 0); + + for byte in src { + let (last, count) = seq; + + seq = if byte == last { + (byte, count + 1) + } else { + match count { + 3.. => { + written += self + .encode_segment(&raw, count, dst) + .ok_or(RleEncodeError::BufferTooSmall)?; + raw.clear(); + } + 2 => raw.extend_from_slice(&[last, last]), + 1 => raw.push(last), + _ => {} + } + + raw.push(byte); + + (byte, 0) + } + } + + let (last, mut count) = seq; + if count < 3 { + raw.extend(vec![last; count].into_iter()); + count = 0; + } + + written += self + .encode_segment(&raw, count, dst) + .ok_or(RleEncodeError::BufferTooSmall)?; + + Ok(written) + } + + fn encode_segment(&self, mut raw: &[u8], run: usize, dst: &mut WriteCursor<'_>) -> Option { + let mut extra_bytes = 0; + + while raw.len() > 15 { + extra_bytes += self.encode_segment(&raw[0..15], 0, dst)?; + raw = &raw[15..]; + } + + let control = ((raw.len() as u8) << 4) + std::cmp::min(run, 15) as u8; + + ensure_size!(dst: dst, size: raw.len() + 1); + + dst.write_u8(control); + dst.write_slice(raw); + + if run > 15 { + let last = raw.last().unwrap(); + extra_bytes += self.encode_long_sequence(run - 15, *last, dst)?; + } + + Some(1 + raw.len() + extra_bytes) + } + + fn encode_long_sequence(&self, mut run: usize, last: u8, dst: &mut WriteCursor<'_>) -> Option { + let mut written = 0; + + while run >= 16 { + ensure_size!(dst: dst, size: 1); + + let current = std::cmp::min(run, MAX_DECODED_SEGMENT_SIZE) as u8; + + let c_raw_bytes = std::cmp::min(current / 16, 2); + let n_run_length = current - c_raw_bytes * 16; + + let control = (n_run_length << 4) + c_raw_bytes; + dst.write_u8(control); + written += 1; + + run -= current as usize; + } + + if run > 0 { + match run { + short @ 1..=3 => { + written += self.encode_segment(&vec![last; short], 0, dst)?; + } + long => { + written += self.encode_segment(&[last], long - 1, dst)?; + } + } + } + + Some(written) + } +} + +/// Performs compression of 8bpp color plane pixel stream into a buffer. +/// Pixel iterator must have at least width * height items. +/// Destination slice must have enough space for the compressed data. +/// +/// Returns number of bytes written to the dst buffer. +pub fn compress_8bpp_plane( + src: impl Iterator, + dst: &mut WriteCursor<'_>, + width: usize, + height: usize, +) -> Result { + let iter = RleEncoderScanlineIterator::new(width, src); + RlePlaneEncoder::new(width, height).encode(iter, dst) +} + #[cfg(test)] mod tests { use expect_test::expect; @@ -166,20 +372,70 @@ mod tests { use super::*; /// Performs decompression of 8bpp color plane into vector. Vector will be resized to fit decompressed data. - pub fn decompress( - src: &[u8], - dst: &mut Vec, - width: impl Into, - height: impl Into, - ) -> Result { - let width = width.into(); - let height = height.into(); + pub fn decompress(src: &[u8], dst: &mut Vec, width: usize, height: usize) -> Result { // Ensure dest buffer have enough space for decompressed data dst.resize(width * height, 0); decompress_8bpp_plane(src, dst.as_mut_slice(), width, height) } + pub fn compress(src: &[u8], dst: &mut Vec, width: usize, height: usize) -> Result { + compress_8bpp_plane(src.iter().copied(), &mut WriteCursor::new(dst), width, height) + } + + #[test] + fn simple_encode() { + // Example AAAABBCCCCCD from 3.1.9.2 of [MS-RDPEGDI]. + let src = [65, 65, 65, 65, 66, 66, 67, 67, 67, 67, 67, 68]; + + let width = src.len(); + let height = 1usize; + + let expected = &[0x13, 65, 0x34, 66, 66, 67, 0x10, 68]; + + let mut compressed = vec![0; 255]; + let len = compress(&src, &mut compressed, width, height).unwrap(); + + assert_eq!(&compressed[..len], expected); + } + + #[test] + fn long_sequence_encode() { + // Example from 3.1.9.2.2 of [MS-RDPEGDI]. + let src = [0x41u8; 100]; + + let width = 100usize; + let height = 1usize; + + let expected = &[0x1F, 0x41, 0xF2, 0x52]; + + let mut compressed = vec![0; 255]; + let len = compress(&src, &mut compressed, width, height).unwrap(); + + assert_eq!(&compressed[..len], expected); + } + + #[test] + fn multiline_encode() { + // Example from 3.1.9.2.1 of [MS-RDPEGDI]. + let src = [ + 255, 255, 255, 255, 254, 253, 254, 192, 132, 96, 75, 25, 253, 140, 62, 14, 135, 193, + ]; + + let width = 6usize; + let height = 3usize; + + let expected = &[ + 0x13, 0xFF, 0x20, 0xFE, 0xFD, 0x60, 0x01, 0x7D, 0xF5, 0xC2, 0x9A, 0x38, 0x60, 0x01, 0x67, 0x8B, 0xA3, 0x78, + 0xAF, + ]; + + let mut compressed = vec![0; 255]; + let len = compress(&src, &mut compressed, width, height).unwrap(); + + assert_eq!(&compressed[..len], expected); + } + #[test] fn long_sequence_decode() { // Example from 3.1.9.2.2 of [MS-RDPEGDI]. @@ -215,6 +471,61 @@ mod tests { assert_eq!(actual, expected); } + #[test] + fn long_sequence_encode_decode() { + // Example from 3.1.9.2.2 of [MS-RDPEGDI]. + let src = [0x41u8; 100]; + + let width = 100usize; + let height = 1usize; + + let mut compressed = vec![0; 255]; + let len = compress(&src, &mut compressed, width, height).unwrap(); + + let mut actual = Vec::new(); + decompress(&compressed[..len], &mut actual, width, height).unwrap(); + + assert_eq!(actual.as_slice(), src.as_slice()); + } + + #[test] + fn complex_encode_decode() { + let src = [ + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 18, 18, 18, 19, 19, 18, 18, 18, + 18, 18, 18, 18, 18, + ]; + + let width = src.len(); + let height = 1usize; + + let mut compressed = vec![0; 255]; + let len = compress(&src, &mut compressed, width, height).unwrap(); + + let mut actual = Vec::new(); + decompress(&compressed[..len], &mut actual, width, height).unwrap(); + + assert_eq!(actual.as_slice(), src.as_slice()); + } + + #[test] + fn multiline_encode_decode() { + // Example from 3.1.9.2.3 of [MS-RDPEGDI]. + let src = [ + 255, 255, 255, 255, 254, 253, 254, 192, 132, 96, 75, 25, 253, 140, 62, 14, 135, 193, + ]; + + let width = 6usize; + let height = 3usize; + + let mut compressed = vec![0; 255]; + let len = compress(&src, &mut compressed, width, height).unwrap(); + + let mut actual = Vec::new(); + decompress(&compressed[..len], &mut actual, width, height).unwrap(); + + assert_eq!(actual.as_slice(), src.as_slice()); + } + #[test] fn each_scanline_resets_last_decoded_byte() { let src = [0x17, 0xFF, 0x04, 0x40, 0x01, 0x02, 0x03, 0x04]; @@ -309,6 +620,42 @@ mod tests { .assert_debug_eq(&decompress(&src, &mut actual, width, height)); } + #[test] + fn buffer_too_small_encode() { + let src = [ + 255, 255, 255, 255, 254, 253, 254, 192, 132, 96, 75, 25, 253, 140, 62, 14, 135, 193, + ]; + + let width = 6usize; + let height = 3usize; + + let mut compressed = vec![0; 4]; + + expect![[r#" + Err( + BufferTooSmall, + ) + "#]] + .assert_debug_eq(&compress(&src, &mut compressed, width, height)); + } + + #[test] + fn not_enough_bytes_to_encode() { + let src = [255, 255, 255, 255, 254, 253, 254, 192, 132, 96, 75, 25, 253]; + + let width = 8usize; + let height = 3usize; + + let mut compressed = vec![0; 255]; + + expect![[r#" + Err( + NotEnoughBytes, + ) + "#]] + .assert_debug_eq(&compress(&src, &mut compressed, width, height)); + } + #[test] fn too_small_dest_buffer_handled() { let src = [0x17, 0xFF, 0x04, 0x40, 0x01, 0x02, 0x03, 0x04]; diff --git a/crates/ironrdp-pdu/src/basic_output/bitmap.rs b/crates/ironrdp-pdu/src/basic_output/bitmap.rs index b79407fd..c5fe58ea 100644 --- a/crates/ironrdp-pdu/src/basic_output/bitmap.rs +++ b/crates/ironrdp-pdu/src/basic_output/bitmap.rs @@ -24,6 +24,17 @@ impl BitmapUpdateData<'_> { const FIXED_PART_SIZE: usize = core::mem::size_of::() * 2; } +impl BitmapUpdateData<'_> { + pub fn encode_header(rectangles: u16, dst: &mut crate::cursor::WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: 2); + + dst.write_u16(BitmapFlags::BITMAP_UPDATE_TYPE.bits()); + dst.write_u16(rectangles); + + Ok(()) + } +} + impl<'en> PduEncode for BitmapUpdateData<'en> { fn encode(&self, dst: &mut crate::cursor::WriteCursor<'_>) -> PduResult<()> { ensure_size!(in: dst, size: self.size()); @@ -32,8 +43,7 @@ impl<'en> PduEncode for BitmapUpdateData<'en> { return Err(invalid_message_err!("numberRectangles", "rectangle count is too big")); } - dst.write_u16(BitmapFlags::BITMAP_UPDATE_TYPE.bits()); - dst.write_u16(self.rectangles.len() as u16); + Self::encode_header(self.rectangles.len() as u16, dst)?; for bitmap_data in self.rectangles.iter() { bitmap_data.encode(dst)?; diff --git a/crates/ironrdp-pdu/src/basic_output/bitmap/rdp6.rs b/crates/ironrdp-pdu/src/basic_output/bitmap/rdp6.rs index d54e21d2..4efb9c28 100644 --- a/crates/ironrdp-pdu/src/basic_output/bitmap/rdp6.rs +++ b/crates/ironrdp-pdu/src/basic_output/bitmap/rdp6.rs @@ -3,23 +3,96 @@ use crate::{PduDecode, PduEncode, PduResult, ReadCursor, WriteCursor}; const NON_RLE_PADDING_SIZE: usize = 1; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ColorPlanes<'a> { - Argb { - data: &'a [u8], - }, +pub enum ColorPlaneDefinition { + Argb, AYCoCg { color_loss_level: u8, use_chroma_subsampling: bool, - data: &'a [u8], }, } -/// Represents `RDP6_BITMAP_STREAM` structure described in [MS-RDPEGDI] 2.2.2.5.1 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct BitmapStream<'a> { +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BitmapStreamHeader { pub enable_rle_compression: bool, pub use_alpha: bool, - pub color_planes: ColorPlanes<'a>, + pub color_plane_definition: ColorPlaneDefinition, +} + +impl BitmapStreamHeader { + pub const NAME: &'static str = "Rdp6BitmapStreamHeader"; + const FIXED_PART_SIZE: usize = 1; +} + +impl PduDecode<'_> for BitmapStreamHeader { + fn decode(src: &mut ReadCursor<'_>) -> PduResult { + ensure_fixed_part_size!(in: src); + let header = src.read_u8(); + + let color_loss_level = header & 0x07; + let use_chroma_subsampling = (header & 0x08) != 0; + let enable_rle_compression = (header & 0x10) != 0; + let use_alpha = (header & 0x20) == 0; + + let color_plane_definition = match color_loss_level { + 0 => ColorPlaneDefinition::Argb, + color_loss_level => ColorPlaneDefinition::AYCoCg { + color_loss_level, + use_chroma_subsampling, + }, + }; + + Ok(Self { + enable_rle_compression, + use_alpha, + color_plane_definition, + }) + } +} + +impl PduEncode for BitmapStreamHeader { + fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { + ensure_size!(in: dst, size: self.size()); + + let mut header = ((self.enable_rle_compression as u8) << 4) | ((!self.use_alpha as u8) << 5); + + match self.color_plane_definition { + ColorPlaneDefinition::Argb { .. } => { + // ARGB color planes keep cll and cs flags set to 0 + } + ColorPlaneDefinition::AYCoCg { + color_loss_level, + use_chroma_subsampling, + .. + } => { + // Add cll and cs flags to header + header |= (color_loss_level & 0x07) | ((use_chroma_subsampling as u8) << 3); + } + } + + dst.write_u8(header); + + Ok(()) + } + + fn name(&self) -> &'static str { + Self::NAME + } + + fn size(&self) -> usize { + Self::FIXED_PART_SIZE + + if self.enable_rle_compression { + 0 + } else { + NON_RLE_PADDING_SIZE + } + } +} + +/// Represents `RDP6_BITMAP_STREAM` structure described in [MS-RDPEGDI] 2.2.2.5.1 +#[derive(Debug, Clone)] +pub struct BitmapStream<'a> { + pub header: BitmapStreamHeader, + pub color_planes: &'a [u8], } impl<'a> BitmapStream<'a> { @@ -27,16 +100,13 @@ impl<'a> BitmapStream<'a> { const FIXED_PART_SIZE: usize = 1; pub fn color_panes_data(&self) -> &'a [u8] { - match self.color_planes { - ColorPlanes::Argb { data } => data, - ColorPlanes::AYCoCg { data, .. } => data, - } + self.color_planes } pub fn has_subsampled_chroma(&self) -> bool { - match self.color_planes { - ColorPlanes::Argb { .. } => false, - ColorPlanes::AYCoCg { + match self.header.color_plane_definition { + ColorPlaneDefinition::Argb { .. } => false, + ColorPlaneDefinition::AYCoCg { use_chroma_subsampling, .. } => use_chroma_subsampling, } @@ -46,14 +116,9 @@ impl<'a> BitmapStream<'a> { impl<'a> PduDecode<'a> for BitmapStream<'a> { fn decode(src: &mut ReadCursor<'a>) -> PduResult { ensure_fixed_part_size!(in: src); - let header = src.read_u8(); + let header = crate::decode_cursor::(src)?; - let color_loss_level = header & 0x07; - let use_chroma_subsampling = (header & 0x08) != 0; - let enable_rle_compression = (header & 0x10) != 0; - let use_alpha = (header & 0x20) == 0; - - let color_planes_size = if !enable_rle_compression { + let color_planes_size = if !header.enable_rle_compression { // Cut padding field if RLE flags is set to 0 if src.is_empty() { return Err(invalid_message_err!( @@ -66,55 +131,21 @@ impl<'a> PduDecode<'a> for BitmapStream<'a> { src.len() }; - let color_planes_data = src.peek_slice(color_planes_size); + let color_planes = src.peek_slice(color_planes_size); - let color_planes = match color_loss_level { - 0 => { - // ARGB color planes - ColorPlanes::Argb { - data: color_planes_data, - } - } - color_loss_level => ColorPlanes::AYCoCg { - color_loss_level, - use_chroma_subsampling, - data: color_planes_data, - }, - }; - - Ok(Self { - enable_rle_compression, - use_alpha, - color_planes, - }) + Ok(Self { header, color_planes }) } } impl<'a> PduEncode for BitmapStream<'a> { fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> { - let mut header = ((self.enable_rle_compression as u8) << 4) | ((!self.use_alpha as u8) << 5); - - match self.color_planes { - ColorPlanes::Argb { .. } => { - // ARGB color planes keep cll and cs flags set to 0 - } - ColorPlanes::AYCoCg { - color_loss_level, - use_chroma_subsampling, - .. - } => { - // Add cll and cs flags to header - header |= (color_loss_level & 0x07) | ((use_chroma_subsampling as u8) << 3); - } - } - ensure_size!(in: dst, size: self.size()); - dst.write_u8(header); + crate::encode_cursor(&self.header, dst)?; dst.write_slice(self.color_panes_data()); // Write padding - if !self.enable_rle_compression { + if !self.header.enable_rle_compression { dst.write_u8(0); } @@ -126,11 +157,7 @@ impl<'a> PduEncode for BitmapStream<'a> { } fn size(&self) -> usize { - if self.enable_rle_compression { - Self::FIXED_PART_SIZE + self.color_panes_data().len() - } else { - Self::FIXED_PART_SIZE + NON_RLE_PADDING_SIZE + self.color_panes_data().len() - } + self.header.size() + self.color_panes_data().len() } } @@ -162,18 +189,20 @@ mod tests { &[0x3F, 0x01, 0x02, 0x03, 0x04], expect![[r#" BitmapStream { - enable_rle_compression: true, - use_alpha: false, - color_planes: AYCoCg { - color_loss_level: 7, - use_chroma_subsampling: true, - data: [ - 1, - 2, - 3, - 4, - ], + header: BitmapStreamHeader { + enable_rle_compression: true, + use_alpha: false, + color_plane_definition: AYCoCg { + color_loss_level: 7, + use_chroma_subsampling: true, + }, }, + color_planes: [ + 1, + 2, + 3, + 4, + ], } "#]], ); @@ -183,16 +212,17 @@ mod tests { &[0x10, 0x01, 0x02, 0x03, 0x04], expect![[r#" BitmapStream { - enable_rle_compression: true, - use_alpha: true, - color_planes: Argb { - data: [ - 1, - 2, - 3, - 4, - ], + header: BitmapStreamHeader { + enable_rle_compression: true, + use_alpha: true, + color_plane_definition: Argb, }, + color_planes: [ + 1, + 2, + 3, + 4, + ], } "#]], ); @@ -202,15 +232,16 @@ mod tests { &[0x20, 0x01, 0x02, 0x03, 0x00], expect![[r#" BitmapStream { - enable_rle_compression: false, - use_alpha: false, - color_planes: Argb { - data: [ - 1, - 2, - 3, - ], + header: BitmapStreamHeader { + enable_rle_compression: false, + use_alpha: false, + color_plane_definition: Argb, }, + color_planes: [ + 1, + 2, + 3, + ], } "#]], ); @@ -220,11 +251,12 @@ mod tests { &[0x10], expect![[r#" BitmapStream { - enable_rle_compression: true, - use_alpha: true, - color_planes: Argb { - data: [], + header: BitmapStreamHeader { + enable_rle_compression: true, + use_alpha: true, + color_plane_definition: Argb, }, + color_planes: [], } "#]], ); @@ -234,11 +266,12 @@ mod tests { &[0x00, 0x00], expect![[r#" BitmapStream { - enable_rle_compression: false, - use_alpha: true, - color_planes: Argb { - data: [], + header: BitmapStreamHeader { + enable_rle_compression: false, + use_alpha: true, + color_plane_definition: Argb, }, + color_planes: [], } "#]], ); diff --git a/crates/ironrdp-pdu/src/basic_output/fast_path.rs b/crates/ironrdp-pdu/src/basic_output/fast_path.rs index 9b471b40..23653e72 100644 --- a/crates/ironrdp-pdu/src/basic_output/fast_path.rs +++ b/crates/ironrdp-pdu/src/basic_output/fast_path.rs @@ -27,6 +27,14 @@ impl FastPathHeader { const NAME: &str = "TS_FP_UPDATE_PDU header"; const FIXED_PART_SIZE: usize = std::mem::size_of::(); + pub fn new(flags: EncryptionFlags, data_length: usize) -> Self { + Self { + flags, + data_length, + forced_long_length: false, + } + } + fn minimal_size(&self) -> usize { Self::FIXED_PART_SIZE + per::sizeof_length(self.data_length as u16) } diff --git a/crates/ironrdp-server/Cargo.toml b/crates/ironrdp-server/Cargo.toml new file mode 100644 index 00000000..6229a318 --- /dev/null +++ b/crates/ironrdp-server/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "ironrdp-server" +version = "0.1.0" +readme = "README.md" +description = "" +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +authors.workspace = true +keywords.workspace = true +categories.workspace = true + +[lib] +doctest = true +test = false + +[dependencies] +anyhow = "1.0" +bytes = "1" +tokio = { version = "1", features = ["macros"] } +tokio-rustls = "0.24" +async-trait = "0.1" +ironrdp-pdu.workspace = true +ironrdp-tokio.workspace = true +ironrdp-acceptor.workspace = true +ironrdp-graphics.workspace = true +tracing.workspace = true diff --git a/crates/ironrdp-server/README.md b/crates/ironrdp-server/README.md new file mode 100644 index 00000000..2e7ba92e --- /dev/null +++ b/crates/ironrdp-server/README.md @@ -0,0 +1,21 @@ +# IronRDP Server + +Library for implementing custom async RDP servers on the tokio runtime. + +--- +The server currently supports: + +**Security** + - Enhanced RDP Security with TLS External Security Protocols (TLS 1.2 and TLS 1.3) + +**Input** + - FastPath input events + - x224 input events and disconnect + +**Codecs** + - bitmap display updates with RDP 6.0 compression + +--- +Custom logic for your RDP server can be added by implementing these traits: + - `RdpServerInputHandler` - callbacks used when the server receives input events from a client + - `RdpServerDisplay` - notifies the server of display updates diff --git a/crates/ironrdp-server/src/builder.rs b/crates/ironrdp-server/src/builder.rs new file mode 100644 index 00000000..e671aa06 --- /dev/null +++ b/crates/ironrdp-server/src/builder.rs @@ -0,0 +1,156 @@ +use std::net::SocketAddr; +use tokio_rustls::TlsAcceptor; + +use super::display::{DesktopSize, DisplayUpdate, RdpServerDisplay}; +use super::handler::{KeyboardEvent, MouseEvent, RdpServerInputHandler}; +use super::server::*; + +pub struct WantsAddr {} +pub struct WantsSecurity { + addr: SocketAddr, +} +pub struct WantsHandler { + addr: SocketAddr, + security: RdpServerSecurity, +} +pub struct WantsDisplay { + addr: SocketAddr, + security: RdpServerSecurity, + handler: Box, +} +pub struct BuilderDone { + addr: SocketAddr, + security: RdpServerSecurity, + handler: Box, + display: Box, +} + +pub struct RdpServerBuilder { + state: State, +} + +impl RdpServerBuilder { + pub fn new() -> Self { + Self { state: WantsAddr {} } + } +} + +impl Default for RdpServerBuilder { + fn default() -> Self { + Self::new() + } +} + +impl RdpServerBuilder { + pub fn with_addr(self, addr: impl Into) -> RdpServerBuilder { + RdpServerBuilder { + state: WantsSecurity { addr: addr.into() }, + } + } +} + +impl RdpServerBuilder { + pub fn with_no_security(self) -> RdpServerBuilder { + RdpServerBuilder { + state: WantsHandler { + addr: self.state.addr, + security: RdpServerSecurity::None, + }, + } + } + + pub fn with_tls(self, acceptor: impl Into) -> RdpServerBuilder { + RdpServerBuilder { + state: WantsHandler { + addr: self.state.addr, + security: RdpServerSecurity::Tls(acceptor.into()), + }, + } + } +} + +impl RdpServerBuilder { + pub fn with_input_handler(self, handler: H) -> RdpServerBuilder + where + H: RdpServerInputHandler + 'static, + { + RdpServerBuilder { + state: WantsDisplay { + addr: self.state.addr, + security: self.state.security, + handler: Box::new(handler), + }, + } + } + + pub fn with_no_input(self) -> RdpServerBuilder { + RdpServerBuilder { + state: WantsDisplay { + addr: self.state.addr, + security: self.state.security, + handler: Box::new(NoopInputHandler), + }, + } + } +} + +impl RdpServerBuilder { + pub fn with_display_handler(self, display: D) -> RdpServerBuilder + where + D: RdpServerDisplay + 'static, + { + RdpServerBuilder { + state: BuilderDone { + addr: self.state.addr, + security: self.state.security, + handler: self.state.handler, + display: Box::new(display), + }, + } + } + + pub fn with_no_display(self) -> RdpServerBuilder { + RdpServerBuilder { + state: BuilderDone { + addr: self.state.addr, + security: self.state.security, + handler: self.state.handler, + display: Box::new(NoopDisplay), + }, + } + } +} + +impl RdpServerBuilder { + pub fn build(self) -> RdpServer { + RdpServer::new( + RdpServerOptions { + addr: self.state.addr, + security: self.state.security, + }, + self.state.handler, + self.state.display, + ) + } +} + +pub struct NoopInputHandler; + +#[async_trait::async_trait] +impl RdpServerInputHandler for NoopInputHandler { + async fn keyboard(&mut self, _: KeyboardEvent) {} + async fn mouse(&mut self, _: MouseEvent) {} +} + +pub struct NoopDisplay; + +#[async_trait::async_trait] +impl RdpServerDisplay for NoopDisplay { + async fn size(&mut self) -> DesktopSize { + DesktopSize { width: 0, height: 0 } + } + async fn get_update(&mut self) -> Option { + let () = std::future::pending().await; + unreachable!() + } +} diff --git a/crates/ironrdp-server/src/capabilities.rs b/crates/ironrdp-server/src/capabilities.rs new file mode 100644 index 00000000..40dafa90 --- /dev/null +++ b/crates/ironrdp-server/src/capabilities.rs @@ -0,0 +1,78 @@ +use crate::{DesktopSize, RdpServerOptions}; +use ironrdp_pdu::rdp::capability_sets; + +pub fn capabilities(_opts: &RdpServerOptions, size: DesktopSize) -> Vec { + vec![ + capability_sets::CapabilitySet::General(general_capabilities()), + capability_sets::CapabilitySet::Bitmap(bitmap_capabilities(&size)), + capability_sets::CapabilitySet::Order(order_capabilities()), + capability_sets::CapabilitySet::Pointer(pointer_capabilities()), + capability_sets::CapabilitySet::Input(input_capabilities()), + capability_sets::CapabilitySet::VirtualChannel(virtual_channel_capabilities()), + capability_sets::CapabilitySet::MultiFragmentUpdate(multifragment_update()), + ] +} + +fn general_capabilities() -> capability_sets::General { + capability_sets::General { + major_platform_type: capability_sets::MajorPlatformType::Unspecified, + minor_platform_type: capability_sets::MinorPlatformType::Unspecified, + extra_flags: capability_sets::GeneralExtraFlags::empty(), + refresh_rect_support: false, + suppress_output_support: false, + } +} + +fn bitmap_capabilities(size: &DesktopSize) -> capability_sets::Bitmap { + capability_sets::Bitmap { + pref_bits_per_pix: 32, + desktop_width: size.width, + desktop_height: size.height, + desktop_resize_flag: false, + drawing_flags: capability_sets::BitmapDrawingFlags::empty(), + } +} + +fn order_capabilities() -> capability_sets::Order { + capability_sets::Order::new( + capability_sets::OrderFlags::empty(), + capability_sets::OrderSupportExFlags::empty(), + 2048, + 224, + ) +} + +fn pointer_capabilities() -> capability_sets::Pointer { + capability_sets::Pointer { + color_pointer_cache_size: 2048, + pointer_cache_size: 2048, + } +} + +fn input_capabilities() -> capability_sets::Input { + capability_sets::Input { + input_flags: capability_sets::InputFlags::SCANCODES + | capability_sets::InputFlags::MOUSEX + | capability_sets::InputFlags::FASTPATH_INPUT + | capability_sets::InputFlags::UNICODE + | capability_sets::InputFlags::FASTPATH_INPUT_2, + keyboard_layout: 0, + keyboard_type: None, + keyboard_subtype: 0, + keyboard_function_key: 128, + keyboard_ime_filename: "".into(), + } +} + +fn virtual_channel_capabilities() -> capability_sets::VirtualChannel { + capability_sets::VirtualChannel { + flags: capability_sets::VirtualChannelFlags::NO_COMPRESSION, + chunk_size: None, + } +} + +fn multifragment_update() -> capability_sets::MultifragmentUpdate { + capability_sets::MultifragmentUpdate { + max_request_size: u32::MAX, + } +} diff --git a/crates/ironrdp-server/src/display.rs b/crates/ironrdp-server/src/display.rs new file mode 100644 index 00000000..c256f637 --- /dev/null +++ b/crates/ironrdp-server/src/display.rs @@ -0,0 +1,75 @@ +pub use ironrdp_acceptor::DesktopSize; +pub use ironrdp_graphics::image_processing::PixelFormat; + +/// Display Update +/// +/// Contains all types of display updates currently supported by the server implementation +/// and the RDP spec +/// +#[derive(Debug, Clone)] +pub enum DisplayUpdate { + Bitmap(BitmapUpdate), +} + +#[derive(Debug, Clone, Copy)] +pub enum PixelOrder { + TopToBottom, + BottomToTop, +} + +/// Bitmap Display Update +/// +/// Bitmap updates are encoded using RDP 6.0 compression, fragmented and sent using +/// Fastpath Server Updates +/// +#[derive(Debug, Clone)] +pub struct BitmapUpdate { + pub top: u32, + pub left: u32, + pub width: u32, + pub height: u32, + pub format: PixelFormat, + pub order: PixelOrder, + pub data: Vec, +} + +/// Display Update reciever for an RDP server +/// +/// The RDP server will repeatadly call the `get_update` method to receive display updates +/// which will then be encoded and sent to the client +/// +/// # Example +/// +/// ``` +/// use ironrdp_server::{DesktopSize, DisplayUpdate, RdpServerDisplay}; +/// +/// pub struct DisplayHandler { +/// width: u16, +/// height: u16, +/// receiver: tokio::sync::mpsc::Receiver, +/// } +/// +/// #[async_trait::async_trait] +/// impl RdpServerDisplay for DisplayHandler { +/// async fn size(&mut self) -> DesktopSize { +/// DesktopSize { width: self.width, height: self.height } +/// } +/// +/// async fn get_update(&mut self) -> Option { +/// self.receiver.recv().await +/// } +/// } +/// ``` +#[async_trait::async_trait] +pub trait RdpServerDisplay { + /// This method should return the current size of the display. + /// Currently, there is no way for the client to negotiate resolution, + /// so the size returned by this method will be enforced. + async fn size(&mut self) -> DesktopSize; + + /// # Cancel safety + /// + /// This method MUST be cancellation safe because it is used in a `tokio::select!` statement. + /// If some other branch completes first, it MUST be guaranteed that no data is lost. + async fn get_update(&mut self) -> Option; +} diff --git a/crates/ironrdp-server/src/encoder/bitmap.rs b/crates/ironrdp-server/src/encoder/bitmap.rs new file mode 100644 index 00000000..7a770455 --- /dev/null +++ b/crates/ironrdp-server/src/encoder/bitmap.rs @@ -0,0 +1,111 @@ +use ironrdp_graphics::{ + image_processing::PixelFormat, + rdp6::{ABgrChannels, ARgbChannels, BgrAChannels, BitmapStreamEncoder, RgbAChannels}, +}; +use ironrdp_pdu::{ + bitmap::{self, BitmapData, BitmapUpdateData, Compression}, + cursor::WriteCursor, + geometry::InclusiveRectangle, + PduEncode, PduError, +}; + +use crate::{BitmapUpdate, PixelOrder}; + +// PERF: we could also remove the need for this buffer +pub struct BitmapEncoder { + buffer: Vec, +} + +impl BitmapEncoder { + pub fn new() -> Self { + Self { + buffer: vec![0; u16::MAX as usize], + } + } + + pub fn encode(&mut self, bitmap: &BitmapUpdate, output: &mut [u8]) -> Result { + let row_len = bitmap.width * bitmap.format.bytes_per_pixel() as u32; + let chunk_height = u16::MAX as u32 / row_len; + + let mut cursor = WriteCursor::new(output); + let chunks = bitmap.data.chunks((row_len * chunk_height) as usize); + + let total = chunks.size_hint().0; + BitmapUpdateData::encode_header(total as u16, &mut cursor)?; + + for (i, chunk) in chunks.enumerate() { + let height = chunk.len() as u32 / row_len; + let top = bitmap.top + i as u32 * chunk_height; + + let encoder = BitmapStreamEncoder::new(bitmap.width as usize, height as usize); + + let len = match bitmap.order { + PixelOrder::BottomToTop => { + Self::encode_slice(encoder, bitmap.format, chunk, self.buffer.as_mut_slice()) + } + + PixelOrder::TopToBottom => { + let bytes_per_pixel = bitmap.format.bytes_per_pixel() as usize; + let pixels = chunk + .chunks(row_len as usize) + .rev() + .flat_map(|row| row.chunks(bytes_per_pixel)); + + Self::encode_iter(encoder, bitmap.format, pixels, self.buffer.as_mut_slice()) + } + }; + + let data = BitmapData { + rectangle: InclusiveRectangle { + left: bitmap.left as u16, + top: top as u16, + right: (bitmap.left + bitmap.width - 1) as u16, + bottom: (top + height - 1) as u16, + }, + width: bitmap.width as u16, + height: height as u16, + bits_per_pixel: bitmap.format.bytes_per_pixel() as u16 * 8, + compression_flags: Compression::BITMAP_COMPRESSION, + compressed_data_header: Some(bitmap::CompressedDataHeader { + main_body_size: len as u16, + scan_width: bitmap.width as u16, + uncompressed_size: chunk.len() as u16, + }), + bitmap_data: &self.buffer[..len], + }; + + data.encode(&mut cursor)?; + } + + Ok(cursor.pos()) + } + + fn encode_slice(mut encoder: BitmapStreamEncoder, format: PixelFormat, src: &[u8], dst: &mut [u8]) -> usize { + match format { + PixelFormat::ARgb32 | PixelFormat::XRgb32 => encoder.encode_bitmap::(src, dst, true).unwrap(), + PixelFormat::RgbA32 | PixelFormat::RgbX32 => encoder.encode_bitmap::(src, dst, true).unwrap(), + PixelFormat::ABgr32 | PixelFormat::XBgr32 => encoder.encode_bitmap::(src, dst, true).unwrap(), + PixelFormat::BgrA32 | PixelFormat::BgrX32 => encoder.encode_bitmap::(src, dst, true).unwrap(), + } + } + + fn encode_iter<'a, P>(mut encoder: BitmapStreamEncoder, format: PixelFormat, src: P, dst: &mut [u8]) -> usize + where + P: Iterator + Clone, + { + match format { + PixelFormat::ARgb32 | PixelFormat::XRgb32 => { + encoder.encode_pixels_stream::<_, ARgbChannels>(src, dst, true).unwrap() + } + PixelFormat::RgbA32 | PixelFormat::RgbX32 => { + encoder.encode_pixels_stream::<_, RgbAChannels>(src, dst, true).unwrap() + } + PixelFormat::ABgr32 | PixelFormat::XBgr32 => { + encoder.encode_pixels_stream::<_, ABgrChannels>(src, dst, true).unwrap() + } + PixelFormat::BgrA32 | PixelFormat::BgrX32 => { + encoder.encode_pixels_stream::<_, BgrAChannels>(src, dst, true).unwrap() + } + } + } +} diff --git a/crates/ironrdp-server/src/encoder/mod.rs b/crates/ironrdp-server/src/encoder/mod.rs new file mode 100644 index 00000000..76089317 --- /dev/null +++ b/crates/ironrdp-server/src/encoder/mod.rs @@ -0,0 +1,123 @@ +pub mod bitmap; + +use std::cmp; + +use ironrdp_pdu::{ + cursor::WriteCursor, + fast_path::{EncryptionFlags, FastPathHeader, FastPathUpdatePdu, Fragmentation, UpdateCode}, + PduEncode, +}; + +use self::bitmap::BitmapEncoder; + +use super::BitmapUpdate; + +// this is the maximum amount of data (not including headers) we can send in a single TS_FP_UPDATE_PDU +const MAX_FASTPATH_UPDATE_SIZE: usize = 16_374; + +const FASTPATH_HEADER_SIZE: usize = 6; + +pub struct UpdateEncoder { + buffer: Vec, + bitmap: BitmapEncoder, +} + +impl UpdateEncoder { + pub fn new() -> Self { + Self { + buffer: vec![0; 16384], + bitmap: BitmapEncoder::new(), + } + } + + pub fn bitmap(&mut self, bitmap: BitmapUpdate) -> Option { + let len = loop { + match self.bitmap.encode(&bitmap, self.buffer.as_mut_slice()) { + Err(e) => match e.kind() { + ironrdp_pdu::PduErrorKind::NotEnoughBytes { .. } => { + self.buffer.resize(self.buffer.len() * 2, 0); + debug!("encoder buffer resized to: {}", self.buffer.len() * 2); + } + + _ => { + debug!("bitmap encode error: {:?}", e); + return None; + } + }, + Ok(len) => break len, + } + }; + + Some(UpdateFragmenter::new(UpdateCode::Bitmap, &self.buffer[..len])) + } +} + +pub struct UpdateFragmenter<'a> { + code: UpdateCode, + index: usize, + data: &'a [u8], +} + +impl<'a> UpdateFragmenter<'a> { + pub fn new(code: UpdateCode, data: &'a [u8]) -> Self { + Self { code, index: 0, data } + } + + pub fn size_hint(&self) -> usize { + FASTPATH_HEADER_SIZE + cmp::min(self.data.len(), MAX_FASTPATH_UPDATE_SIZE) + } + + pub fn next(&mut self, dst: &mut [u8]) -> Option { + let (consumed, written) = self.encode_next(dst)?; + self.data = &self.data[consumed..]; + self.index += 1; + Some(written) + } + + fn encode_next(&mut self, dst: &mut [u8]) -> Option<(usize, usize)> { + match self.data.len() { + 0 => None, + + 1..=MAX_FASTPATH_UPDATE_SIZE => { + let frag = if self.index > 0 { + Fragmentation::Last + } else { + Fragmentation::Single + }; + + self.encode_fastpath(frag, self.data, dst) + .map(|written| (self.data.len(), written)) + } + + _ => { + let frag = if self.index > 0 { + Fragmentation::Next + } else { + Fragmentation::First + }; + + self.encode_fastpath(frag, &self.data[..MAX_FASTPATH_UPDATE_SIZE], dst) + .map(|written| (MAX_FASTPATH_UPDATE_SIZE, written)) + } + } + } + + fn encode_fastpath(&self, frag: Fragmentation, data: &[u8], dst: &mut [u8]) -> Option { + let mut cursor = WriteCursor::new(dst); + + let update = FastPathUpdatePdu { + fragmentation: frag, + update_code: self.code, + compression_flags: None, + compression_type: None, + data, + }; + + let header = FastPathHeader::new(EncryptionFlags::empty(), update.size()); + + header.encode(&mut cursor).ok()?; + update.encode(&mut cursor).ok()?; + + Some(cursor.pos()) + } +} diff --git a/crates/ironrdp-server/src/handler.rs b/crates/ironrdp-server/src/handler.rs new file mode 100644 index 00000000..1a748a50 --- /dev/null +++ b/crates/ironrdp-server/src/handler.rs @@ -0,0 +1,181 @@ +use ironrdp_pdu::input::{ + fast_path::{self, SynchronizeFlags}, + mouse::PointerFlags, + mouse_x::PointerXFlags, + scan_code, + sync::SyncToggleFlags, + unicode, MousePdu, MouseXPdu, +}; + +/// Keyboard Event +/// +/// Describes a keyboard event received from the client +/// +#[derive(Debug)] +pub enum KeyboardEvent { + Pressed { code: u8, extended: bool }, + Released { code: u8, extended: bool }, + UnicodePressed(u16), + UnicodeReleased(u16), + Synchronize(SynchronizeFlags), +} + +/// Mouse Event +/// +/// Describes a mouse event received from the client +/// +#[derive(Debug)] +pub enum MouseEvent { + Move { x: u16, y: u16 }, + RightPressed, + RightReleased, + LeftPressed, + LeftReleased, + VerticalScroll { value: i16 }, +} + +/// Input Event Handler for an RDP server +/// +/// Whenever the RDP server will receive an input event from a client, the relevent callback from +/// this handler will be called +/// +/// # Example +/// +/// ``` +/// use ironrdp_server::{KeyboardEvent, MouseEvent, RdpServerInputHandler}; +/// +/// pub struct InputHandler; +/// +/// #[async_trait::async_trait] +/// impl RdpServerInputHandler for InputHandler { +/// async fn keyboard(&mut self, event: KeyboardEvent) { +/// match event { +/// KeyboardEvent::Pressed { code, .. } => println!("Pressed {}", code), +/// KeyboardEvent::Released { code, .. } => println!("Released {}", code), +/// other => println!("unhandled event: {:?}", other), +/// }; +/// } +/// +/// async fn mouse(&mut self, event: MouseEvent) { +/// let result = match event { +/// MouseEvent::Move { x, y } => println!("Moved mouse to {} {}", x, y), +/// other => println!("unhandled event: {:?}", other), +/// }; +/// } +/// } +/// ``` +#[async_trait::async_trait] +pub trait RdpServerInputHandler { + async fn keyboard(&mut self, event: KeyboardEvent); + async fn mouse(&mut self, event: MouseEvent); +} + +impl From<(u8, fast_path::KeyboardFlags)> for KeyboardEvent { + fn from((key, flags): (u8, fast_path::KeyboardFlags)) -> Self { + let extended = flags.contains(fast_path::KeyboardFlags::EXTENDED); + if flags.contains(fast_path::KeyboardFlags::RELEASE) { + KeyboardEvent::Released { code: key, extended } + } else { + KeyboardEvent::Pressed { code: key, extended } + } + } +} + +impl From<(u16, fast_path::KeyboardFlags)> for KeyboardEvent { + fn from((key, flags): (u16, fast_path::KeyboardFlags)) -> Self { + if flags.contains(fast_path::KeyboardFlags::RELEASE) { + KeyboardEvent::UnicodeReleased(key) + } else { + KeyboardEvent::UnicodePressed(key) + } + } +} + +impl From<(u16, scan_code::KeyboardFlags)> for KeyboardEvent { + fn from((key, flags): (u16, scan_code::KeyboardFlags)) -> Self { + let extended = flags.contains(scan_code::KeyboardFlags::EXTENDED); + if flags.contains(scan_code::KeyboardFlags::RELEASE) { + KeyboardEvent::Released { + code: key as u8, + extended, + } + } else { + KeyboardEvent::Pressed { + code: key as u8, + extended, + } + } + } +} + +impl From<(u16, unicode::KeyboardFlags)> for KeyboardEvent { + fn from((key, flags): (u16, unicode::KeyboardFlags)) -> Self { + if flags.contains(unicode::KeyboardFlags::RELEASE) { + KeyboardEvent::UnicodeReleased(key) + } else { + KeyboardEvent::UnicodePressed(key) + } + } +} + +impl From for KeyboardEvent { + fn from(value: SynchronizeFlags) -> Self { + KeyboardEvent::Synchronize(value) + } +} + +impl From for KeyboardEvent { + fn from(value: SyncToggleFlags) -> Self { + KeyboardEvent::Synchronize(SynchronizeFlags::from_bits_truncate(value.bits() as u8)) + } +} + +impl From for MouseEvent { + fn from(value: MousePdu) -> Self { + if value.flags.contains(PointerFlags::LEFT_BUTTON) { + if value.flags.contains(PointerFlags::DOWN) { + MouseEvent::LeftPressed + } else { + MouseEvent::LeftReleased + } + } else if value.flags.contains(PointerFlags::RIGHT_BUTTON) { + if value.flags.contains(PointerFlags::DOWN) { + MouseEvent::RightPressed + } else { + MouseEvent::RightReleased + } + } else if value.flags.contains(PointerFlags::VERTICAL_WHEEL) { + MouseEvent::VerticalScroll { + value: value.number_of_wheel_rotation_units, + } + } else { + MouseEvent::Move { + x: value.x_position, + y: value.y_position, + } + } + } +} + +impl From for MouseEvent { + fn from(value: MouseXPdu) -> Self { + if value.flags.contains(PointerXFlags::BUTTON1) { + if value.flags.contains(PointerXFlags::DOWN) { + MouseEvent::LeftPressed + } else { + MouseEvent::LeftReleased + } + } else if value.flags.contains(PointerXFlags::BUTTON2) { + if value.flags.contains(PointerXFlags::DOWN) { + MouseEvent::RightPressed + } else { + MouseEvent::RightReleased + } + } else { + MouseEvent::Move { + x: value.x_position, + y: value.y_position, + } + } + } +} diff --git a/crates/ironrdp-server/src/lib.rs b/crates/ironrdp-server/src/lib.rs new file mode 100644 index 00000000..cac12c11 --- /dev/null +++ b/crates/ironrdp-server/src/lib.rs @@ -0,0 +1,13 @@ +#[macro_use] +extern crate tracing; + +mod builder; +mod capabilities; +mod display; +mod encoder; +mod handler; +mod server; + +pub use display::*; +pub use handler::*; +pub use server::*; diff --git a/crates/ironrdp-server/src/server.rs b/crates/ironrdp-server/src/server.rs new file mode 100644 index 00000000..e5abfd41 --- /dev/null +++ b/crates/ironrdp-server/src/server.rs @@ -0,0 +1,284 @@ +use std::{io::Cursor, net::SocketAddr}; + +use anyhow::Result; +use bytes::BytesMut; +use tokio::net::TcpListener; + +use ironrdp_acceptor::{self, Acceptor, BeginResult}; +use ironrdp_pdu::{ + self, + input::{ + fast_path::{FastPathInput, FastPathInputEvent}, + InputEventPdu, + }, + mcs, nego, rdp, Action, PduParsing, +}; +use ironrdp_tokio::{Framed, FramedRead, FramedWrite, TokioFramed}; +use tokio_rustls::TlsAcceptor; + +use crate::{ + builder, capabilities, + display::{DisplayUpdate, RdpServerDisplay}, + encoder::UpdateEncoder, + handler::RdpServerInputHandler, +}; + +#[derive(Clone)] +pub struct RdpServerOptions { + pub addr: SocketAddr, + pub security: RdpServerSecurity, +} + +#[derive(Clone)] +pub enum RdpServerSecurity { + None, + Tls(TlsAcceptor), +} + +impl RdpServerSecurity { + pub fn flag(&self) -> nego::SecurityProtocol { + match self { + RdpServerSecurity::None => ironrdp_pdu::nego::SecurityProtocol::empty(), + RdpServerSecurity::Tls(_) => ironrdp_pdu::nego::SecurityProtocol::SSL, + } + } +} + +/// RDP Server +/// +/// A server is created to listen for connections. +/// After the connection sequence is finalized using the provided security mechanism, the server can: +/// - receive display updates from a [`RdpServerDisplay`] and forward them to the client +/// - receive input events from a client and forward them to an [`RdpServerInputHandler`] +/// +/// # Example +/// +/// ```ignore +/// use ironrdp_server::{RdpServer, RdpServerInputHandler, RdpServerDisplay}; +/// +/// async fn main() { +/// let tls_acceptor = todo!(); +/// let input_handler = todo!(); +/// let display_handler = todo!(); +/// +/// let mut server = RdpServer::builder() +/// .with_addr(([127, 0, 0, 1], 3389)) +/// .with_tls(tls_acceptor) +/// .with_input_handler(input_handler) +/// .with_display_handler(display_handler) +/// .build(); +/// +/// server.run().await; +/// } +/// ``` +pub struct RdpServer { + opts: RdpServerOptions, + handler: Box, + display: Box, +} + +impl RdpServer { + pub fn new( + opts: RdpServerOptions, + handler: Box, + display: Box, + ) -> Self { + Self { opts, handler, display } + } + + pub fn builder() -> builder::RdpServerBuilder { + builder::RdpServerBuilder::new() + } + + pub async fn run(&mut self) -> Result<()> { + let listener = TcpListener::bind(self.opts.addr).await?; + + while let Ok((stream, peer)) = listener.accept().await { + debug!(?peer, "received connection"); + let framed = TokioFramed::new(stream); + + let size = self.display.size().await; + let capabilities = capabilities::capabilities(&self.opts, size.clone()); + let mut acceptor = Acceptor::new(self.opts.security.flag(), size, capabilities); + + match ironrdp_acceptor::accept_begin(framed, &mut acceptor).await { + Ok(BeginResult::ShouldUpgrade(stream)) => { + let framed = TokioFramed::new(match &self.opts.security { + RdpServerSecurity::Tls(acceptor) => acceptor.accept(stream).await?, + RdpServerSecurity::None => unreachable!(), + }); + let (framed, _) = ironrdp_acceptor::accept_finalize(framed, &mut acceptor).await?; + self.client_loop(framed).await?; + } + + Ok(BeginResult::Continue(framed)) => { + let (framed, _) = ironrdp_acceptor::accept_finalize(framed, &mut acceptor).await?; + self.client_loop(framed).await?; + } + + Err(e) => { + error!("connection error: {:?}", e); + } + } + } + + Ok(()) + } + + async fn client_loop(&mut self, mut framed: Framed) -> Result<()> + where + S: FramedWrite + FramedRead, + { + let mut buffer = vec![0u8; 4096]; + let mut encoder = UpdateEncoder::new(); + + debug!("starting client loop"); + + 'main: loop { + tokio::select! { + frame = framed.read_pdu() => { + let Ok((action, bytes)) = frame else { + break; + }; + + match action { + Action::FastPath => { + let input = FastPathInput::from_buffer(Cursor::new(&bytes))?; + self.handle_fastpath(input).await; + } + + Action::X224 => { + match self.handle_x224(bytes).await { + Ok(disconnect) => { + if disconnect { + break 'main; + } + }, + + Err(e) => { + error!("x224 input error: {:?}", e); + } + }; + } + } + }, + + Some(update) = self.display.get_update() => { + let fragmenter = match update { + DisplayUpdate::Bitmap(bitmap) => encoder.bitmap(bitmap) + }; + + if let Some(mut fragmenter) = fragmenter { + if fragmenter.size_hint() > buffer.len() { + buffer.resize(fragmenter.size_hint(), 0); + } + + while let Some(len) = fragmenter.next(&mut buffer) { + if let Err(e) = framed.write_all(&buffer[..len]).await { + error!("write error: {:?}", e); + break; + }; + } + } + } + } + } + + Ok(()) + } + + async fn handle_fastpath(&mut self, input: FastPathInput) { + for event in input.0 { + match event { + FastPathInputEvent::KeyboardEvent(flags, key) => { + self.handler.keyboard((key, flags).into()).await; + } + + FastPathInputEvent::UnicodeKeyboardEvent(flags, key) => { + self.handler.keyboard((key, flags).into()).await; + } + + FastPathInputEvent::SyncEvent(flags) => { + self.handler.keyboard(flags.into()).await; + } + + FastPathInputEvent::MouseEvent(mouse) => { + self.handler.mouse(mouse.into()).await; + } + + FastPathInputEvent::MouseEventEx(mouse) => { + self.handler.mouse(mouse.into()).await; + } + + FastPathInputEvent::QoeEvent(quality) => { + warn!("received QoE: {}", quality); + } + } + } + } + + async fn handle_x224(&mut self, frame: BytesMut) -> Result { + let message = ironrdp_pdu::decode::(&frame)?; + match message { + mcs::McsMessage::SendDataRequest(data) => { + let control = rdp::headers::ShareControlHeader::from_buffer(Cursor::new(data.user_data))?; + + match control.share_control_pdu { + rdp::headers::ShareControlPdu::Data(header) => match header.share_data_pdu { + rdp::headers::ShareDataPdu::Input(pdu) => { + self.handle_input_event(pdu).await; + } + + unexpected => { + warn!("unexpected share data pdu {:?}", unexpected); + } + }, + + unexpected => { + warn!("unexpected share control {:?}", unexpected); + } + } + } + + mcs::McsMessage::DisconnectProviderUltimatum(disconnect) => { + if disconnect.reason == mcs::DisconnectReason::UserRequested { + return Ok(true); + } + } + + unexpected => { + warn!("unexpected mcs message {:?}", ironrdp_pdu::name(&unexpected)); + } + } + + Ok(false) + } + + async fn handle_input_event(&mut self, input: InputEventPdu) { + for event in input.0 { + match event { + ironrdp_pdu::input::InputEvent::ScanCode(key) => { + self.handler.keyboard((key.key_code, key.flags).into()).await; + } + + ironrdp_pdu::input::InputEvent::Unicode(key) => { + self.handler.keyboard((key.unicode_code, key.flags).into()).await; + } + + ironrdp_pdu::input::InputEvent::Sync(sync) => { + self.handler.keyboard(sync.flags.into()).await; + } + + ironrdp_pdu::input::InputEvent::Mouse(mouse) => { + self.handler.mouse(mouse.into()).await; + } + + ironrdp_pdu::input::InputEvent::MouseX(mouse) => { + self.handler.mouse(mouse.into()).await; + } + + ironrdp_pdu::input::InputEvent::Unused(_) => {} + } + } + } +} diff --git a/crates/ironrdp/Cargo.toml b/crates/ironrdp/Cargo.toml index 8cc78f78..b3f5fe4f 100644 --- a/crates/ironrdp/Cargo.toml +++ b/crates/ironrdp/Cargo.toml @@ -19,13 +19,17 @@ test = false default = ["pdu", "connector", "session"] pdu = ["dep:ironrdp-pdu"] connector = ["dep:ironrdp-connector"] +acceptor = ["dep:ironrdp-acceptor"] session = ["dep:ironrdp-session"] graphics = ["dep:ironrdp-graphics"] input = ["dep:ironrdp-input"] +server = ["dep:ironrdp-server"] [dependencies] ironrdp-pdu = { workspace = true, optional = true } ironrdp-connector = { workspace = true, optional = true } +ironrdp-acceptor = { workspace = true, optional = true } ironrdp-session = { workspace = true, optional = true } ironrdp-graphics = { workspace = true, optional = true } ironrdp-input = { workspace = true, optional = true } +ironrdp-server = { workspace = true, optional = true } diff --git a/crates/ironrdp/src/lib.rs b/crates/ironrdp/src/lib.rs index 08790551..d5e54c1a 100644 --- a/crates/ironrdp/src/lib.rs +++ b/crates/ironrdp/src/lib.rs @@ -2,6 +2,8 @@ //! //! This is a meta crate re-exporting other ironrdp crates for convenience. +#[cfg(feature = "acceptor")] +pub use ironrdp_acceptor as acceptor; #[cfg(feature = "connector")] pub use ironrdp_connector as connector; #[cfg(feature = "graphics")] @@ -10,5 +12,7 @@ pub use ironrdp_graphics as graphics; pub use ironrdp_input as input; #[cfg(feature = "pdu")] pub use ironrdp_pdu as pdu; +#[cfg(feature = "server")] +pub use ironrdp_server as server; #[cfg(feature = "session")] pub use ironrdp_session as session; diff --git a/fuzz/fuzz_targets/bitmap_stream.rs b/fuzz/fuzz_targets/bitmap_stream.rs index 475290d1..e843a435 100644 --- a/fuzz/fuzz_targets/bitmap_stream.rs +++ b/fuzz/fuzz_targets/bitmap_stream.rs @@ -3,5 +3,6 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|input: ironrdp_fuzzing::generators::BitmapInput<'_>| { - ironrdp_fuzzing::oracles::rdp6_decode_bitmap_stream_to_rgb24(input); + ironrdp_fuzzing::oracles::rdp6_encode_bitmap_stream(&input); + ironrdp_fuzzing::oracles::rdp6_decode_bitmap_stream_to_rgb24(&input); });