Skip to content

feat(client): expose generic session configuration and lifecycle APIs - #1522

Merged
Marc-André Moreau (mamoreau-devolutions) merged 8 commits into
masterfrom
copilot/client-lifecycle-apis
Aug 2, 2026
Merged

feat(client): expose generic session configuration and lifecycle APIs#1522
Marc-André Moreau (mamoreau-devolutions) merged 8 commits into
masterfrom
copilot/client-lifecycle-apis

Conversation

@mamoreau-devolutions

@mamoreau-devolutions Marc-André Moreau (mamoreau-devolutions) commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • expose generic client configuration for connection metadata, compression, shell/work directory, audio, and runtime static-channel factories
  • add bounded input delivery with independent close cancellation, host clipboard plumbing, lifecycle events, and Display Control resize readiness/fallback handling
  • update agent, viewer, web, FFI, examples, and tests for the generic APIs

Stack dependencies

This PR is stacked on copilot/tls-validation-policy (b2bbcece), which already includes the merged runtime static-channel support from master. It intentionally contains no TLS implementation/policy, ActiveX/COM, SVC implementation, decompression, or bitmap-recovery changes.

Validation

  • cargo fmt --check --all
  • cargo xtask check tests --no-run -v
  • cargo xtask check lints -v
  • cargo test -p ironrdp-client --lib --features rustls
  • cargo check -p ironrdp-agent -p ironrdp-viewer -p ironrdp-web -p ffi

@github-actions github-actions Bot added A-native-client Area: Native client (ironrdp-client) A-web-client Area: Web client (ironrdp-web, iron-remote-gui, iron-svelte-client) A-core Area: Core tier A-extra Area: Extra tier A-internal Area: Internal tier A-virtual-channel Area: Static or dynamic virtual channel A-server Area: RDP server rust Pull requests that update Rust code A-agent Area: Agent size/XL Size: Above 800 lines of code and removed size/XL Size: Above 800 lines of code labels Aug 2, 2026
@github-actions github-actions Bot added size/XL Size: Above 800 lines of code dependencies Pull requests that update a dependency file and removed size/XL Size: Above 800 lines of code labels Aug 2, 2026
Base automatically changed from copilot/tls-validation-policy to master August 2, 2026 22:14
Copilot AI review requested due to automatic review settings August 2, 2026 22:14
@github-actions github-actions Bot added A-tooling Area: Project tooling size/XL Size: Above 800 lines of code and removed size/XL Size: Above 800 lines of code labels Aug 2, 2026
@github-actions github-actions Bot added size/XL Size: Above 800 lines of code and removed size/XL Size: Above 800 lines of code labels Aug 2, 2026
@github-actions github-actions Bot added size/XL Size: Above 800 lines of code and removed A-tooling Area: Project tooling size/XL Size: Above 800 lines of code labels Aug 2, 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

Exposes generic RDP client configuration, bounded input delivery, lifecycle events, clipboard integration, and Display Control resize fallback behavior across consumers.

