Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions benches/src/perfenc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::io::Write as _;
use std::time::Instant;

use anyhow::Context as _;
use ironrdp::pdu::rdp::capability_sets::{CmdFlags, EntropyBits};
use ironrdp::pdu::rdp::capability_sets::{CmdFlags, EntropyBits, LargePointerSupportFlags};
use ironrdp::server::bench::encoder::{UpdateEncoder, UpdateEncoderCodecs};
use ironrdp::server::{BitmapUpdate, DesktopSize, DisplayUpdate, PixelFormat, RdpServerDisplayUpdates};
use tokio::fs::File;
Expand Down Expand Up @@ -59,15 +59,17 @@ async fn main() -> Result<(), anyhow::Error> {
OptCodec::QoiZ => update_codecs.set_qoiz(Some(0)),
};

// u16::MAX: this benchmark replays a fixed update file and isn't exercising the
// client capability gate, so don't let a default of 0 silently start dropping any
// pointer updates the replay file happens to contain.
// u16::MAX / LargePointerSupportFlags::all(): this benchmark replays a fixed update
// file and isn't exercising the client capability gates, so don't let a
// no-capability default silently start dropping any pointer updates the replay
// file happens to contain.
let mut encoder = UpdateEncoder::new(
DesktopSize { width, height },
flags,
update_codecs,
8 * 1024 * 1024,
u16::MAX,
LargePointerSupportFlags::all(),
)
.context("failed to initialize update encoder")?;

Expand Down
32 changes: 30 additions & 2 deletions crates/ironrdp-server/src/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use ironrdp_pdu::rdp::capability_sets::{self, GeneralExtraFlags};
use crate::{DesktopSize, RdpServerOptions};

pub(crate) fn capabilities(opts: &RdpServerOptions, size: DesktopSize) -> Vec<capability_sets::CapabilitySet> {
vec![
let mut caps = vec![
capability_sets::CapabilitySet::General(general_capabilities()),
capability_sets::CapabilitySet::Bitmap(bitmap_capabilities(&size)),
capability_sets::CapabilitySet::Order(order_capabilities()),
Expand All @@ -13,7 +13,13 @@ pub(crate) fn capabilities(opts: &RdpServerOptions, size: DesktopSize) -> Vec<ca
capability_sets::CapabilitySet::VirtualChannel(virtual_channel_capabilities()),
capability_sets::CapabilitySet::MultiFragmentUpdate(multifragment_update(opts)),
capability_sets::CapabilitySet::BitmapCodecs(opts.codecs.clone()),
]
];

if let Some(large_pointer) = large_pointer_capabilities(opts) {
caps.push(capability_sets::CapabilitySet::LargePointer(large_pointer));
}

caps
}

fn general_capabilities() -> capability_sets::General {
Expand Down Expand Up @@ -93,3 +99,25 @@ fn multifragment_update(opts: &RdpServerOptions) -> capability_sets::Multifragme
max_request_size: opts.max_request_size,
}
}

