Skip to content

Commit 9383380

Browse files
authored
feat(session): handle slow-path graphics and pointer updates (#1132)
Adds support for slow-path graphics and pointer updates to IronRDP, fixing connectivity issues with servers like XRDP that use slow-path output instead of fast-path. The implementation parses slow-path framing headers and routes the inner payload structures through the existing fast-path processing pipeline by extracting shared bitmap and pointer processing methods.
1 parent 5c08c7f commit 9383380

8 files changed

Lines changed: 618 additions & 241 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod bitmap;
22
pub mod fast_path;
33
pub mod pointer;
4+
pub mod slow_path;
45
pub mod surface_commands;
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Slow-path graphics and pointer update parsing.
2+
//
3+
// Slow-path updates arrive inside ShareDataPdu::Update (graphics) and
4+
// ShareDataPdu::Pointer, wrapped with a small framing header that differs
5+
// from the fast-path encoding. The inner payload structures are identical
6+
// to their fast-path counterparts.
7+
//
8+
// References:
9+
// [MS-RDPBCGR] 2.2.9.1.1.3 — Slow-Path Graphics Update
10+
// [MS-RDPBCGR] 2.2.9.1.1.4 — Slow-Path Pointer Update
11+
12+
use ironrdp_core::{Decode as _, DecodeResult, ReadCursor, ensure_size, invalid_field_err};
13+
14+
use super::bitmap::{BitmapData, BitmapUpdateData};
15+
use super::pointer::{
16+
CachedPointerAttribute, ColorPointerAttribute, LargePointerAttribute, PointerAttribute, PointerPositionAttribute,
17+
PointerUpdateData,
18+
};
19+
20+
// --- Graphics updates ([MS-RDPBCGR] 2.2.9.1.1.3.1) ---
21+
22+
/// `updateType` field in TS_UPDATE_HDR for slow-path graphics updates.
23+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24+
#[repr(u16)]
25+
pub enum GraphicsUpdateType {
26+
Orders = 0x0000,
27+
Bitmap = 0x0001,
28+
Palette = 0x0002,
29+
Synchronize = 0x0003,
30+
}
31+
32+
/// Read the `updateType` u16 from the front of a slow-path graphics update.
33+
pub fn read_graphics_update_type(src: &mut ReadCursor<'_>) -> DecodeResult<GraphicsUpdateType> {
34+
ensure_size!(in: src, size: 2);
35+
let raw = src.read_u16();
36+
match raw {
37+
0x0000 => Ok(GraphicsUpdateType::Orders),
38+
0x0001 => Ok(GraphicsUpdateType::Bitmap),
39+
0x0002 => Ok(GraphicsUpdateType::Palette),
40+
0x0003 => Ok(GraphicsUpdateType::Synchronize),
41+
_ => Err(invalid_field_err!(
42+
"updateType",
43+
"unknown slow-path graphics update type"
44+
)),
45+
}
46+
}
47+
48+
/// Decode a slow-path bitmap update.
49+
///
50+
/// The cursor must be positioned right after the `updateType` field
51+
/// (i.e. already consumed by [`read_graphics_update_type`]).
52+
pub fn decode_slow_path_bitmap<'a>(src: &mut ReadCursor<'a>) -> DecodeResult<BitmapUpdateData<'a>> {
53+
// Read numberRectangles directly; updateType was already consumed by the dispatcher.
54+
ensure_size!(in: src, size: 2);
55+
let rectangle_count = usize::from(src.read_u16());
56+
let mut rectangles = Vec::with_capacity(rectangle_count);
57+
for _ in 0..rectangle_count {
58+
rectangles.push(BitmapData::decode(src)?);
59+
}
60+
Ok(BitmapUpdateData { rectangles })
61+
}
62+
63+
// --- Pointer updates ([MS-RDPBCGR] 2.2.9.1.1.4) ---
64+
65+
/// `messageType` values for slow-path pointer updates.
66+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67+
#[repr(u16)]
68+
pub enum PointerMessageType {
69+
System = 0x0001,
70+
Position = 0x0003,
71+
Color = 0x0006,
72+
Cached = 0x0007,
73+
/// TS_POINTERATTRIBUTE (new pointer with xor_bpp)
74+
Pointer = 0x0008,
75+
Large = 0x0009,
76+
}
77+
78+
/// `systemPointerType` values used when `messageType == System`.
79+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80+
#[repr(u32)]
81+
pub enum SystemPointerType {
82+
/// SYSPTR_NULL — hide the pointer
83+
Null = 0x0000_0000,
84+
/// SYSPTR_DEFAULT — show the OS default pointer
85+
Default = 0x0000_7F00,
86+
}
87+
88+
/// Decode a complete slow-path pointer update from its raw payload.
89+
///
90+
/// The payload starts with `messageType(u16)` + `pad2Octets(u16)`,
91+
/// followed by the type-specific data.
92+
pub fn decode_slow_path_pointer<'a>(src: &mut ReadCursor<'a>) -> DecodeResult<PointerUpdateData<'a>> {
93+
ensure_size!(in: src, size: 4);
94+
let message_type = src.read_u16();
95+
let _pad = src.read_u16();
96+
97+
match message_type {
98+
0x0001 => {
99+
// System pointer: the body is a single u32 indicating which system pointer.
100+
ensure_size!(in: src, size: 4);
101+
let system_type = src.read_u32();
102+
match system_type {
103+
0x0000_0000 => Ok(PointerUpdateData::SetHidden),
104+
0x0000_7F00 => Ok(PointerUpdateData::SetDefault),
105+
_ => Err(invalid_field_err!("systemPointerType", "unknown system pointer type")),
106+
}
107+
}
108+
0x0003 => {
109+
let pos = PointerPositionAttribute::decode(src)?;
110+
Ok(PointerUpdateData::SetPosition(pos))
111+
}
112+
0x0006 => {
113+
let color = ColorPointerAttribute::decode(src)?;
114+
Ok(PointerUpdateData::Color(color))
115+
}
116+
0x0007 => {
117+
let cached = CachedPointerAttribute::decode(src)?;
118+
Ok(PointerUpdateData::Cached(cached))
119+
}
120+
0x0008 => {
121+
let attr = PointerAttribute::decode(src)?;
122+
Ok(PointerUpdateData::New(attr))
123+
}
124+
0x0009 => {
125+
let large = LargePointerAttribute::decode(src)?;
126+
Ok(PointerUpdateData::Large(large))
127+
}
128+
_ => Err(invalid_field_err!(
129+
"messageType",
130+
"unknown slow-path pointer message type"
131+
)),
132+
}
133+
}

