Skip to content

refactor(server): thread quantization values through the RemoteFX encoder - #1684

Merged
Marc-André Moreau (mamoreau-devolutions) merged 1 commit into
Devolutions:masterfrom
lamco-admin:feat/rfx-encoder-config
Aug 18, 2026
Merged

Marc-André Moreau (mamoreau-devolutions) merged 1 commit into
Devolutions:masterfrom
lamco-admin:feat/rfx-encoder-config

Conversation

@glamberson

@glamberson Greg Lamberson (glamberson) commented Aug 16, 2026 •

Copy link
Copy Markdown
Contributor

RfxEncoder hardcoded Quant::default() at the point of use instead of accepting it as encoder state. Add a quant field to RfxEncoder and a remotefx_quant field to UpdateEncoderCodecs, with a setter that mirrors the existing set_remotefx pattern.

Per [MS-RDPRFX] 2.2.2.1.5, quantization is chosen entirely by the sender and isn't part of capability negotiation, so making it configurable is purely a server-side plumbing problem. This is the first step: give the encoder somewhere to actually hold and use a caller-chosen Quant. #1557 asks for the ability to set it; this PR doesn't add that public entry point yet, it just makes the encoder capable of taking one.

Server behavior is unchanged. The capability negotiation call site still passes Quant::default(), so this is pure plumbing with no functional change.

…oder

RfxEncoder hardcoded Quant::default() at the point of use instead of
accepting it as encoder state. Add a quant field to RfxEncoder and a
remotefx_quant field to UpdateEncoderCodecs, with a setter mirroring
the existing set_remotefx pattern. Server behavior is unchanged since
the capability-negotiation call site still passes Quant::default().

This is prerequisite plumbing for making quantization configurable.
@github-actions github-actions Bot added kind/protocol Affects RDP or related protocol behavior needs-review A human reviewer is the current next actor risk/low Self-contained change with no cross-crate behavioral effect size/S Size: up to 199 counted lines and 5 files; exceeds XS in either measure ai-reviewed/1 One automated review completed labels Aug 16, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Threads sender-selected RemoteFX quantization through the server encoder while preserving current defaults.

Changes:

  • Stores quantization in UpdateEncoderCodecs and RfxEncoder.
  • Passes the configured table through encoder construction.
  • Retains Quant::default() during capability processing.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
crates/ironrdp-server/src/server.rs Supplies default RemoteFX quantization.
crates/ironrdp-server/src/encoder/mod.rs Threads quantization through codec selection.
crates/ironrdp-server/src/encoder/rfx.rs Uses encoder-held quantization values.

/// Quantization values for the RemoteFX encoder. Independent of
/// `remotefx` (entropy coder + codec id, negotiated from the client's
/// capabilities) because this is server-chosen, not client-negotiated:
/// [MS-RDPRFX] 2.2.2.1.5 leaves quantization entirely up to the sender.
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit 1bb08bf into Devolutions:master Aug 18, 2026
59 checks passed
Marc-André Moreau (mamoreau-devolutions) pushed a commit that referenced this pull request Aug 18, 2026
Add Quant::try_new(), a validating constructor that rejects any subband
value outside the 6..=15 range, and
RdpServerBuilder::with_remotefx_quant(quant: Quant), wiring it through
RdpServerOptions to the same capability-negotiation call site #1684
touched.

Per [MS-RDPRFX] 2.2.2.1.5, each of the 10 TS_RFX_CODEC_QUANT values is a
4-bit field, and the legal range is 6 to 15. #1557 reports the quant
table is hardcoded to Quant::default(); this gives callers a validated
way to set it instead.

Builds on #1684, which added the storage this PR wires up. Default
behavior is unchanged: with_remotefx_quant is opt-in, and a server that
doesn't call it still gets Quant::default(), the same values Windows RDP
servers send.

Closes #1557.
Anton Mostovoy (antonmos) added a commit to antonmos/IronRDP that referenced this pull request Aug 18, 2026
Two purely additive conflicts against master's Devolutions#1684/Devolutions#1685
(configurable RemoteFX quantization table, unrelated to preemption):
RdpServerOptions and its builder-state counterpart each gained a new
remotefx_quant: Quant field alongside this branch's own new
preempt_existing_session: bool field -- both fields kept.

Also added the new field to the one hand-built RdpServerOptions literal
this branch has (the preempt_tests test-context helper), which the
auto-merge couldn't do for me since master has no equivalent code to
diff against there.

