Skip to content

feat(server): add ironrdp-server integration for AUDIO_INPUT - #1946

Merged
Marc-André Moreau (mamoreau-devolutions) merged 2 commits into
Devolutions:masterfrom
lamco-admin:feat/rdpeai-server-wiring
Sep 22, 2026
Merged

Marc-André Moreau (mamoreau-devolutions) merged 2 commits into
Devolutions:masterfrom
lamco-admin:feat/rdpeai-server-wiring

Conversation

@glamberson

@glamberson Greg Lamberson (glamberson) commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

RdpeaiServerFactory (ironrdp-server/src/rdpeai.rs) is a thin factory trait mirroring RdpdrServerFactory and SoundServerFactory: build_backend() returns the RdpeaiServerBackend the embedding application supplies. attach_channels registers RdpeaiServer on the DVC stack alongside AInput, DisplayControl, Echo, and RdpeiServer when a factory is configured. RdpServerBuilder::with_rdpeai_factory() follows with_rdpdr_factory's shape.

ServerEvent::Rdpeai(RdpeaiServerMessage) lets the embedding application trigger open() and change_format() from outside the DVC message loop, since RdpeaiServer is nested inside DrdynvcServer rather than being its own top-level channel. The dispatch arm reaches it via DrdynvcServer::dvc_by_id_mut, the same path Echo's dispatch uses to reach EchoDvcBridge, then wraps the resulting PDUs with dvc::encode_dvc_messages before writing them.

rdpei and rdpeai are legitimately distinct protocols (touch and pen input versus audio input) whose names happen to be textually close; the constructor's clippy::similar_names is expected and annotated rather than worked around by renaming either.

Review round

Six findings addressed in a follow-up commit, plus two additional stale duplicates of #1945's already-merged fix closed with an explanation: the dispatch arm's open()/change_format() state rejections now drop-and-warn instead of tearing down the connection, handle_open_reply accepts a client's Open-rejection reply even when it skips the FormatChange confirm (matches ironrdp-rdpeai's own client behavior and MS-RDPEAI 3.3.5.1.8), Open Reply success now checks the HRESULT sign bit instead of equality with S_OK, server-initiated AUDIO_INPUT DVC data uses ChannelFlags::SHOW_PROTOCOL matching every sibling arm, and a redundant channel-opened guard was removed. Two regression tests added.

@github-actions github-actions Bot added breaking-change Includes a breaking change, and requires special scrutiny at the boundaries triage/overlap Possible overlap with another pull request; advisory only kind/protocol Affects RDP or related protocol behavior needs-review A human reviewer is the current next actor risk/medium Behavioral change that does not substantially alter a core public API size/XL Size: up to 1299 counted lines and 49 files; exceeds L in either measure labels Sep 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Potential duplicate detected: #1945.

Candidate #1945 describes the same RdpeaiServer orchestration: identical scope, spec citations (MS-RDPEAI 3.1.5 ignore rule, 3.3.5.3.1 AAC/version-1 SHOULD NOT), the RdpdrServer #1783 precedent, and 14 tests in ironrdp-testsuite-core — all matching this diff's content and phrasing, though head SHAs differ (rebase or re-submission of the same change).

Maintainer review is required.

@github-actions github-actions Bot 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.