/// MS-RDPBCGR 2.2.7.2.7: the Large Pointer Capability Set's flags MUST be backed by a
/// matching `MaxRequestSize` in the Multifragment Update Capability Set (already
/// advertised above from the same `opts.max_request_size`): at least 38,055 bytes for
/// `LARGE_POINTER_FLAG_96x96`, at least 608,299 bytes for `LARGE_POINTER_FLAG_384x384`
/// (a 96x96 or 384x384 32bpp pointer respectively). Advertise only what the server's
/// configured fragment size can actually deliver; `None` when it can't cover even the
/// smaller tier, so the capability set is omitted entirely rather than advertised empty.
fn large_pointer_capabilities(opts: &RdpServerOptions) -> Option<capability_sets::LargePointer> {
const MIN_REQUEST_SIZE_FOR_96X96: u32 = 38_055;
const MIN_REQUEST_SIZE_FOR_384X384: u32 = 608_299;

let mut flags = capability_sets::LargePointerSupportFlags::empty();
if MIN_REQUEST_SIZE_FOR_96X96 <= opts.max_request_size {
flags |= capability_sets::LargePointerSupportFlags::UP_TO_96X96_PIXELS;
}
if MIN_REQUEST_SIZE_FOR_384X384 <= opts.max_request_size {
flags |= capability_sets::LargePointerSupportFlags::UP_TO_384X384_PIXELS;
}

(!flags.is_empty()).then_some(capability_sets::LargePointer { flags })
}
31 changes: 31 additions & 0 deletions crates/ironrdp-server/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ pub enum DisplayUpdate {
PointerPosition(PointerPositionAttribute),
ColorPointer(ColorPointer),
RGBAPointer(RGBAPointer),
/// A pointer shape wider or taller than the 96x96 ceiling `RGBAPointer` can carry
/// (MS-RDPBCGR 2.2.7.2.7), up to 384x384. Only usable when the client's negotiated
/// Large Pointer Capability Set includes `LARGE_POINTER_FLAG_384x384`; dropped
/// otherwise. See [`RGBAPointer`] for shapes up to 96x96 (32x32 without that
/// capability set at all).
LargePointer(LargePointer),
HidePointer,
DefaultPointer,
CachedPointer(u16),
Expand Down Expand Up @@ -51,6 +57,31 @@ impl core::fmt::Debug for RGBAPointer {
}
}

/// Same shape as [`RGBAPointer`] (32bpp XOR mask, no AND mask), for a pointer image
/// above the 96x96 ceiling `RGBAPointer` can carry, up to 384x384
/// (MS-RDPBCGR 2.2.9.1.2.1.11, Fast-Path Large Pointer Update).
#[derive(Clone)]
pub struct LargePointer {
pub cache_index: u16,
pub width: u16,
pub height: u16,
pub hot_x: u16,
pub hot_y: u16,
pub data: Vec<u8>,
}

impl core::fmt::Debug for LargePointer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("LargePointer")
.field("width", &self.width)
.field("height", &self.height)
.field("hot_x", &self.hot_x)
.field("hot_y", &self.hot_y)
.field("data_len", &self.data.len())
.finish()
}
}