Changes:

  • Adds generic configuration and runtime channel APIs.
  • Adds cancellable bounded input, lifecycle notifications, and resize fallback handling.
  • Includes TLS validation changes despite the stated stack boundary.

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
xtask/src/check.rs Adds native-TLS test execution.
testing/agentic-rdp/Connect-AgentSession.ps1 Configures test certificate policy and readiness checks.
ffi/src/session/mod.rs Exposes session-info output.
ffi/src/connector/config.rs Supplies the connection profile.
crates/ironrdp/examples/screenshot.rs Updates example configuration.
crates/ironrdp-web/src/session.rs Handles lifecycle output and connection profile.
crates/ironrdp-viewer/src/clipboard.rs Uses bounded clipboard input.
crates/ironrdp-viewer/src/app.rs Adopts bounded input and lifecycle events.
crates/ironrdp-tls/tests/native_tls.rs Tests native-TLS validation policies.
crates/ironrdp-tls/tests/certs/server-key.pem Adds the TLS test key.
crates/ironrdp-tls/tests/certs/server-cert.pem Adds the TLS test certificate.
crates/ironrdp-tls/src/stub.rs Adds validation-policy stubs.
crates/ironrdp-tls/src/rustls.rs Implements Rustls validation policies.
crates/ironrdp-tls/src/native_tls.rs Implements native-TLS validation policies.
crates/ironrdp-tls/src/lib.rs Exposes certificate-validation APIs.
crates/ironrdp-tls/README.md Documents validation behavior.
crates/ironrdp-tls/Cargo.toml Adds roots and test dependencies.
crates/ironrdp-testsuite-extra/tests/e2e.rs Adds TLS and configuration coverage.
crates/ironrdp-testsuite-extra/tests/client_config.rs Tests generic builder options.
crates/ironrdp-testsuite-core/tests/session/connection_activation.rs Updates test configuration.
crates/ironrdp-testsuite-core/tests/connector/autodetect.rs Updates test configuration.
crates/ironrdp-session/src/x224/mod.rs Surfaces session-info notifications.
crates/ironrdp-session/src/active_stage.rs Adds display readiness and lifecycle output.
crates/ironrdp-dvc/src/client.rs Adds DVC registration inspection.
crates/ironrdp-connector/src/lib.rs Adds configurable connection type.
crates/ironrdp-connector/src/connection.rs Advertises the selected connection type.
crates/ironrdp-client/src/rdp.rs Implements bounded input, lifecycle, and resize handling.
crates/ironrdp-client/src/config.rs Expands generic client configuration.
crates/ironrdp-client/src/clipboard.rs Adopts bounded clipboard delivery.
crates/ironrdp-client/README.md Documents TLS configuration.
crates/ironrdp-client/Cargo.toml Marks TLS as a public dependency.
crates/ironrdp-agent/src/daemon.rs Adopts generic configuration and lifecycle APIs.
crates/ironrdp-agent/src/cli.rs Documents certificate policy configuration.
crates/ironrdp-agent/Cargo.toml Adds the TLS dependency.
Cargo.lock Updates dependency metadata.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/ironrdp-client/src/rdp.rs Outdated
Comment thread crates/ironrdp-viewer/src/app.rs
Comment thread crates/ironrdp-client/src/rdp.rs Outdated
Comment thread crates/ironrdp-client/src/config.rs Outdated
Comment thread crates/ironrdp-dvc/src/client.rs
@github-actions github-actions Bot added size/XL Size: Above 800 lines of code and removed size/XL Size: Above 800 lines of code labels Aug 2, 2026
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added size/XL Size: Above 800 lines of code and removed size/XL Size: Above 800 lines of code labels Aug 2, 2026
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit 57b1366 into master Aug 2, 2026
27 checks passed
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) deleted the copilot/client-lifecycle-apis branch August 2, 2026 23:36
Marc-André Moreau (mamoreau-devolutions) pushed a commit that referenced this pull request Aug 3, 2026
…uto-reconnect cookie (#1501)

> **Rebased onto post-#1522 master.** #1509 landed the server half of
#1508 while this was open, including the `ClientAutoReconnect`
structure. This PR no longer declares it; it extends it, and picks up
the parts #1509 did not build.

## What

The client half of automatic reconnection. The session layer surfaces
the Server Auto-Reconnect Cookie, `ironrdp-pdu` derives and verifies the
client's response to it, and the connector sends that response when
resuming a session.

## Why

A client whose connection drops ungracefully can reattach to its session
instead of making the user log on again, provided it returns the cookie
the server issued during logon ([MS-RDPBCGR] 1.3.1.5).

#1509 built the server side of that: it validates a returning
`ARC_CS_PRIVATE_PACKET` and rotates the random. Nothing answers it.
`ironrdp-session` decodes the cookie and drops it, `ironrdp-connector`
has no way to send one back, and `TODO(#271)` still sits in
`ironrdp-client`. So `ironrdp-client` cannot resume a session against
`ironrdp-server`, and the validation #1509 added has no in-tree
counterpart to exercise it.

The wire encoding was already there. `ExtendedClientOptionalInfo`
carries, encodes and decodes a 28-byte `autoReconnectCookie` and its
builder already had a `reconnect_cookie` step; `ServerAutoReconnect`
already decoded; #1509 added `ClientAutoReconnect` and its decode.
Nothing connected them.

## The three parts

**Receive.** `SaveSessionInfo` now also surfaces the cookie, as
`ProcessorOutput::AutoReconnectCookie` and
`ActiveStageOutput::AutoReconnectCookie`. #1522 added a `SaveSessionInfo
{ logon_complete }` output on that same handler; the two coexist rather
than compete, since both are read off one PDU and neither supersedes the
other. The handler emits the logon notification unconditionally and
appends the cookie when one is present, and a test pins that surfacing
the cookie does not suppress the notification. #1509's server replaces
the cookie whenever a client connects and again hourly ([MS-RDPBCGR]
3.3.6.2), so this can arrive more than once in a session and the
consumer keeps the most recent.

**Derive.** `ClientAutoReconnect::from_server_cookie` implements
[MS-RDPBCGR] 5.5:

> The auto-reconnect random is used to key the HMAC function
([RFC2104]), which uses MD5 as the iterative hash function. The security
verifier is derived by applying the HMAC to the client random received
in Step 3.
>
> `SecurityVerifier = HMAC(AutoReconnectRandom, ClientRandom)`
>
> When Enhanced RDP Security is in effect the client random value is not
generated (section 5.3.2). In this case, for the purpose of generating
the security verifier, the client random is assumed to be an array of 32
zero bytes.

IronRDP implements no Standard RDP Security path (there is no Security
Exchange PDU), so the zero-client-random case is the only one that
arises. As 5.5 notes, that makes the verifier constant for a given
cookie, so it proves possession of the cookie and nothing more; session
security comes from the outer TLS/CredSSP handshake.

@clintcan independently confirmed this construction against real
**mstsc** while validating #1509
([comment](#1509 (comment))):
a Windows client's `ARC_CS_PRIVATE_PACKET` verifies against
`HMAC-MD5(random_bits, [0u8; 32])`. That is the same derivation
implemented here, so the two halves interoperate with Microsoft's client
and not only with each other.

**Send.** `ClientConnector::with_auto_reconnect_cookie` takes the cookie
last received and makes the connector put the derived Client
Auto-Reconnect Packet ([MS-RDPBCGR] 2.2.4.3) in the Client Info PDU.
Absent, that PDU is byte-for-byte what it was.

Unlike the server packet, this structure has no enclosing logon-info
field header, so it encodes to exactly the 28 bytes the cookie field
expects. `to_bytes` writes that layout directly rather than going
through `Encode`, so filling a fixed-size field has no error path a
caller must handle; a test pins the two to agree.

## One derivation, not two

Putting `from_server_cookie` in `ironrdp-pdu` would leave the workspace
with two implementations of 5.5, since #1509 added a private HMAC to
`ironrdp-server`. So `ClientAutoReconnect` also gains `verify`, and the
server routes through it.

`verify` keeps the constant-time comparison the server had. The verifier
is the whole credential, so a comparison returning early on the first
differing byte would let a peer recover it a byte at a time from the
timing; the session identifier is not secret and is compared normally.
`ironrdp-server` keeps the policy around the check, which cookies are
live and whether the security protocol permits auto-reconnect, and drops
its `hmac` and `md-5` dependencies. `hmac` moves to `ironrdp-pdu` as
`default-features = false`; the crate's full feature powerset still
checks clean, including `--no-default-features`.

I would rather not have reached into `ironrdp-server` in a
`pdu,session,connector` change, but the alternative was shipping the
duplicate and filing a follow-up to remove it, which is a worse trade
for reviewer time.

## Tests that were not running

That move also rehomes the known-answer tests @clintcan contributed on
#1509. They went in as an inline `#[cfg(test)]` module in
`crates/ironrdp-server/src/server.rs`, and that crate sets `[lib] test =
false`, so they have never executed in CI. They now live in
`ironrdp-testsuite-core` against the public API, where CI runs them: his
HMAC-MD5 reference vector is kept as a second vector alongside a
differently-keyed one, plus the cases for a tampered verifier and a
mismatched logon ID.

Worth flagging separately: `ironrdp-server` is not alone.
`ironrdp-agent`, `ironrdp-session` and `ironrdp-web` also set `[lib]
test = false` and between them carry 16 files of inline `#[cfg(test)]`
modules that CI never runs. That is out of scope here, but I am happy to
open an issue if it would be useful.

## Breaking changes

`ActiveStageOutput` and `x224::ProcessorOutput` gain a variant, and
`ClientConnector` gains a public field, so exhaustive matches and struct
literals need updating.

Confirmed with `cargo-semver-checks` against the merge-base: those three
are the only findings this branch introduces. The others it reports on
`master` today (`ShareDataPdu::Compressed` and the `ShareDataCtx` fields
from #1518, `ProcessorBuilder.bulk_decompressor` from #1518,
`ServerEvent::SetAutoReconnectCookie` from #1509) are present on
`master` unchanged. The `ironrdp-pdu` additions are additive.

## Scope

This is the library half. `ironrdp-client`, `ironrdp-web` and the FFI
bindings gain an arm for the new output but none of them reconnect
automatically yet; that is the remaining part of #271, and the existing
`TODO(#271)` in `ironrdp-client` marks where it goes.

I kept receive, derive and send together deliberately. Split up, none of
them is usable on its own: without the receive half there is no way to
obtain a cookie, and without the send half there is nothing to do with
one.

## Tests

Thirteen, all in `ironrdp-testsuite-core`.

On the packet and the derivation: the `SecurityVerifier` matches two
independently computed HMAC-MD5 vectors of 32 zero bytes under different
keys, so the tests pin the derivation rather than restating the code;
the logon ID carries over from the server cookie; the encoding matches
the 2.2.4.3 field layout byte for byte with `cbLen` fixed at `0x1C`;
`to_bytes` agrees with `Encode`; it round-trips; and it rejects both a
wrong packet length and an unknown version.

On verification: a derived answer is accepted, a single flipped byte in
the verifier is rejected, a correct verifier under a different logon ID
is rejected, and an answer derived from a different random is rejected.

On the surfacing path: a Save Session Info PDU framed the way a server
sends it, through the real x224 processor, yields an
`AutoReconnectCookie` carrying the right logon ID and random bits,
alongside #1522's logon notification rather than in place of it; and one
without a cookie surfaces no cookie.

## Verification

`cargo xtask check fmt/lints/tests/typos/locks` all pass on 1.94.1,
including a `fuzz/` build before the lock check.

## Note

#1496 also touches the `ClientAutoReconnect` declaration. Whichever of
the two lands second needs a one-line rebase on the derive attribute;
happy to take that in either order.

[MS-RDPBCGR]:
https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/e729948a-3f4e-4568-9aef-d355e30b5389
[RFC2104]: https://www.rfc-editor.org/rfc/rfc2104
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-agent Area: Agent A-core Area: Core tier A-extra Area: Extra tier A-internal Area: Internal tier A-native-client Area: Native client (ironrdp-client) A-server Area: RDP server A-virtual-channel Area: Static or dynamic virtual channel A-web-client Area: Web client (ironrdp-web, iron-remote-gui, iron-svelte-client) dependencies Pull requests that update a dependency file rust Pull requests that update Rust code size/XL Size: Above 800 lines of code

Development

Successfully merging this pull request may close these issues.

2 participants