crates/ironrdp-pdu/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub(crate) mod ber;
2929
pub(crate) mod crypto;
3030
pub(crate) mod per;
3131

32-
pub use crate::basic_output::{bitmap, fast_path, pointer, surface_commands};
32+
pub use crate::basic_output::{bitmap, fast_path, pointer, slow_path, surface_commands};
3333
pub use crate::rdp::vc::dvc;
3434

3535
pub type PduResult<T> = Result<T, PduError>;

crates/ironrdp-session/src/active_stage.rs

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::sync::Arc;
33
use ironrdp_bulk::BulkCompressor;
44
use ironrdp_connector::ConnectionResult;
55
use ironrdp_connector::connection_activation::ConnectionActivationSequence;
6-
use ironrdp_core::WriteBuf;
6+
use ironrdp_core::{ReadCursor, WriteBuf};
77
use ironrdp_displaycontrol::client::DisplayControlClient;
88
use ironrdp_dvc::{DrdynvcClient, DvcProcessor, DynamicVirtualChannel};
99
use ironrdp_graphics::pointer::DecodedPointer;
@@ -13,9 +13,10 @@ use ironrdp_pdu::rdp::autodetect::AutoDetectRequest;
1313
use ironrdp_pdu::rdp::client_info::CompressionType as PduCompressionType;
1414
use ironrdp_pdu::rdp::headers::ShareDataPdu;
1515
use ironrdp_pdu::rdp::multitransport::MultitransportRequestPdu;
16+
use ironrdp_pdu::slow_path::{self, GraphicsUpdateType};
1617
use ironrdp_pdu::{Action, mcs};
1718
use ironrdp_svc::{SvcMessage, SvcProcessor, SvcProcessorMessages};
18-
use tracing::{debug, info};
19+
use tracing::{debug, info, warn};
1920

