Repository navigation
feat(server): let the display decline the Display Control channel - #1985
Greg Lamberson (glamberson) wants to merge 5 commits into
Conversation
e68daa9 to
ef12e2a
Compare
There was a problem hiding this comment.
Additive, low-risk PR: a default-true RdpServerDisplay::offers_display_control() hook gates registration of DisplayControlServer in attach_channels, letting fixed-size displays (e.g., mirrored physical screens) suppress the MS-RDPEDISP offer and avoid the no-reject layout-request hang. Both call sites (serve_negotiated and run_connection_inner) consistently query the hook before channel setup, the conditional dvc construction matches the existing shadowing-builder idiom used for echo/rdpei/rdpeai in the same function, and no capability sets or PDU formats change, so no protocol defect exists. Independently verified against pr-head. Two valid low-severity candidates from specialists are accepted unchanged: the new test only asserts the hook was consulted rather than that the channel is actually omitted from the connection, and serve_negotiated acquires the display mutex twice back-to-back where one held guard suffices.
- [code-compressor] Back-to-back display mutex acquisitions in serve_negotiated can share one guard — low 🟡 — crates/ironrdp-server/src/server.rs
serve_negotiated acquires the display tokio Mutex twice in a row (monitor_count, then the new offers_display_control query). Holding one guard across both awaited calls is behaviorally equivalent, removes one lock round-trip, and closes a window where another task could interleave between the two queries. The same pattern in run_connection_inner is separated by pre-existing code and is deliberately out of scope; only the adjacent pair in serve_negotiated is a clean, low-risk cleanup.
There was a problem hiding this comment.
PR #1985 adds a defaulted RdpServerDisplay::offers_display_control() hook queried once per connection, letting a display omit the MS-RDPEDISP Display Control dynamic channel when it cannot honor layout requests. Assessment confirms the change is additive and safe: the true default preserves behavior for all existing implementors, omitting the DYNVC_CREATE_REQ is specification-consistent (MS-RDPEDYC makes dynamic channel creation a server-initiated per-channel decision, and MS-RDPEDISP imposes no obligation to open the channel), and the gate is applied at both connection paths before channels attach. New e2e tests cover both outcomes with a real client. No correctness, protocol, or API defects were found. The remaining candidates are low-severity maintainability items, all independently verified: monitor_count() is awaited and discarded when the display declines (contradicting its documented contract tied to the channel opening), the test re-hardcodes ironrdp-displaycontrol::CHANNEL_NA…
- [skeptical] monitor_count() is awaited and discarded when the display declines Display Control — low 🟡 — crates/ironrdp-server/src/server.rs
Both call sites unconditionally await monitor_count() immediately before offers_display_control() (serve_negotiated at server.rs:2049-2050; the TLS path likewise), but when the gate returns false the DisplayControlBackend is never built and the value is unused. The monitor_count doc states it is 'called once, before the Display Control Virtual Channel opens' (display.rs:345-346), so an opting-out display now observes a call whose contract no longer holds, paying any implementation cost for nothing. Conditioning the monitor_count fetch (or folding both queries into attach_channels) restores the documented behavior. - [code-compressor] offer_display_control parameter threading and duplicated display-lock query could be folded into attach_channels — low 🟡 — crates/ironrdp-server/src/server.rs
attach_channels has exactly two callers, both async, and each repeats the same pattern: query the display under its lock, then pass the result as a parameter, with monitor_count already consumed only inside attach_channels. Making attach_channels async and querying monitor_count() and offers_display_control() inside it deletes the parameter threading, the monitor_count parameter, and the duplicated lock-and-query line at both call sites, with identical query timing relative to channel attachment. This would also resolve the discarded monitor_count call when the display declines.
|
Both points are right. monitor_count() was awaited at both call sites and discarded when the display declined, which contradicted its documented contract. attach_channels is now async and asks the display itself: It queries offers_display_control() first and only asks monitor_count() when the channel is offered, so the two parameters and the duplicated query at each call site are gone. |
|
The two display queries now share one guard. attach_channels holds the display lock across offers_display_control() and, only when the channel is offered, monitor_count(), so no other task can interleave between them, and it saves the second lock acquisition. |
MS-RDPEDISP section 1.3 gives a server no reject message for a monitor layout it cannot apply, so a client offered Display Control on a fixed-size display waits for a reconfiguration that never comes. ironrdp-client, for instance, waits out its 3 second resize deadline and then reconnects. Add RdpServerDisplay::offers_display_control(), queried once per connection before the dynamic channels are built. Returning false skips DisplayControlServer. Defaults to true, so existing implementations are unchanged.
The old test only showed offers_display_control was consulted. Two end-to-end tests in ironrdp-testsuite-extra now connect a real client that registers a Display Control channel and an echo channel. The server creates its dynamic channels in registration order, so once the echo channel has opened the answer for Display Control is final: a display that offers it gets the channel created on the client, a display that declines it does not. The e2e harness gains client_server_with_display so a test can supply its own RdpServerDisplay.
…the tests attach_channels is now async and asks the display itself. It queries offers_display_control first and only calls monitor_count when the channel is offered, so a display that declines is no longer asked for a value its documented contract ties to the channel opening, and the two parameters and the duplicated query at both call sites are gone. In the end-to-end tests the Display Control channel name comes from ironrdp::displaycontrol::CHANNEL_NAME (the displaycontrol feature is enabled on the ironrdp dev-dependency), TestDisplay carries an offers_display_control field in place of the ConfigurableDisplay wrapper, and the probe settles on a quiet connection after echo opens instead of relying on registration order.
offers_display_control and monitor_count were asked under two separate lock acquisitions in a row. attach_channels now holds one guard across both, asking monitor_count only when the channel is offered, so no other task can interleave between the two queries and the second acquisition is gone.
The rebase onto master brought in the stale_events and server_udp_addr arguments of client_server_impl, two new TestDisplay literals and the ironrdp_dvc import that covers the names StartRecorder uses. client_server_with_display now takes the two arguments and passes them on, the two other TestDisplay literals offer Display Control as they did before, and the import line is master's again.
d542fbf to
89f17cd
Compare
Summary
Validation
cargo xtask check fmt/lints/tests/typos/locksall pass. Two end-to-end tests in ironrdp-testsuite-extra connect a real client that registers a Display Control channel and an echo channel: A display that offers Display Control gets the channel created on the client, and a display that declines it does not. The declining test fails if the gate is removed.Notes