Skip to content

feat(server)!: support the Large Pointer Capability Set - #1787

Merged
Marc-André Moreau (mamoreau-devolutions) merged 1 commit into
Devolutions:masterfrom
lamco-admin:feat/large-pointer-capability-set
Aug 28, 2026
Merged

Marc-André Moreau (mamoreau-devolutions) merged 1 commit into
Devolutions:masterfrom
lamco-admin:feat/large-pointer-capability-set

Conversation

@glamberson

Copy link
Copy Markdown
Contributor

Depends on #1786

Stacked on top of #1786's branch: Both touch client_accepted()'s capabilities loop
and UpdateEncoder::new()'s constructor signature. #1786 must land first.

Summary

  • ironrdp-server never advertises or reads the Large Pointer Capability Set
    (MS-RDPBCGR 2.2.7.2.7), even though ironrdp-pdu already defines everything needed
    (LargePointer/LargePointerSupportFlags, LargePointerAttribute) and the client
    side already decodes and renders UpdateCode::LargePointer. The server side was the
    only gap: A pointer shape above 32x32 (or 96x96 with just the base Pointer
    Capability Set) could never be sent.
  • Advertises the capability derived from ironrdp-server's own configured
    max_request_size, not asserted independently: MS-RDPBCGR 2.2.7.2.7 requires the
    Multifragment Update Capability Set's MaxRequestSize to be at least 38,055 bytes
    for LARGE_POINTER_FLAG_96x96 and at least 608,299 bytes for
    LARGE_POINTER_FLAG_384x384. The default max_request_size (8 MiB) clears both, so
    this is enabled by default unless a server has lowered it.
  • Reads the client's negotiated flags back and enforces a distinction the spec draws
    explicitly: LARGE_POINTER_FLAG_96x96 only raises the existing Color/New Pointer
    Update's size ceiling from 32x32 to 96x96 (New Pointer Update wraps the same
    ColorPointerAttribute as Color Pointer Update, so MS-RDPBCGR 2.2.9.1.1.4.4's
    width/height field docs apply to both); it does not enable the separate Large
    Pointer Update PDU. Only LARGE_POINTER_FLAG_384x384 does that.
    UpdateEncoder now enforces both halves: RGBAPointer is dropped (debug log) if it
    exceeds the negotiated 32x32/96x96 ceiling, and the new
    DisplayUpdate::LargePointer is dropped if the client never advertised
    LARGE_POINTER_FLAG_384x384, or if the shape exceeds the protocol's absolute
    384x384 maximum regardless of what was advertised.
  • DisplayUpdate::LargePointer mirrors RGBAPointer's shape exactly (32bpp XOR mask
    only, no AND mask) since that's the only color depth this crate's pointer path
    produces. ColorPointer's own ceiling (same LargePointerSupportFlags) is left
    unenforced for the same reason the prior PR left its separate availability gate
    (colorPointerCacheSize) ungated: Nothing in this crate constructs a ColorPointer
    update.

Breaking change

DisplayUpdate gains a new variant (LargePointer), and UpdateEncoder's (crate
internal, #[cfg(feature = "__bench")]-exposed for benchmarks) constructor gains a
6th parameter. No exhaustive match over DisplayUpdate exists elsewhere in the
workspace today, so nothing else needed updating.

Validation

cargo xtask check fmt/typos/locks/lints/tests all pass.

@github-actions github-actions Bot added needs-review A human reviewer is the current next actor risk/unknown Risk could not be determined automatically; needs maintainer-level scrutiny size/M Size: up to 449 counted lines and 10 files; exceeds S in either measure labels Aug 23, 2026
@glamberson
Greg Lamberson (glamberson) force-pushed the feat/large-pointer-capability-set branch from 4dd2c66 to 4b3174d Compare August 26, 2026 22:10
@glamberson
Greg Lamberson (glamberson) force-pushed the feat/large-pointer-capability-set branch from 4b3174d to b234e2d Compare August 27, 2026 22:27
Without this, ironrdp-server can never send a pointer shape above 32x32
(or 96x96 with the base Pointer Capability Set alone): The Large Pointer
Capability Set (MS-RDPBCGR 2.2.7.2.7) is never advertised by the server
and never read from the client, even though the wire layer already has
everything needed to use it. ironrdp-pdu already defines LargePointer /
LargePointerSupportFlags (the capability set) and LargePointerAttribute
(TS_FP_LARGEPOINTERATTRIBUTE, the Fast-Path Large Pointer Update PDU
itself, up to 384x384), and the client side already decodes and renders
it (ironrdp-session's fast_path dispatch, ironrdp-graphics's pointer
decode, and a fuzz oracle all already exercise UpdateCode::LargePointer).
Only the server side was missing.

