mirror of
https://github.com/netbirdio/IronRDP.git
synced 2026-05-22 18:43:12 -07:00
feat: initial server support (#167)
This commit is contained in:
Generated
+28
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
# IronRDP Acceptor
|
||||
|
||||
State machine for the server connection acceptance sequence.
|
||||
@@ -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<u16>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub enum ChannelConnectionState {
|
||||
#[default]
|
||||
Consumed,
|
||||
|
||||
WaitErectDomainRequest,
|
||||
WaitAttachUserRequest,
|
||||
SendAttachUserConfirm,
|
||||
WaitChannelJoinRequest {
|
||||
joined: HashSet<u16>,
|
||||
},
|
||||
SendChannelJoinConfirm {
|
||||
joined: HashSet<u16>,
|
||||
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<u8>) -> ConnectorResult<Written> {
|
||||
let (written, next_state) = match std::mem::take(&mut self.state) {
|
||||
ChannelConnectionState::WaitErectDomainRequest => {
|
||||
let erect_domain_request =
|
||||
ironrdp_pdu::decode::<mcs::ErectDomainPdu>(input).map_err(ConnectorError::pdu)?;
|
||||
|
||||
debug!(message = ?erect_domain_request, "Received");
|
||||
|
||||
(Written::Nothing, ChannelConnectionState::WaitAttachUserRequest)
|
||||
}
|
||||
|
||||
ChannelConnectionState::WaitAttachUserRequest => {
|
||||
let attach_user_request =
|
||||
ironrdp_pdu::decode::<mcs::AttachUserRequest>(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::<mcs::ChannelJoinRequest>(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<u16>) -> 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()
|
||||
}
|
||||
}
|
||||
@@ -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<CapabilitySet>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AcceptorResult {
|
||||
pub channels: Vec<(u16, gcc::Channel)>,
|
||||
pub capabilities: Vec<CapabilitySet>,
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
pub fn new(security: nego::SecurityProtocol, desktop_size: DesktopSize, capabilities: Vec<CapabilitySet>) -> 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<nego::SecurityProtocol> {
|
||||
match self.state {
|
||||
AcceptorState::SecurityUpgrade { .. } => Some(self.security),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_result(&mut self) -> Option<AcceptorResult> {
|
||||
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<gcc::ClientEarlyCapabilityFlags>,
|
||||
channels: Vec<(u16, gcc::Channel)>,
|
||||
},
|
||||
ChannelConnection {
|
||||
early_capability: Option<gcc::ClientEarlyCapabilityFlags>,
|
||||
channels: Vec<(u16, gcc::Channel)>,
|
||||
connection: ChannelConnectionSequence,
|
||||
},
|
||||
RdpSecurityCommencement {
|
||||
early_capability: Option<gcc::ClientEarlyCapabilityFlags>,
|
||||
channels: Vec<(u16, gcc::Channel)>,
|
||||
},
|
||||
SecureSettingsExchange {
|
||||
early_capability: Option<gcc::ClientEarlyCapabilityFlags>,
|
||||
channels: Vec<(u16, gcc::Channel)>,
|
||||
},
|
||||
LicensingExchange {
|
||||
early_capability: Option<gcc::ClientEarlyCapabilityFlags>,
|
||||
channels: Vec<(u16, gcc::Channel)>,
|
||||
},
|
||||
CapabilitiesSendServer {
|
||||
early_capability: Option<gcc::ClientEarlyCapabilityFlags>,
|
||||
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<CapabilitySet>,
|
||||
},
|
||||
Accepted {
|
||||
channels: Vec<(u16, gcc::Channel)>,
|
||||
client_capabilities: Vec<CapabilitySet>,
|
||||
},
|
||||
}
|
||||
|
||||
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<u8>) -> ConnectorResult<Written> {
|
||||
let (written, next_state) = match std::mem::take(&mut self.state) {
|
||||
AcceptorState::InitiationWaitRequest => {
|
||||
let connection_request =
|
||||
ironrdp_pdu::decode::<nego::ConnectionRequest>(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::<mcs::ConnectInitial>(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<u16> = 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::<pdu::mcs::SendDataRequest>(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::<pdu::mcs::SendDataRequest>(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<u16>,
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -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<u8>) -> ConnectorResult<Written> {
|
||||
let (written, next_state) = match std::mem::take(&mut self.state) {
|
||||
FinalizationState::WaitSynchronize => {
|
||||
let data = pdu::decode::<pdu::mcs::SendDataRequest>(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::<pdu::mcs::SendDataRequest>(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::<pdu::mcs::SendDataRequest>(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::<pdu::mcs::SendDataRequest>(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,
|
||||
})
|
||||
}
|
||||
@@ -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<S>
|
||||
where
|
||||
S: StreamWrapper,
|
||||
{
|
||||
ShouldUpgrade(S::InnerStream),
|
||||
Continue(Framed<S>),
|
||||
}
|
||||
|
||||
pub async fn accept_begin<S>(mut framed: Framed<S>, acceptor: &mut Acceptor) -> ConnectorResult<BeginResult<S>>
|
||||
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<S>(
|
||||
mut framed: Framed<S>,
|
||||
acceptor: &mut Acceptor,
|
||||
) -> ConnectorResult<(Framed<S>, 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<S>(
|
||||
framed: &mut Framed<S>,
|
||||
acceptor: &mut Acceptor,
|
||||
buf: &mut Vec<u8>,
|
||||
) -> ConnectorResult<Written>
|
||||
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)
|
||||
}
|
||||
@@ -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<T>(
|
||||
initiator_id: u16,
|
||||
channel_id: u16,
|
||||
user_msg: &T,
|
||||
buf: &mut Vec<u8>,
|
||||
) -> ConnectorResult<usize>
|
||||
where
|
||||
T: PduParsing,
|
||||
ConnectorError: From<T::Error>,
|
||||
{
|
||||
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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -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::<RgbChannels>(
|
||||
input.src,
|
||||
out.as_mut_slice(),
|
||||
false,
|
||||
);
|
||||
|
||||
let _ = BitmapStreamEncoder::new(input.width.into(), input.height.into()).encode_bitmap::<RgbAChannels>(
|
||||
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();
|
||||
|
||||
@@ -88,6 +88,24 @@ pub enum PixelFormat {
|
||||
RgbX32 = 537_069_704,
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for PixelFormat {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
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 {
|
||||
|
||||
+14
-102
@@ -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<u8>) -> 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<T> 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<T> 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<R, G, B>(
|
||||
&mut self,
|
||||
(r, g, b): (R, G, B),
|
||||
dst: &mut [u8],
|
||||
rle: bool,
|
||||
) -> Result<usize, BitmapEncodeError>
|
||||
where
|
||||
R: Iterator<Item = u8>,
|
||||
G: Iterator<Item = u8>,
|
||||
B: Iterator<Item = u8>,
|
||||
{
|
||||
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(
|
||||
<PduError as ironrdp_pdu::PduErrorExt>::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<usize, BitmapEncodeError>
|
||||
where
|
||||
F: PixelFormat,
|
||||
I: Iterator<Item = &'a [u8]> + 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<F>(&mut self, src: &[u8], dst: &mut [u8], rle: bool) -> Result<usize, BitmapEncodeError>
|
||||
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<R, G, B, A>(
|
||||
&mut self,
|
||||
(r, g, b, a): (R, G, B, A),
|
||||
dst: &mut [u8],
|
||||
rle: bool,
|
||||
) -> Result<usize, BitmapEncodeError>
|
||||
where
|
||||
R: Iterator<Item = u8>,
|
||||
G: Iterator<Item = u8>,
|
||||
B: Iterator<Item = u8>,
|
||||
A: Iterator<Item = u8>,
|
||||
{
|
||||
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(
|
||||
<PduError as ironrdp_pdu::PduErrorExt>::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<F>(&mut self, src: &[u8], dst: &mut [u8], rle: bool) -> Result<usize, BitmapEncodeError>
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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<u8> {
|
||||
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::<RgbChannels>(&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::<RgbChannels>(&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);
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
@@ -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<usize, RleError> {
|
||||
let control_byte = src.read_u8().map_err(RleError::ReadCompressedData)?;
|
||||
fn decompress_next_segment(&mut self, mut src: &[u8]) -> Result<usize, RleDecodeError> {
|
||||
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<usize, RleError> {
|
||||
fn decode_scanline(&mut self, src: &[u8], mut dst: &mut [u8]) -> Result<usize, RleDecodeError> {
|
||||
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<usize, RleError> {
|
||||
pub fn decode(mut self, src: &[u8], dst: &mut [u8]) -> Result<usize, RleDecodeError> {
|
||||
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<usize>,
|
||||
height: impl Into<usize>,
|
||||
) -> Result<usize, RleError> {
|
||||
let width = width.into();
|
||||
let height = height.into();
|
||||
|
||||
pub fn decompress_8bpp_plane(src: &[u8], dst: &mut [u8], width: usize, height: usize) -> Result<usize, RleDecodeError> {
|
||||
RlePlaneDecoder::new(width, height).decode(src, dst)
|
||||
}
|
||||
|
||||
struct RleEncoderScanlineIterator<I> {
|
||||
inner: std::iter::Enumerate<I>,
|
||||
width: usize,
|
||||
prev_scanline: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<I: Iterator> RleEncoderScanlineIterator<I> {
|
||||
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<I: Iterator<Item = u8>> Iterator for RleEncoderScanlineIterator<I> {
|
||||
type Item = I::Item;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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<usize>) {
|
||||
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<Item = u8>,
|
||||
dst: &mut WriteCursor<'_>,
|
||||
) -> Result<usize, RleEncodeError> {
|
||||
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<Item = u8>,
|
||||
dst: &mut WriteCursor<'_>,
|
||||
) -> Result<usize, RleEncodeError> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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<Item = u8>,
|
||||
dst: &mut WriteCursor<'_>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
) -> Result<usize, RleEncodeError> {
|
||||
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<u8>,
|
||||
width: impl Into<usize>,
|
||||
height: impl Into<usize>,
|
||||
) -> Result<usize, RleError> {
|
||||
let width = width.into();
|
||||
let height = height.into();
|
||||
pub fn decompress(src: &[u8], dst: &mut Vec<u8>, width: usize, height: usize) -> Result<usize, RleDecodeError> {
|
||||
// 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<u8>, width: usize, height: usize) -> Result<usize, RleEncodeError> {
|
||||
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];
|
||||
|
||||
@@ -24,6 +24,17 @@ impl BitmapUpdateData<'_> {
|
||||
const FIXED_PART_SIZE: usize = core::mem::size_of::<u16>() * 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)?;
|
||||
|
||||
@@ -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<Self> {
|
||||
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<Self> {
|
||||
ensure_fixed_part_size!(in: src);
|
||||
let header = src.read_u8();
|
||||
let header = crate::decode_cursor::<BitmapStreamHeader>(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: [],
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
|
||||
@@ -27,6 +27,14 @@ impl FastPathHeader {
|
||||
const NAME: &str = "TS_FP_UPDATE_PDU header";
|
||||
const FIXED_PART_SIZE: usize = std::mem::size_of::<EncryptionFlags>();
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user