#[derive(Debug, Clone)]
pub struct ColorPointer {
pub cache_index: u16,
Expand Down
93 changes: 90 additions & 3 deletions crates/ironrdp-server/src/encoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ use ironrdp_pdu::encode_vec;
use ironrdp_pdu::fast_path::UpdateCode;
use ironrdp_pdu::geometry::ExclusiveRectangle;
use ironrdp_pdu::pointer::{
CachedPointerAttribute, ColorPointerAttribute, Point16, PointerAttribute, PointerPositionAttribute,
CachedPointerAttribute, ColorPointerAttribute, LargePointerAttribute, Point16, PointerAttribute,
PointerPositionAttribute,
};
use ironrdp_pdu::rdp::capability_sets::{CmdFlags, EntropyBits};
use ironrdp_pdu::rdp::capability_sets::{CmdFlags, EntropyBits, LargePointerSupportFlags};
use ironrdp_pdu::surface_commands::{ExtendedBitmapDataPdu, SurfaceBitsPdu, SurfaceCommand};
use tracing::{debug, warn};

Expand All @@ -19,7 +20,7 @@ use self::rfx::RfxEncoder;
use super::BitmapUpdate;
use crate::error::{ServerError, ServerErrorExt as _, ServerResult, ServerResultExt as _};
use crate::macros::time_warn;
use crate::{ColorPointer, DisplayUpdate, Framebuffer, RGBAPointer};
use crate::{ColorPointer, DisplayUpdate, Framebuffer, LargePointer, RGBAPointer};

mod bitmap;
mod fast_path;
Expand Down Expand Up @@ -132,6 +133,12 @@ pub(crate) struct UpdateEncoder {
/// New Pointer Update; `RGBAPointer`/`CachedPointer` emission is skipped in that
/// case rather than sending a PDU the client did not agree to accept.
pointer_cache_size: u16,
/// Client's advertised Large Pointer Capability Set flags (MS-RDPBCGR 2.2.7.2.7).
/// Empty when the client didn't send this capability set at all. Governs both the
/// pointer size ceiling for `RGBAPointer` (32x32 with no flags, 96x96 with
/// `UP_TO_96X96_PIXELS`) and whether the dedicated `LargePointer` update, up to
/// 384x384, is usable at all (`UP_TO_384X384_PIXELS`).
large_pointer_flags: LargePointerSupportFlags,
}

impl fmt::Debug for UpdateEncoder {
Expand All @@ -150,6 +157,7 @@ impl UpdateEncoder {
codecs: UpdateEncoderCodecs,
max_request_size: u32,
pointer_cache_size: u16,
large_pointer_flags: LargePointerSupportFlags,
) -> ServerResult<Self> {
let bitmap_updater = if surface_flags.contains(CmdFlags::SET_SURFACE_BITS) {
match codecs {
Expand Down Expand Up @@ -186,9 +194,37 @@ impl UpdateEncoder {
max_request_size: usize::try_from(max_request_size)
.map_err(|e| ServerError::custom("max_request_size", e))?,
pointer_cache_size,
large_pointer_flags,
})
}

/// Whether a `width`x`height` pointer shape fits the Color/New Pointer Update
/// ceiling: 96x96 if the client's Large Pointer Capability Set includes
/// `UP_TO_96X96_PIXELS` (MS-RDPBCGR 2.2.9.1.1.4.4's width/height field docs; New
/// Pointer Update carries the same `ColorPointerAttribute` internally, so the same
/// ceiling applies to both), 32x32 otherwise. Shapes above this ceiling need the
/// dedicated Large Pointer Update instead (see `large_pointer_update_supported`).
fn color_or_new_pointer_size_ok(&self, width: u16, height: u16) -> bool {
let max = if self
.large_pointer_flags
.contains(LargePointerSupportFlags::UP_TO_96X96_PIXELS)
{
96
} else {
32
};
width <= max && height <= max
}

/// Whether the client negotiated the dedicated Fast-Path Large Pointer Update
/// itself. Per MS-RDPBCGR 2.2.7.2.7, only `UP_TO_384X384_PIXELS` unlocks this PDU;
/// `UP_TO_96X96_PIXELS` alone only raises the Color/New Pointer Update ceiling and
/// does not enable it.
fn large_pointer_update_supported(&self) -> bool {
self.large_pointer_flags
.contains(LargePointerSupportFlags::UP_TO_384X384_PIXELS)
}

#[cfg_attr(feature = "__bench", visibility::make(pub))]
pub(crate) fn update(&mut self, update: DisplayUpdate) -> EncoderIter<'_> {
EncoderIter {
Expand Down Expand Up @@ -230,6 +266,28 @@ impl UpdateEncoder {
))
}

fn large_pointer(ptr: LargePointer) -> ServerResult<UpdateFragmenter> {
let xor_mask = ptr.data;

let hot_spot = Point16 {
x: ptr.hot_x,
y: ptr.hot_y,
};
let large_pointer = LargePointerAttribute {
xor_bpp: 32,
cache_index: ptr.cache_index,
hot_spot,
width: ptr.width,
height: ptr.height,
xor_mask: &xor_mask,
and_mask: &[],
};
Ok(UpdateFragmenter::new(
UpdateCode::LargePointer,
encode_vec(&large_pointer).map_err(ServerError::encode)?,
))
}

fn color_pointer(ptr: ColorPointer) -> ServerResult<UpdateFragmenter> {
let hot_spot = Point16 {
x: ptr.hot_x,
Expand Down Expand Up @@ -430,8 +488,37 @@ impl EncoderIter<'_> {
debug!("Dropping RGBAPointer update: client did not advertise New Pointer Update support");
continue;
}
if !encoder.color_or_new_pointer_size_ok(ptr.width, ptr.height) {
debug!(
"Dropping RGBAPointer update: {}x{} exceeds the client's negotiated pointer size \
ceiling",
ptr.width, ptr.height,
);
continue;
}
UpdateEncoder::rgba_pointer(ptr)
}
DisplayUpdate::LargePointer(ptr) => {
if encoder.pointer_cache_size == 0 {
debug!("Dropping LargePointer update: client did not advertise New Pointer Update support");
continue;
}
if !encoder.large_pointer_update_supported() {
debug!(
"Dropping LargePointer update: client did not advertise \
LARGE_POINTER_FLAG_384x384 support"
);
continue;
}
if 384 < ptr.width || 384 < ptr.height {
debug!(
"Dropping LargePointer update: {}x{} exceeds the 384x384 protocol maximum",
ptr.width, ptr.height,
);
continue;
}
UpdateEncoder::large_pointer(ptr)
}
DisplayUpdate::ColorPointer(ptr) => UpdateEncoder::color_pointer(ptr),
DisplayUpdate::HidePointer => UpdateEncoder::hide_pointer(),
DisplayUpdate::DefaultPointer => UpdateEncoder::default_pointer(),
Expand Down
4 changes: 2 additions & 2 deletions crates/ironrdp-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ mod urbdrc;

pub use clipboard::CliprdrServerFactory;
pub use display::{
BitmapUpdate, ColorPointer, DesktopSize, DisplayUpdate, Framebuffer, PixelFormat, RGBAPointer, RdpServerDisplay,
RdpServerDisplayUpdates,
BitmapUpdate, ColorPointer, DesktopSize, DisplayUpdate, Framebuffer, LargePointer, PixelFormat, RGBAPointer,
RdpServerDisplay, RdpServerDisplayUpdates,
};
pub use echo::{EchoDvcBridge, EchoRoundTripMeasurement, EchoServerHandle, EchoServerMessage};
pub use error::{ServerError, ServerErrorExt, ServerErrorKind, ServerResult, ServerResultExt};
Expand Down
15 changes: 14 additions & 1 deletion crates/ironrdp-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use ironrdp_pdu::input::InputEventPdu;
use ironrdp_pdu::input::fast_path::{FastPathInput, FastPathInputEvent};
use ironrdp_pdu::mcs::{SendDataIndication, SendDataRequest};
use ironrdp_pdu::rdp::capability_sets::{
BitmapCodecs, CapabilitySet, CmdFlags, CodecProperty, EntropyBits, GeneralExtraFlags,
BitmapCodecs, CapabilitySet, CmdFlags, CodecProperty, EntropyBits, GeneralExtraFlags, LargePointerSupportFlags,
};
pub use ironrdp_pdu::rdp::client_info::Credentials;
use ironrdp_pdu::rdp::headers::{ServerDeactivateAll, ShareControlPdu};
Expand Down Expand Up @@ -2430,6 +2430,10 @@ impl RdpServer {
let mut update_codecs = UpdateEncoderCodecs::new();
let mut surface_flags = CmdFlags::empty();
let mut pointer_cache_size: u16 = 0;
// Absence means the client did not send a Large Pointer Capability Set at all,
// which per MS-RDPBCGR 2.2.7.2.7 leaves the pointer size ceiling at 32x32 (the
// base Color/New Pointer Update limit with no large-pointer flags set).
let mut large_pointer_flags = LargePointerSupportFlags::empty();
for c in result.capabilities {
match c {
CapabilitySet::General(c) => {
Expand Down Expand Up @@ -2523,6 +2527,14 @@ impl RdpServer {
// else in this crate populates that cache via the Color Pointer Update.
pointer_cache_size = p.pointer_cache_size;
}
CapabilitySet::LargePointer(lp) => {
// MS-RDPBCGR 2.2.7.2.7: LARGE_POINTER_FLAG_96x96 raises the Color/New
// Pointer Update ceiling from 32x32 to 96x96; LARGE_POINTER_FLAG_384x384
// additionally unlocks the dedicated Fast-Path Large Pointer Update, up to
// 384x384. `UpdateEncoder` uses these flags to decide which pointer
// updates it can send at all, and at what size.
large_pointer_flags = lp.flags;
}
_ => {}
}
}
Expand All @@ -2534,6 +2546,7 @@ impl RdpServer {
update_codecs,
self.opts.max_request_size,
pointer_cache_size,
large_pointer_flags,
)?;

self.send_next_auto_reconnect_cookie(writer, result.io_channel_id, result.user_channel_id)
Expand Down
Loading