23/23 tests pass in both default and --features egfx, clippy
--all-targets clean in both, fmt clean, full cargo build --workspace
green.
Marc-André Moreau (mamoreau-devolutions) pushed a commit that referenced this pull request Aug 20, 2026
… last (#1686)

The client's advertised TS_RFX_ICAP array was folded with a plain
assignment in the capability exchange loop, so whichever entry happened
to be parsed last silently decided the entropy coder. Add
pick_remotefx_entropy_coder and
RdpServerBuilder::with_remotefx_entropy_coder so the caller can state a
preferred coder, with a documented fallback instead of an accidental
one.

Per MS-RDPRFX 2.2.1.1.1.1, the TS_RFX_ICAP array is the set of codecs
the client supports, not a ranked preference, so the server is entitled
to pick any entry. With no preference configured, the new fallback is
whichever coder the client offers first, which is deterministic and
matches the array's natural order, instead of whichever happened to be
parsed last. mstsc sends RLGR1 then RLGR3, so today's last-wins behavior
always lands on RLGR3, and RLGR1 never runs against a real client.

Builds on #1684 and #1685, which give the quant table (#1557) the same
server-side shape. Default behavior changes for clients that offer both
coders: the server now uses the first-offered coder instead of the
last-offered one, unless with_remotefx_entropy_coder states otherwise.

Closes #1562.
Anton Mostovoy (antonmos) added a commit to antonmos/IronRDP that referenced this pull request Aug 20, 2026
…, keyboard metadata, entropy coder)

Three upstream commits since the last sync, all in crates/ironrdp-server:

Devolutions#1691 (feat!, keyboard metadata via ConnectionHandler::on_connection_info)
merged with no conflict -- purely additive (a new struct, a new default
trait method, one call site in client_accepted, which this branch never
touches).

Devolutions#1721 (fix, release static channels when a connection ends) restructured
run_connection_with itself, which this branch also restructured (into
PendingConnection-based negotiation) -- git's merge conflicted on run()'s
whole preemption-race loop and, separately, split my mod preempt_tests
and Devolutions#1721's own new mod tests into two overlapping conflict hunks (same
file-tail location, different module names). Resolved by taking this
branch's side for the run() loop (a superset of upstream's simpler
non-preemption tail -- same on_disconnected call, just wrapped in the
preemption if/else) and keeping BOTH test modules side by side. One
splice bug caught by the build: mod preempt_tests was left without its
own closing brace; fixed.

The real interaction worth documenting: Devolutions#1721's fix assumes
run_connection_with is the only connection entry point, but this branch
adds a second one, serve_negotiated (the preemption-winner path), which
never calls run_connection_with. Removing run()'s own static_channels
reset -- which upstream's fix intends, since run_connection_with now
resets it universally -- would have silently reintroduced Devolutions#1721's exact
leak, just scoped to every preemption takeover instead of every
run_connection embedder. Kept run()'s reset, with a comment explaining
why it is NOT redundant despite resetting the same field twice on the
run_connection path.

Devolutions#1685/Devolutions#1684 (RemoteFX quant, already merged in c831bcc) plus this sync's
new Devolutions#1686 (entropy coder weighting) add a second new RdpServerOptions
field, remotefx_entropy_coder: Option<EntropyBits> -- added to the one
hand-built RdpServerOptions literal in preempt_tests, same as
remotefx_quant before it.

Verification, in order, catching problems at each step rather than
trusting a clean `cargo build`: plain build was clean but does not
compile #[cfg(test)] code, so `cargo test` is what actually caught the
missing remotefx_entropy_coder field. 24/24 tests pass in both default
and --features egfx (up from 23 -- upstream's own
run_connection_releases_the_static_channels regression test now runs
against this branch's PendingConnection-based negotiation and passes,
which is strong evidence the merge is semantically correct, not just
textually conflict-free). clippy --all-targets clean in both configs,
fmt clean, full cargo build --workspace green.
@glamberson
Greg Lamberson (glamberson) deleted the feat/rfx-encoder-config branch August 30, 2026 15:12

This branch was previously deployed

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

Labels

ai-reviewed/1 One automated review completed kind/protocol Affects RDP or related protocol behavior needs-review A human reviewer is the current next actor risk/low Self-contained change with no cross-crate behavioral effect size/S Size: up to 199 counted lines and 5 files; exceeds XS in either measure

Development

Successfully merging this pull request may close these issues.

3 participants