2021
use crate::fast_path::UpdateKind;
2122
use crate::image::DecodedImage;
@@ -147,13 +148,27 @@ impl ActiveStage {
147148
)
148149
}
149150
Action::X224 => {
150-
let outputs = self
151-
.x224_processor
152-
.process(frame)?
153-
.into_iter()
154-
.map(TryFrom::try_from)
155-
.collect::<Result<Vec<_>, _>>()?;
156-
(outputs, Vec::new())
151+
let x224_outputs = self.x224_processor.process(frame)?;
152+
let mut stage_outputs = Vec::new();
153+
let mut processor_updates = Vec::new();
154+
155+
for output in x224_outputs {
156+
match output {
157+
x224::ProcessorOutput::GraphicsUpdate(data) => {
158+
let updates = process_slow_path_graphics(&mut self.fast_path_processor, image, &data)?;
159+
processor_updates.extend(updates);
160+
}
161+
x224::ProcessorOutput::PointerUpdate(data) => {
162+
let updates = process_slow_path_pointer(&mut self.fast_path_processor, image, &data)?;
163+
processor_updates.extend(updates);
164+
}
165+
other => {
166+
stage_outputs.push(ActiveStageOutput::try_from(other)?);
167+
}
168+
}
169+
}
170+
171+
(stage_outputs, processor_updates)
157172
}
158173
};
159174

@@ -345,6 +360,11 @@ impl TryFrom<x224::ProcessorOutput> for ActiveStageOutput {
345360
x224::ProcessorOutput::DeactivateAll(cas) => Ok(Self::DeactivateAll(cas)),
346361
x224::ProcessorOutput::MultitransportRequest(pdu) => Ok(Self::MultitransportRequest(pdu)),
347362
x224::ProcessorOutput::AutoDetect(request) => Ok(Self::AutoDetect(request)),
363+
// GraphicsUpdate and PointerUpdate are consumed in ActiveStage::process()
364+
// before reaching this conversion.
365+
x224::ProcessorOutput::GraphicsUpdate(_) | x224::ProcessorOutput::PointerUpdate(_) => Err(
366+
SessionError::general("slow-path graphics/pointer updates should be handled before this conversion"),
367+
),
348368
}
349369
}
350370
}
@@ -373,3 +393,45 @@ impl core::fmt::Display for GracefulDisconnectReason {
373393
f.write_str(&self.description())
374394
}
375395
}
396+
397+
/// Parse and process a slow-path graphics update through the shared bitmap pipeline.
398+
fn process_slow_path_graphics(
399+
fast_path_processor: &mut fast_path::Processor,
400+
image: &mut DecodedImage,
401+
data: &[u8],
402+
) -> SessionResult<Vec<UpdateKind>> {
403+
let mut src = ReadCursor::new(data);
404+
let update_type = slow_path::read_graphics_update_type(&mut src).map_err(SessionError::decode)?;
405+
406+
match update_type {
407+
GraphicsUpdateType::Bitmap => {
408+
let bitmap = slow_path::decode_slow_path_bitmap(&mut src).map_err(SessionError::decode)?;
409+
fast_path_processor.process_bitmap_update(image, bitmap)
410+
}
411+
GraphicsUpdateType::Orders => {
412+
warn!("Slow-path drawing orders not supported (MS-RDPEGDI)");
413+
Ok(Vec::new())
414+
}
415+
GraphicsUpdateType::Palette => {
416+
warn!("Slow-path palette update not supported (8bpp)");
417+
Ok(Vec::new())
418+
}
419+
// Synchronize is an artifact from the T.128 multipoint protocol
420+
// and carries no data. Safe to ignore.
421+
GraphicsUpdateType::Synchronize => {
422+
debug!("Ignoring slow-path synchronize update");
423+
Ok(Vec::new())
424+
}
425+
}
426+
}
427+
428+
/// Parse and process a slow-path pointer update through the shared pointer pipeline.
429+
fn process_slow_path_pointer(
430+
fast_path_processor: &mut fast_path::Processor,
431+
image: &mut DecodedImage,
432+
data: &[u8],
433+
) -> SessionResult<Vec<UpdateKind>> {
434+
let mut src = ReadCursor::new(data);
435+
let pointer = slow_path::decode_slow_path_pointer(&mut src).map_err(SessionError::decode)?;
436+
fast_path_processor.process_pointer_update(image, pointer)
437+
}

0 commit comments

Comments
 (0)