use std::sync::Arc;
use rat_rdp_bulk::{BulkCompressor, CompressionType as BulkCompressionType};
use rat_rdp_core::{ReadCursor, WriteBuf};
use rat_rdp_dvc::{DrdynvcClient, DvcClientProcessor, DynamicChannelRef};
use rat_rdp_graphics::pointer::DecodedPointer;
use rat_rdp_pdu::gcc::{ChannelName, Monitor};
use rat_rdp_pdu::geometry::InclusiveRectangle;
use rat_rdp_pdu::input::fast_path::{FastPathInput, FastPathInputEvent};
use rat_rdp_pdu::rdp::autodetect::AutoDetectRequest;
use rat_rdp_pdu::rdp::capability_sets::WindowSupportLevel;
use rat_rdp_pdu::rdp::client_info::CompressionType;
use rat_rdp_pdu::rdp::headers::ShareDataPdu;
use rat_rdp_pdu::rdp::multitransport::MultitransportRequestPdu;
use rat_rdp_pdu::rdp::refresh_rectangle::RefreshRectanglePdu;
use rat_rdp_pdu::rdp::session_info::ServerAutoReconnect;
use rat_rdp_pdu::rdp::suppress_output::SuppressOutputPdu;
use rat_rdp_pdu::slow_path::{self, GraphicsUpdateType};
use rat_rdp_pdu::window::{
WindowingOrdersUpdate, try_decode_fast_path_windowing_orders, try_decode_slow_path_windowing_orders,
};
use rat_rdp_pdu::{Action, mcs};
use rat_rdp_svc::{StaticChannelSet, SvcMessage, SvcProcessor, SvcProcessorMessages};
use tracing::debug;
use crate::fast_path::UpdateKind;
use crate::image::DecodedImage;
use crate::{SessionError, SessionErrorExt as _, SessionResult, fast_path, x224};
fn to_bulk_compression_type(compression_type: CompressionType) -> BulkCompressionType {
match compression_type {
CompressionType::K8 => BulkCompressionType::Rdp4,
CompressionType::K64 => BulkCompressionType::Rdp5,
CompressionType::Rdp6 => BulkCompressionType::Rdp6,
CompressionType::Rdp61 => BulkCompressionType::Rdp61,
}
}
pub struct ActiveStage {
x224_processor: x224::Processor,
fast_path_processor: fast_path::Processor,
bulk_decompressor: Option<BulkCompressor>,
enable_server_pointer: bool,
window_support_level: Option<WindowSupportLevel>,
}
pub struct ActiveStageBuilder {
pub static_channels: StaticChannelSet,
pub user_channel_id: u16,
pub io_channel_id: u16,
pub message_channel_id: Option<u16>,
pub share_id: u32,
pub compression_type: Option<CompressionType>,
pub enable_server_pointer: bool,
pub pointer_software_rendering: bool,
}
impl ActiveStageBuilder {
pub fn build(self) -> ActiveStage {
let Self {
static_channels,
user_channel_id,
io_channel_id,
message_channel_id,
share_id,
compression_type,
enable_server_pointer,
pointer_software_rendering,
} = self;
let x224_processor = x224::Processor::new(
static_channels,
user_channel_id,
io_channel_id,
message_channel_id,
share_id,
);
let fast_path_processor = fast_path::ProcessorBuilder {
io_channel_id,
user_channel_id,
share_id,
enable_server_pointer,
pointer_software_rendering,
}
.build();
ActiveStage {
x224_processor,
fast_path_processor,
bulk_decompressor: new_bulk_decompressor(compression_type),
enable_server_pointer,
window_support_level: None,
}
}
}
fn new_bulk_decompressor(compression_type: Option<CompressionType>) -> Option<BulkCompressor> {
compression_type.map(|compression_type| BulkCompressor::new(to_bulk_compression_type(compression_type)))
}
impl ActiveStage {
pub fn update_mouse_pos(&mut self, x: u16, y: u16) {
self.fast_path_processor.update_mouse_pos(x, y);
}
pub fn take_bitmap_recovery_request(&mut self) -> bool {
self.fast_path_processor.take_bitmap_recovery_request()
}
pub fn process_fastpath_input(
&mut self,
image: &mut DecodedImage,
events: &[FastPathInputEvent],
) -> SessionResult<Vec<ActiveStageOutput>> {
if events.is_empty() {
return Ok(Vec::new());
}
let mut output = Vec::with_capacity(events.len().div_ceil(FastPathInput::MAX_EVENTS) + 1);
for event_chunk in events.chunks(FastPathInput::MAX_EVENTS) {
let fastpath_input = FastPathInput::new(event_chunk.to_vec()).map_err(SessionError::decode)?;
let frame = rat_rdp_core::encode_vec(&fastpath_input).map_err(SessionError::encode)?;
output.push(ActiveStageOutput::ResponseFrame(frame));
}
if !self.enable_server_pointer {
return Ok(output);
}
let mouse_pos = events.iter().find_map(|event| match event {
FastPathInputEvent::MouseEvent(event) => Some((event.x_position, event.y_position)),
FastPathInputEvent::MouseEventEx(event) => Some((event.x_position, event.y_position)),
_ => None,
});
let (mouse_x, mouse_y) = match mouse_pos {
Some(mouse_pos) => mouse_pos,
None => return Ok(output),
};
if let Some(rect) = image.move_pointer(mouse_x, mouse_y)? {
output.push(ActiveStageOutput::GraphicsUpdate(rect));
}
Ok(output)
}
pub fn process(
&mut self,
image: &mut DecodedImage,
action: Action,
frame: &[u8],
) -> SessionResult<Vec<ActiveStageOutput>> {
let (mut stage_outputs, processor_updates) = match action {
Action::FastPath => {
let mut output = WriteBuf::new();
let processor_updates =
self.fast_path_processor
.process(image, frame, &mut output, &mut self.bulk_decompressor)?;
(
vec![ActiveStageOutput::ResponseFrame(output.into_inner())],
processor_updates,
)
}
Action::X224 => {
let x224_outputs = self.x224_processor.process(frame, &mut self.bulk_decompressor)?;
let mut stage_outputs = Vec::new();
let mut processor_updates = Vec::new();
for output in x224_outputs {
match output {
x224::ProcessorOutput::GraphicsUpdate(data) => {
let (updates, windowing_orders) = process_slow_path_graphics(
&mut self.fast_path_processor,
image,
self.window_support_level,
&data,
)?;
processor_updates.extend(updates);
if let Some(windowing_orders) = windowing_orders {
stage_outputs.push(ActiveStageOutput::WindowingOrders(windowing_orders));
}
}
x224::ProcessorOutput::PointerUpdate(data) => {
let updates = process_slow_path_pointer(&mut self.fast_path_processor, image, &data)?;
processor_updates.extend(updates);
}
other => {
stage_outputs.push(ActiveStageOutput::try_from(other)?);
}
}
}
(stage_outputs, processor_updates)
}
};
for update in processor_updates {
match update {
UpdateKind::None => {}
UpdateKind::Orders(data) => {
if let Some(windowing_orders) =
process_fast_path_windowing_orders(self.window_support_level, &data)?
{
stage_outputs.push(ActiveStageOutput::WindowingOrders(windowing_orders));
}
}
UpdateKind::Region(region) => {
stage_outputs.push(ActiveStageOutput::GraphicsUpdate(region));
}
UpdateKind::PointerDefault => {
stage_outputs.push(ActiveStageOutput::PointerDefault);
}
UpdateKind::PointerHidden => {
stage_outputs.push(ActiveStageOutput::PointerHidden);
}
UpdateKind::PointerPosition { x, y } => {
stage_outputs.push(ActiveStageOutput::PointerPosition { x, y });
}
UpdateKind::PointerBitmap(pointer) => {
stage_outputs.push(ActiveStageOutput::PointerBitmap(pointer));
}
}
}
Ok(stage_outputs)
}
pub fn set_fastpath_processor(&mut self, processor: fast_path::Processor) {
self.fast_path_processor = processor;
}
pub fn set_share_id(&mut self, share_id: u32) {
self.x224_processor.set_share_id(share_id);
}
pub fn set_static_channel_chunk_size(&mut self, maximum_chunk_size: usize) -> bool {
self.x224_processor.set_static_channel_chunk_size(maximum_chunk_size)
}
pub fn static_channel_chunk_size(&self) -> usize {
self.x224_processor.static_channel_chunk_size()
}
pub fn set_enable_server_pointer(&mut self, enable_server_pointer: bool) {
self.enable_server_pointer = enable_server_pointer;
}
pub fn set_window_support_level(&mut self, window_support_level: Option<WindowSupportLevel>) {
self.window_support_level = window_support_level;
}
pub fn reactivate(
&mut self,
io_channel_id: u16,
user_channel_id: u16,
share_id: u32,
enable_server_pointer: bool,
pointer_software_rendering: bool,
static_channel_chunk_size: usize,
) -> bool {
if !self
.x224_processor
.set_static_channel_chunk_size(static_channel_chunk_size)
{
return false;
}
self.fast_path_processor = fast_path::ProcessorBuilder {
io_channel_id,
user_channel_id,
share_id,
enable_server_pointer,
pointer_software_rendering,
}
.build();
self.x224_processor.set_share_id(share_id);
self.enable_server_pointer = enable_server_pointer;
true
}
pub fn graceful_shutdown(&self) -> SessionResult<Vec<ActiveStageOutput>> {
let mut frame = WriteBuf::new();
self.x224_processor
.encode_static(&mut frame, ShareDataPdu::ShutdownRequest)?;
Ok(vec![ActiveStageOutput::ResponseFrame(frame.into_inner())])
}
fn request_full_refresh(&self, width: u16, height: u16) -> SessionResult<Vec<u8>> {
debug_assert!(width != 0 && height != 0);
let mut frame = WriteBuf::new();
self.x224_processor.encode_static(
&mut frame,
ShareDataPdu::RefreshRectangle(RefreshRectanglePdu {
areas_to_refresh: vec![InclusiveRectangle {
left: 0,
top: 0,
right: width.saturating_sub(1),
bottom: height.saturating_sub(1),
}],
}),
)?;
Ok(frame.into_inner())
}
pub fn request_full_redraw(
&self,
width: u16,
height: u16,
refresh_rect_support: bool,
suppress_output_support: bool,
) -> SessionResult<Vec<Vec<u8>>> {
debug_assert!(width != 0 && height != 0);
if suppress_output_support {
let mut suppress = WriteBuf::new();
self.x224_processor.encode_static(
&mut suppress,
ShareDataPdu::SuppressOutput(SuppressOutputPdu { desktop_rect: None }),
)?;
let mut resume = WriteBuf::new();
self.x224_processor.encode_static(
&mut resume,
ShareDataPdu::SuppressOutput(SuppressOutputPdu {
desktop_rect: Some(InclusiveRectangle {
left: 0,
top: 0,
right: width.saturating_sub(1),
bottom: height.saturating_sub(1),
}),
}),
)?;
return Ok(vec![suppress.into_inner(), resume.into_inner()]);
}
if refresh_rect_support {
return Ok(vec![self.request_full_refresh(width, height)?]);
}
Ok(Vec::new())
}
pub fn encode_static(&self, output: &mut WriteBuf, pdu: ShareDataPdu) -> SessionResult<usize> {
self.x224_processor.encode_static(output, pdu)
}
pub fn get_svc_processor<T: SvcProcessor + 'static>(&mut self) -> Option<&T> {
self.x224_processor.get_svc_processor()
}
pub fn get_svc_processor_mut<T: SvcProcessor + 'static>(&mut self) -> Option<&mut T> {
self.x224_processor.get_svc_processor_mut()
}
pub fn get_dvc<T: DvcClientProcessor + 'static>(&self) -> Option<DynamicChannelRef<'_, T>> {
self.x224_processor.get_dvc::<T>()
}
pub fn get_dvc_by_channel_id<T: DvcClientProcessor + 'static>(
&self,
channel_id: u32,
) -> Option<DynamicChannelRef<'_, T>> {
self.x224_processor.get_dvc_by_channel_id(channel_id)
}
pub fn display_control_ready(&mut self) -> Option<bool> {
None
}
pub fn process_svc_processor_messages<C: SvcProcessor + 'static>(
&self,
messages: SvcProcessorMessages<C>,
) -> SessionResult<Vec<u8>> {
self.x224_processor.process_svc_processor_messages(messages)
}
pub fn process_svc_messages_by_name(
&self,
channel_name: &ChannelName,
messages: Vec<SvcMessage>,
) -> SessionResult<Vec<u8>> {
self.x224_processor.process_svc_messages_by_name(channel_name, messages)
}
pub fn encode_resize(
&mut self,
_width: u32,
_height: u32,
_scale_factor: Option<u32>,
_physical_dims: Option<(u32, u32)>,
) -> Option<SessionResult<Vec<u8>>> {
None
}
pub fn rdpei_ready(&mut self) -> Option<bool> {
None
}
pub fn rdpei_can_send_touch(&mut self) -> bool {
false
}
pub fn encode_rdpei_touch(&mut self, _event: ()) -> Option<SessionResult<Vec<u8>>> {
None
}
pub fn encode_rdpei_dismiss_hovering(&mut self, _contact_id: u8) -> Option<SessionResult<Vec<u8>>> {
None
}
pub fn encode_rdpei_pen(&mut self, _event: ()) -> Option<SessionResult<Vec<u8>>> {
None
}
pub fn encode_dvc_messages(&mut self, messages: Vec<SvcMessage>) -> SessionResult<Vec<u8>> {
self.process_svc_processor_messages(SvcProcessorMessages::<DrdynvcClient>::new(messages))
}
}
#[derive(Debug)]
pub enum ActiveStageOutput {
ResponseFrame(Vec<u8>),
GraphicsUpdate(InclusiveRectangle),
PointerDefault,
PointerHidden,
PointerPosition {
x: u16,
y: u16,
},
PointerBitmap(Arc<DecodedPointer>),
MonitorLayout(Vec<Monitor>),
WindowingOrders(Vec<u8>),
Terminate(GracefulDisconnectReason),
SaveSessionInfo {
logon_complete: bool,
},
DeactivateAll,
MultitransportRequest(MultitransportRequestPdu),
AutoDetect(AutoDetectRequest),
AutoReconnectCookie(ServerAutoReconnect),
AutoReconnectFailed,
}
impl TryFrom<x224::ProcessorOutput> for ActiveStageOutput {
type Error = SessionError;
fn try_from(value: x224::ProcessorOutput) -> Result<Self, Self::Error> {
match value {
x224::ProcessorOutput::ResponseFrame(frame) => Ok(Self::ResponseFrame(frame)),
x224::ProcessorOutput::Disconnect(desc) => {
let desc = match desc {
x224::DisconnectDescription::McsDisconnect(reason) => match reason {
mcs::DisconnectReason::ProviderInitiated => GracefulDisconnectReason::ServerInitiated,
mcs::DisconnectReason::UserRequested => GracefulDisconnectReason::UserInitiated,
other => GracefulDisconnectReason::Other(other.description().to_owned()),
},
x224::DisconnectDescription::ErrorInfo(info) => GracefulDisconnectReason::Other(info.description()),
};
Ok(Self::Terminate(desc))
}
x224::ProcessorOutput::SaveSessionInfo { logon_complete } => Ok(Self::SaveSessionInfo { logon_complete }),
x224::ProcessorOutput::DeactivateAll => Ok(Self::DeactivateAll),
x224::ProcessorOutput::MultitransportRequest(pdu) => Ok(Self::MultitransportRequest(pdu)),
x224::ProcessorOutput::AutoDetect(request) => Ok(Self::AutoDetect(request)),
x224::ProcessorOutput::AutoReconnectCookie(cookie) => Ok(Self::AutoReconnectCookie(cookie)),
x224::ProcessorOutput::AutoReconnectFailed => Ok(Self::AutoReconnectFailed),
x224::ProcessorOutput::MonitorLayout(monitors) => Ok(Self::MonitorLayout(monitors)),
x224::ProcessorOutput::GraphicsUpdate(_) | x224::ProcessorOutput::PointerUpdate(_) => Err(
SessionError::general("slow-path graphics/pointer updates should be handled before this conversion"),
),
}
}
}
#[derive(Debug, Clone)]
pub enum GracefulDisconnectReason {
UserInitiated,
ServerInitiated,
Other(String),
}
impl GracefulDisconnectReason {
pub fn description(&self) -> String {
match self {
GracefulDisconnectReason::UserInitiated => "user initiated disconnect".to_owned(),
GracefulDisconnectReason::ServerInitiated => "server initiated disconnect".to_owned(),
GracefulDisconnectReason::Other(description) => description.clone(),
}
}
}
impl core::fmt::Display for GracefulDisconnectReason {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.description())
}
}
fn process_slow_path_graphics(
fast_path_processor: &mut fast_path::Processor,
image: &mut DecodedImage,
window_support_level: Option<WindowSupportLevel>,
data: &[u8],
) -> SessionResult<(Vec<UpdateKind>, Option<Vec<u8>>)> {
let mut src = ReadCursor::new(data);
let update_type = slow_path::read_graphics_update_type(&mut src).map_err(SessionError::decode)?;
match update_type {
GraphicsUpdateType::Bitmap => {
let bitmap = slow_path::decode_slow_path_bitmap(&mut src).map_err(SessionError::decode)?;
fast_path_processor
.process_bitmap_update(image, bitmap)
.map(|updates| (updates, None))
}
GraphicsUpdateType::Orders => {
let Some(window_support_level) = window_support_level else {
return Ok((Vec::new(), None));
};
let orders = try_decode_slow_path_windowing_orders(&mut src).map_err(SessionError::decode)?;
validate_windowing_orders_support(&orders, window_support_level)?;
Ok((Vec::new(), Some(data.to_vec())))
}
GraphicsUpdateType::Palette => {
fast_path_processor.process_palette_update(data);
Ok((Vec::new(), None))
}
GraphicsUpdateType::Synchronize => {
debug!("Ignoring slow-path synchronize update");
Ok((Vec::new(), None))
}
}
}
fn process_fast_path_windowing_orders(
window_support_level: Option<WindowSupportLevel>,
data: &[u8],
) -> SessionResult<Option<Vec<u8>>> {
let Some(window_support_level) = window_support_level else {
return Ok(None);
};
let mut src = ReadCursor::new(data);
let orders = try_decode_fast_path_windowing_orders(&mut src).map_err(SessionError::decode)?;
validate_windowing_orders_support(&orders, window_support_level)?;
let mut normalized = Vec::with_capacity(
2 + 2 + data.len() + 2,
);
normalized.extend_from_slice(&0u16.to_le_bytes());
normalized.extend_from_slice(&0u16.to_le_bytes());
normalized.extend_from_slice(&data[..2]);
normalized.extend_from_slice(&0u16.to_le_bytes());
normalized.extend_from_slice(&data[2..]);
Ok(Some(normalized))
}
fn validate_windowing_orders_support(
orders: &WindowingOrdersUpdate<'_>,
window_support_level: WindowSupportLevel,
) -> SessionResult<()> {
if window_support_level == WindowSupportLevel::SupportedEx
|| !orders.orders.iter().any(|order| order.requires_extended_support())
{
return Ok(());
}
Err(SessionError::general(
"received extended window order fields without negotiated extended window support",
))
}
fn process_slow_path_pointer(
fast_path_processor: &mut fast_path::Processor,
image: &mut DecodedImage,
data: &[u8],
) -> SessionResult<Vec<UpdateKind>> {
let mut src = ReadCursor::new(data);
let pointer = slow_path::decode_slow_path_pointer(&mut src).map_err(SessionError::decode)?;
fast_path_processor.process_pointer_update(image, pointer)
}
#[cfg(test)]
mod tests {
use rat_rdp_core::Decode as _;
use rat_rdp_graphics::image_processing::PixelFormat;
use rat_rdp_pdu::gcc::MonitorFlags;
use rat_rdp_pdu::input::fast_path::KeyboardFlags;
use rat_rdp_pdu::pointer::{ColorPointerAttribute, Point16, PointerAttribute, PointerUpdateData};
use super::*;
#[test]
fn full_redraw_prefers_suppress_output_toggle_when_supported() {
let stage = ActiveStageBuilder {
static_channels: StaticChannelSet::new(),
user_channel_id: 1001,
io_channel_id: 1003,
message_channel_id: None,
share_id: 1,
compression_type: None,
enable_server_pointer: true,
pointer_software_rendering: false,
}
.build();
let suppress_output_frames = stage.request_full_redraw(1024, 768, true, true).unwrap();
assert_eq!(suppress_output_frames.len(), 2);
assert!(suppress_output_frames.iter().all(|frame| !frame.is_empty()));
assert_eq!(stage.request_full_redraw(1024, 768, true, false).unwrap().len(), 1);
assert!(stage.request_full_redraw(1024, 768, false, false).unwrap().is_empty());
}
#[test]
fn fastpath_input_splits_at_event_limit() {
let mut stage = ActiveStageBuilder {
static_channels: StaticChannelSet::new(),
user_channel_id: 1001,
io_channel_id: 1003,
message_channel_id: None,
share_id: 1,
compression_type: None,
enable_server_pointer: false,
pointer_software_rendering: false,
}
.build();
let mut image = DecodedImage::new(PixelFormat::RgbA32, 1, 1);
let events = (0..=u8::MAX)
.map(|code| FastPathInputEvent::UnicodeKeyboardEvent(KeyboardFlags::empty(), u16::from(code)))
.collect::<Vec<_>>();
let output = stage.process_fastpath_input(&mut image, &events).unwrap();
let input_frames = output
.into_iter()
.map(|output| {
let ActiveStageOutput::ResponseFrame(frame) = output else {
panic!("expected a fast-path input frame");
};
FastPathInput::decode(&mut ReadCursor::new(&frame)).unwrap()
})
.collect::<Vec<_>>();
assert_eq!(
input_frames
.iter()
.map(|input| input.input_events().len())
.collect::<Vec<_>>(),
[FastPathInput::MAX_EVENTS, 1]
);
assert_eq!(
input_frames
.iter()
.flat_map(FastPathInput::input_events)
.collect::<Vec<_>>(),
events.iter().collect::<Vec<_>>()
);
}
#[test]
fn monitor_layout_is_forwarded_from_x224() {
let monitors = vec![Monitor {
left: 0,
top: 0,
right: 799,
bottom: 599,
flags: MonitorFlags::PRIMARY,
}];
let output = ActiveStageOutput::try_from(x224::ProcessorOutput::MonitorLayout(monitors.clone()))
.expect("monitor layout should be forwarded from X.224");
let ActiveStageOutput::MonitorLayout(actual) = output else {
panic!("expected a monitor layout output");
};
assert_eq!(actual, monitors);
}
#[test]
fn slow_path_palette_applies_to_indexed_pointer() {
let mut palette_data = vec![0; 8 + 256 * 3];
palette_data[0..2].copy_from_slice(&0x0002u16.to_le_bytes());
palette_data[4..8].copy_from_slice(&256u32.to_le_bytes());
palette_data[8 + 3..8 + 6].copy_from_slice(&[0x10, 0x20, 0x30]);
let mut processor = fast_path::ProcessorBuilder {
io_channel_id: 0,
user_channel_id: 0,
share_id: 0,
enable_server_pointer: true,
pointer_software_rendering: false,
}
.build();
let mut image = DecodedImage::new(PixelFormat::RgbA32, 1, 1);
let (palette_updates, windowing_orders) =
process_slow_path_graphics(&mut processor, &mut image, None, &palette_data)
.expect("slow-path palette update should succeed");
assert!(palette_updates.is_empty());
assert!(windowing_orders.is_none());
let pointer = PointerAttribute {
xor_bpp: 8,
color_pointer: ColorPointerAttribute {
cache_index: 0,
hot_spot: Point16 { x: 0, y: 0 },
width: 1,
height: 1,
xor_mask: &[1, 0],
and_mask: &[0, 0],
},
};
let pointer_updates = processor
.process_pointer_update(&mut image, PointerUpdateData::New(pointer))
.expect("indexed pointer should decode with slow-path palette");
let [UpdateKind::PointerBitmap(pointer)] = pointer_updates.as_slice() else {
panic!("expected an accelerated pointer bitmap");
};
assert_eq!(pointer.bitmap_data, [0x10, 0x20, 0x30, 0xff]);
}
fn window_order(fields_present: u32) -> Vec<u8> {
let mut order = Vec::new();
order.push(0x2e);
let client_area_size = (fields_present & 0x0001_0000 != 0).then_some([0; 8]);
let order_size: u16 = if client_area_size.is_some() { 19 } else { 11 };
order.extend_from_slice(&order_size.to_le_bytes());
order.extend_from_slice(&fields_present.to_le_bytes());
order.extend_from_slice(&7u32.to_le_bytes());
if let Some(client_area_size) = client_area_size {
order.extend_from_slice(&client_area_size);
}
order
}
fn slow_path_orders_update(order: &[u8]) -> Vec<u8> {
let mut update = Vec::new();
update.extend_from_slice(&0u16.to_le_bytes());
update.extend_from_slice(&0u16.to_le_bytes());
update.extend_from_slice(&1u16.to_le_bytes());
update.extend_from_slice(&0u16.to_le_bytes());
update.extend_from_slice(order);
update
}
#[test]
fn slow_path_windowing_orders_require_negotiated_support() {
let mut processor = fast_path::ProcessorBuilder {
io_channel_id: 0,
user_channel_id: 0,
share_id: 0,
enable_server_pointer: false,
pointer_software_rendering: false,
}
.build();
let mut image = DecodedImage::new(PixelFormat::RgbA32, 1, 1);
let update = slow_path_orders_update(&window_order(0x2100_0000));
let (_, orders) = process_slow_path_graphics(&mut processor, &mut image, None, &update).unwrap();
assert!(orders.is_none());
let (_, orders) =
process_slow_path_graphics(&mut processor, &mut image, Some(WindowSupportLevel::Supported), &update)
.unwrap();
assert_eq!(orders.as_deref(), Some(update.as_slice()));
}
#[test]
fn fast_path_windowing_orders_are_normalized_for_forwarding() {
let order = window_order(0x2100_0000);
let mut update = Vec::new();
update.extend_from_slice(&1u16.to_le_bytes());
update.extend_from_slice(&order);
let normalized = process_fast_path_windowing_orders(Some(WindowSupportLevel::Supported), &update)
.unwrap()
.unwrap();
assert_eq!(
normalized,
[
0, 0, 0, 0, 1, 0, 0, 0, 0x2e, 11, 0, 0, 0, 0, 0x21, 7, 0, 0, 0,
]
);
}
#[test]
fn extended_windowing_orders_require_extended_support() {
let update = slow_path_orders_update(&window_order(0x0101_0000));
let mut processor = fast_path::ProcessorBuilder {
io_channel_id: 0,
user_channel_id: 0,
share_id: 0,
enable_server_pointer: false,
pointer_software_rendering: false,
}
.build();
let mut image = DecodedImage::new(PixelFormat::RgbA32, 1, 1);
assert!(
process_slow_path_graphics(&mut processor, &mut image, Some(WindowSupportLevel::Supported), &update)
.is_err()
);
assert!(
process_slow_path_graphics(
&mut processor,
&mut image,
Some(WindowSupportLevel::SupportedEx),
&update
)
.is_ok()
);
}
}