Verified the AUDIO_INPUT server state machine and ironrdp-server wiring against the PR head. The MS-RDPEAI sequencing, the AAC/v1 FormatChange SHOULD-NOT guard, and client format filtering are sound, and the factory/builder/ServerEvent plumbing mirrors existing rdpdr/rdpsnd/rdpei conventions. Eight candidate findings were independently confirmed: malformed AUDIO_INPUT packets and anticipated open()/change_format() state races escalate to connection-fatal errors; a client FormatChange with an out-of-range index permanently disables audio delivery; an OpenReply arriving without the initial FormatChange (which IronRDP's own client sends on Open rejection) permanently wedges the channel; OpenReply success uses S_OK equality instead of HRESULT success semantics; server-initiated DVC data uses empty flags diverging from every sibling path; and two small pieces of redundant state/control flow exist. All eight candidates are accepted with line ranges verified against the head tree.

Comment thread crates/ironrdp-rdpeai/src/server.rs Outdated
Comment thread crates/ironrdp-server/src/server.rs
Comment thread crates/ironrdp-rdpeai/src/server.rs
Comment thread crates/ironrdp-rdpeai/src/server.rs
Comment thread crates/ironrdp-rdpeai/src/server.rs Outdated
Comment thread crates/ironrdp-server/src/server.rs Outdated
Comment thread crates/ironrdp-rdpeai/src/server.rs Outdated
Comment thread crates/ironrdp-server/src/server.rs Outdated
@github-actions github-actions Bot added ai-reviewed/1 One automated review completed and removed needs-review A human reviewer is the current next actor labels Sep 11, 2026
Depends on the RdpeaiServer orchestration this stacks on. Wires it
into ironrdp-server the same way RdpdrServer's follow-up wiring did,
matching RdpeiServerFactory's shape:

- RdpeaiServerFactory (ironrdp-server/src/rdpeai.rs), a thin factory
  trait mirroring RdpdrServerFactory/SoundServerFactory: build_backend()
  returns the RdpeaiServerBackend the embedding application supplies.
- attach_channels registers RdpeaiServer on the DVC stack alongside
  AInput/DisplayControl/Echo/RdpeiServer when a factory is configured.
- RdpServerBuilder::with_rdpeai_factory(), following with_rdpdr_factory's
  shape exactly.
- ServerEvent::Rdpeai(RdpeaiServerMessage) lets the embedding
  application trigger open()/change_format() from outside the DVC
  message loop, since RdpeaiServer is nested inside DrdynvcServer
  rather than being its own top-level channel: the dispatch arm reaches
  it via DrdynvcServer::dvc_by_id_mut, the same path Echo's dispatch
  uses to reach EchoDvcBridge, then wraps the resulting PDUs with
  dvc::encode_dvc_messages before writing them.

rdpei/rdpeai are legitimately distinct protocols (touch/pen input vs.
audio input) whose names happen to be textually close; the
constructor's clippy::similar_names is expected and annotated rather
than worked around by renaming either.
…y and HRESULT gaps

The ServerEvent::Rdpeai dispatch arm escalated open()/change_format()
state rejections to a connection-fatal error via map_err_kind, even
though open()'s own doc comment documents that callers may legitimately
race negotiation completing. Every other unavailability in the same
arm (missing drdynvc, missing channel, channel not opened) is
drop-and-warn; this now matches, reserving '?' for genuine encode/write
failures.

handle_open_reply only accepted the PDU in AwaitingOpenReply, but
ironrdp-rdpeai's own client rejects Open by replying with OpenReply
failure directly, without a preceding FormatChange confirm, whenever
initialFormat is out of range or frames_per_packet is rejected. That
left the server wedged in AwaitingFormatConfirm forever with no path
back to Ready. MS-RDPEAI 3.3.5.1.8 conditions the server's reaction
only on the Result field, not on a preceding FormatChange, so the
handler now accepts the reply from AwaitingFormatConfirm too.

Open Reply success was checked via equality with S_OK rather than
proper HRESULT semantics (an HRESULT is an error only when its sign
bit is set, per 3.3.5.1.8), so other valid non-negative success codes
like S_FALSE took the failure path and silently dropped all
subsequent Data PDUs.

Also: server-initiated AUDIO_INPUT DVC data now uses
ChannelFlags::SHOW_PROTOCOL, matching every sibling send path (Echo,
USB, DrdynvcServer itself) instead of an unexplained ChannelFlags::empty();
and the redundant is_channel_opened guard is removed since
dvc_by_id_mut already returns None for an unopened channel and that
case was already handled.

Regression tests added for the OpenReply-before-FormatChange-confirm
wedge and the non-S_OK success code; both verified to fail against the
prior behavior and pass with the fix.
@github-actions github-actions Bot added risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny scope/cross-cutting Spans multiple architectural boundaries size/M Size: up to 449 counted lines and 10 files; exceeds S in either measure needs-review A human reviewer is the current next actor and removed risk/medium Behavioral change that does not substantially alter a core public API size/XL Size: up to 1299 counted lines and 49 files; exceeds L in either measure triage/overlap Possible overlap with another pull request; advisory only labels Sep 11, 2026
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit 5b139ba into Devolutions:master Sep 22, 2026
42 checks passed

This branch was successfully deployed

1 active deployment
llm-providers — fe73e188 Deployed Sep 11, 2026 by glamberson via Classify pull request #319
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 breaking-change Includes a breaking change, and requires special scrutiny at the boundaries kind/protocol Affects RDP or related protocol behavior needs-review A human reviewer is the current next actor risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny scope/cross-cutting Spans multiple architectural boundaries 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