Repository navigation
feat(pdu): decode the Server Heartbeat PDU on the message channel - #1814
Merged
Marc-André Moreau (mamoreau-devolutions) merged 1 commit intoAug 28, 2026
Conversation
Greg Lamberson (glamberson)
temporarily deployed
to
llm-providers
August 27, 2026 23:38 — with
GitHub Actions
Inactive
Greg Lamberson (glamberson)
force-pushed
the
feat/message-channel-heartbeat
branch
from
August 28, 2026 01:34
f1acc50 to
d6e406b
Compare
Greg Lamberson (glamberson)
temporarily deployed
to
llm-providers
August 28, 2026 01:35 — with
GitHub Actions
Inactive
Marc-André Moreau (mamoreau-devolutions)
approved these changes
Aug 28, 2026
Marc-André Moreau (mamoreau-devolutions)
merged commit Aug 28, 2026
4275b3d
into
Devolutions:master
43 checks passed
This was referenced Aug 30, 2026
Marc-André Moreau (mamoreau-devolutions)
pushed a commit
that referenced
this pull request
Aug 30, 2026
) The server never sends Server Heartbeat PDUs (MS-RDPBCGR 2.2.16.1), even though the client half of the feature is in place: HeartbeatPdu encode/decode landed with #1814 and clients decode and ignore them. This adds the send half, opt-in. RdpServer::enable_heartbeat(HeartbeatConfig) arms a fifth joined future in the client loop, following refresh_auto_reconnect_cookie's shape. It only runs when three gates hold: the feature is enabled, the client requested a message channel, and the client advertised RNS_UD_CS_SUPPORT_HEARTBEAT_PDU in its GCC early capability flags. That last gate needed the acceptor to expose those flags, so AcceptorResult gains client_early_capability_flags, populated the same way #1453 exposed multitransport_flags. Two spec details drove the shape. Heartbeats ride the MCS message channel framed by a Basic Security Header carrying SEC_HEARTBEAT, exactly like auto-detect after #1348, so encode_heartbeat mirrors encode_autodetect_request. And 2.2.16.1 says heartbeats SHOULD only be sent when no other PDU went out in the interval, so SharedWriter now counts successful writes and the heartbeat tick skips the send when the count moved: ordinary traffic doubles as the liveness signal. The defaults (period 5 seconds, warning after 3 missed heartbeats, reconnect after 5 more) are this library's own choice; the spec mandates no values and the client MAY ignore both counts. A zero period is bumped to one second to keep the timer well-formed. No new tests: the wire format is already covered by the HeartbeatPdu round-trip tests #1814 added in ironrdp-pdu, and ironrdp-server builds no test harness for the client-loop plumbing this touches. `cargo xtask check fmt/lints/tests/typos/locks` all pass.
holovchenko
pushed a commit
to holovchenko/IronRDP
that referenced
this pull request
Sep 25, 2026
…volutions#1814) ## Summary Adds `HeartbeatPdu` (MS-RDPBCGR 2.2.16.1), decoded on the MCS message channel that Devolutions#1347/Devolutions#1348 wired up for Auto-Detect. The Heartbeat PDU is server-to-client only and defines no client response; the client is free to ignore `count1`/`count2`. Fixes a real gap this exposed: the message-channel demux in `ironrdp-session` unconditionally decoded incoming traffic as an Auto-Detect Request PDU. A Heartbeat PDU arriving on the same channel would fail that decode and error the session. The demux now peeks the security-header flags first (masking `SEC_RESET_SEQNO`/`SEC_IGNORE_SEQNO`, which MS-RDPBCGR 2.2.8.1.1.2.1 says MUST be ignored, the same way `MultitransportRequestPdu`/`MultitransportResponsePdu` already do) and dispatches by the masked value. Anything it doesn't recognize is logged and ignored rather than treated as session-fatal, matching the forward-safe posture the connect-time demux in `ironrdp-connector` already has. Multitransport Bootstrapping, the other optional feature the message channel carries, is already fully implemented (Devolutions#1098); this PR is the Heartbeat half only. ## Validation `cargo xtask check fmt/typos/tests/locks` and `wasm check` all pass. `lints` has one pre-existing failure on master unrelated to this diff (`clippy::result_large_err` on `ErrorInfoDisconnectHandle::disconnect`, fix filed as Devolutions#1812); this PR's own diff is clean under the same lints run. New tests: `HeartbeatPdu` encode/decode round-trip plus the same seqno-flag and cross-PDU-discriminator coverage `MultitransportRequestPdu` has, in `ironrdp-pdu`; a `Processor::process` integration test in `ironrdp-testsuite-core` covering the full message-channel path (Heartbeat produces no output, Auto-Detect still works alongside it, and an unrecognized PDU is ignored rather than erroring). (cherry picked from commit 4275b3d)
This was referenced Oct 23, 2026
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
HeartbeatPdu(MS-RDPBCGR 2.2.16.1), decoded on the MCS message channel that #1347/#1348 wired up for Auto-Detect. The Heartbeat PDU is server-to-client only and defines no client response; the client is free to ignorecount1/count2.Fixes a real gap this exposed: the message-channel demux in
ironrdp-sessionunconditionally decoded incoming traffic as an Auto-Detect Request PDU. A Heartbeat PDU arriving on the same channel would fail that decode and error the session. The demux now peeks the security-header flags first (maskingSEC_RESET_SEQNO/SEC_IGNORE_SEQNO, which MS-RDPBCGR 2.2.8.1.1.2.1 says MUST be ignored, the same wayMultitransportRequestPdu/MultitransportResponsePdualready do) and dispatches by the masked value. Anything it doesn't recognize is logged and ignored rather than treated as session-fatal, matching the forward-safe posture the connect-time demux inironrdp-connectoralready has.Multitransport Bootstrapping, the other optional feature the message channel carries, is already fully implemented (#1098); this PR is the Heartbeat half only.
Validation
cargo xtask check fmt/typos/tests/locksandwasm checkall pass.lintshas one pre-existing failure on master unrelated to this diff (clippy::result_large_erronErrorInfoDisconnectHandle::disconnect, fix filed as #1812); this PR's own diff is clean under the same lints run.New tests:
HeartbeatPduencode/decode round-trip plus the same seqno-flag and cross-PDU-discriminator coverageMultitransportRequestPduhas, inironrdp-pdu; aProcessor::processintegration test inironrdp-testsuite-corecovering the full message-channel path (Heartbeat produces no output, Auto-Detect still works alongside it, and an unrecognized PDU is ignored rather than erroring).