Advertises the capability from ironrdp-server's own configured
max_request_size rather than unconditionally claiming full 384x384
support: MS-RDPBCGR 2.2.7.2.7 requires the MaxRequestSize field of the
Multifragment Update Capability Set to be at least 38,055 bytes for
LARGE_POINTER_FLAG_96x96 and at least 608,299 bytes for
LARGE_POINTER_FLAG_384x384 (enough to fit a 96x96 or 384x384 32bpp
pointer respectively), so what gets advertised is derived from the same
max_request_size the Multifragment Update Capability Set already uses,
not asserted independently of it. The default max_request_size (8 MiB)
clears both thresholds, so this is enabled by default for servers that
haven't lowered it.

Reading the client's negotiated flags surfaces a distinction the spec
draws explicitly and that's easy to miss: LARGE_POINTER_FLAG_96x96 only
raises the size ceiling of the existing Color/New Pointer Update from
32x32 to 96x96 (New Pointer Update carries the same ColorPointerAttribute
internally as Color Pointer Update, so the same width/height field docs
in MS-RDPBCGR 2.2.9.1.1.4.4 apply to both); it does not by itself enable
the separate Large Pointer Update PDU. Only LARGE_POINTER_FLAG_384x384
does that. UpdateEncoder now enforces both halves of this: RGBAPointer is
dropped (debug log, not an error) if its shape exceeds the negotiated
32x32/96x96 ceiling, and the new DisplayUpdate::LargePointer is dropped
if the client never advertised LARGE_POINTER_FLAG_384x384 or if the
shape exceeds the protocol's absolute 384x384 maximum regardless of what
was advertised.

DisplayUpdate::LargePointer mirrors RGBAPointer's shape exactly (32bpp
XOR mask only, no AND mask, same field names) since that's the only
color depth this crate's pointer path produces. ColorPointer's own
32x32/96x96 ceiling, governed by the same LargePointerSupportFlags as
RGBAPointer's, is left unenforced for the same reason the prior PR left
ColorPointer's availability gate (colorPointerCacheSize, a separate
concern from the size ceiling) ungated: Nothing in this crate constructs
a ColorPointer update.

Stacked on Devolutions#1786 (both touch the same client_accepted() capabilities
loop and UpdateEncoder construction); Devolutions#1786 must land first.
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit bde5824 into Devolutions:master Aug 28, 2026
43 checks passed
Anton Mostovoy (antonmos) added a commit to antonmos/IronRDP that referenced this pull request Aug 30, 2026
…arge pointer capset)

Three upstream commits since the last sync, all in client_accepted's
capability loop or client_loop's body -- code this branch has never
touched (the preemption/negotiation work stays entirely within
PendingConnection/negotiate_and_authenticate and the accept-loop race,
upstream of accept_finalize where client_accepted/client_loop live):

Devolutions#1787! (Large Pointer Capability Set) -- additive to the capabilities
match in client_accepted and UpdateEncoder::new's constructor, neither
touched here.
Devolutions#1834 (EGFX flow-control + dispatch timing diagnostics) -- logging only
inside client_loop's existing futures, no signature changes.
Devolutions#1842 (periodic Heartbeat PDUs) -- adds a field + a config setter to
RdpServer and a 5th joined future inside client_loop; client_loop's own
signature gained a parameter, but its only two call sites (both inside
client_accepted) are upstream's own code, already updated by the merge.

One trivial conflict: both branches touched the same two import lines
at the top of the file (this branch added IpAddr for the anti-storm
cooldown's EvictedPeer.ip; upstream added AtomicU64 for the heartbeat
write-counter) -- kept both.

Note from Devolutions#1834's own commit message, checked rather than trusted: it
warns the workspace-wide test build was already broken on master
independent of that PR (an ironrdp-daemon call-site mismatch). Did not
reproduce here -- full cargo build --workspace succeeded clean, so
either it was already fixed by a later commit or doesn't affect a
non-test build; not chased further since it doesn't block this merge.

14/14 tests pass in both default and --features egfx (test count
unchanged -- none of the three commits touch a test module), clippy
--all-targets clean in both configs AND under CI's exact stricter
command (--features helper,__bench --locked -- -D warnings), fmt clean,
full cargo build --workspace green.
@glamberson
Greg Lamberson (glamberson) deleted the feat/large-pointer-capability-set branch August 30, 2026 15:12

This branch was previously deployed

1 inactive deployment
llm-providers — 219c5c9c Deployed Aug 28, 2026 by glamberson via Classify pull request #4160
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review A human reviewer is the current next actor risk/unknown Risk could not be determined automatically; needs maintainer-level scrutiny size/M Size: up to 449 counted lines and 10 files; exceeds S in either measure

Development

Successfully merging this pull request may close these issues.

2 participants