Skip to content

Commit 4f5bd55

Browse files
committed
feat(server): expose NetworkAutoDetect RTT via a shared handle
The server measures network RTT in its AutoDetectManager, but after run() takes ownership a backend can no longer call rtt_snapshot(): the measurement is stranded in the running server. Mirror the existing display_suppressed handle so the value can be shared out. Add an Arc<AtomicU32> holding the latest measured RTT in milliseconds (u32::MAX until the first measurement), a with_autodetect_rtt_handle builder injector, and an autodetect_rtt_handle() getter. The AutoDetectRsp handler stores the measured rtt_ms into it. A display backend can then read a fresh, frame-traffic-independent network RTT for flow control without polling the server object. Drop the cfg_attr(egfx) gate on the new() too_many_arguments expect: the added parameter makes the non-egfx parameter count 8 (was 7), so the lint now fires in both feature configs and the expect is satisfied unconditionally.
1 parent 9046144 commit 4f5bd55

3 files changed

Lines changed: 79 additions & 11 deletions

File tree

crates/ironrdp-server/src/builder.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use core::net::SocketAddr;
2-
use core::sync::atomic::AtomicBool;
2+
use core::sync::atomic::{AtomicBool, AtomicU32};
33
use std::sync::Arc;
44

55
use anyhow::Result;
@@ -41,6 +41,7 @@ pub struct BuilderDone {
4141
#[cfg(feature = "egfx")]
4242
gfx_factory: Option<Box<dyn GfxServerFactory>>,
4343
display_suppressed: Option<Arc<AtomicBool>>,
44+
autodetect_rtt: Option<Arc<AtomicU32>>,
4445
}
4546

4647
pub struct RdpServerBuilder<State> {
@@ -140,6 +141,7 @@ impl RdpServerBuilder<WantsDisplay> {
140141
#[cfg(feature = "egfx")]
141142
gfx_factory: None,
142143
display_suppressed: None,
144+
autodetect_rtt: None,
143145
},
144146
}
145147
}
@@ -160,6 +162,7 @@ impl RdpServerBuilder<WantsDisplay> {
160162
#[cfg(feature = "egfx")]
161163
gfx_factory: None,
162164
display_suppressed: None,
165+
autodetect_rtt: None,
163166
},
164167
}
165168
}
@@ -241,6 +244,17 @@ impl RdpServerBuilder<BuilderDone> {
241244
self
242245
}
243246

247+
/// Inject a shared NetworkAutoDetect RTT handle (milliseconds, `u32::MAX`
248+
/// until the first measurement). The server writes the latest measured RTT
249+
/// to the same instance the backend reads. When not called, the server
250+
/// allocates its own (still readable via
251+
/// [`RdpServer::autodetect_rtt_handle`]). The value stays `u32::MAX` unless
252+
/// auto-detect is enabled via [`RdpServer::enable_autodetect`].
253+
pub fn with_autodetect_rtt_handle(mut self, handle: Arc<AtomicU32>) -> Self {
254+
self.state.autodetect_rtt = Some(handle);
255+
self
256+
}
257+
244258
pub fn build(self) -> RdpServer {
245259
let mut server = RdpServer::new(
246260
RdpServerOptions {
@@ -257,6 +271,7 @@ impl RdpServerBuilder<BuilderDone> {
257271
#[cfg(feature = "egfx")]
258272
self.state.gfx_factory,
259273
self.state.display_suppressed,
274+
self.state.autodetect_rtt,
260275
);
261276
server.set_credential_validator(self.state.credential_validator);
262277
server

crates/ironrdp-server/src/server.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use core::fmt;
22
use core::net::SocketAddr;
3-
use core::sync::atomic::{AtomicBool, Ordering};
3+
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
44
use core::time::Duration;
55
use std::rc::Rc;
66
use std::sync::Arc;
@@ -441,6 +441,14 @@ pub struct RdpServer {
441441
/// and locks up its input dispatch for seconds on refocus while it
442442
/// chews through the backlog.
443443
display_suppressed: Arc<AtomicBool>,
444+
445+
/// Latest NetworkAutoDetect round-trip time in milliseconds, or `u32::MAX`
446+
/// until the first measurement (and while auto-detect is disabled). Updated
447+
/// on each RTT Measure Response when auto-detect is enabled (see
448+
/// [`Self::enable_autodetect`]). Exposed via [`Self::autodetect_rtt_handle`]
449+
/// so display backends can read a fresh, frame-traffic-independent network
450+
/// RTT for flow control.
451+
autodetect_rtt: Arc<AtomicU32>,
444452
}
445453

446454
#[derive(Debug)]
@@ -475,15 +483,9 @@ enum RunState {
475483
}
476484

477485
impl RdpServer {
478-
// The lint only fires with the `egfx` feature on (8 args including
479-
// `gfx_factory`); without it the parameter count is 7 and the lint
480-
// is satisfied. `cfg_attr` keeps `#[expect]` strict in both modes.
481-
#[cfg_attr(
482-
feature = "egfx",
483-
expect(
484-
clippy::too_many_arguments,
485-
reason = "called via the builder; positional parameters are an internal detail"
486-
)
486+
#[expect(
487+
clippy::too_many_arguments,
488+
reason = "called via the builder; positional parameters are an internal detail"
487489
)]
488490
pub fn new(
489491
opts: RdpServerOptions,
@@ -494,6 +496,7 @@ impl RdpServer {
494496
connection_handler: Option<Box<dyn ConnectionHandler>>,
495497
#[cfg(feature = "egfx")] mut gfx_factory: Option<Box<dyn GfxServerFactory>>,
496498
display_suppressed: Option<Arc<AtomicBool>>,
499+
autodetect_rtt: Option<Arc<AtomicU32>>,
497500
) -> Self {
498501
let (ev_sender, ev_receiver) = ServerEvent::create_channel();
499502
if let Some(cliprdr) = cliprdr_factory.as_mut() {
@@ -526,6 +529,7 @@ impl RdpServer {
526529
autodetect: None,
527530
connection_handler,
528531
display_suppressed: display_suppressed.unwrap_or_else(|| Arc::new(AtomicBool::new(false))),
532+
autodetect_rtt: autodetect_rtt.unwrap_or_else(|| Arc::new(AtomicU32::new(u32::MAX))),
529533
}
530534
}
531535

@@ -589,6 +593,16 @@ impl RdpServer {
589593
Arc::clone(&self.display_suppressed)
590594
}
591595

596+
/// Returns a handle to the latest NetworkAutoDetect RTT in milliseconds
597+
/// (`u32::MAX` until the first measurement, and while auto-detect is
598+
/// disabled). The server updates it on each RTT Measure Response; backends
599+
/// clone the handle to read a fresh network RTT for flow control. Inject a
600+
/// shared instance at construction with
601+
/// [`RdpServerBuilder::with_autodetect_rtt_handle`](crate::RdpServerBuilder::with_autodetect_rtt_handle).
602+
pub fn autodetect_rtt_handle(&self) -> Arc<AtomicU32> {
603+
Arc::clone(&self.autodetect_rtt)
604+
}
605+
592606
/// Returns the shared ECHO server handle for runtime probe requests and RTT measurements.
593607
pub fn echo_handle(&self) -> &EchoServerHandle {
594608
&self.echo_handle
@@ -1408,6 +1422,7 @@ impl RdpServer {
14081422
rdp::headers::ShareDataPdu::AutoDetectRsp(response) => {
14091423
if let Some(ref mut ad) = self.autodetect {
14101424
if let Some(rtt_ms) = ad.handle_response(&response) {
1425+
self.autodetect_rtt.store(rtt_ms, Ordering::Relaxed);
14111426
debug!(rtt_ms, seq = response.sequence_number(), "RTT measured");
14121427
} else {
14131428
trace!(seq = response.sequence_number(), "Unmatched auto-detect response");

crates/ironrdp-testsuite-core/tests/server/autodetect.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,44 @@ fn sequence_number_wraps_at_u16_max() {
7777
assert_eq!(req2.sequence_number(), 0, "should wrap around");
7878
}
7979

80+
#[test]
81+
fn autodetect_rtt_handle_defaults_to_sentinel() {
82+
use core::net::{Ipv4Addr, SocketAddr};
83+
use core::sync::atomic::Ordering;
84+
85+
use ironrdp_server::RdpServer;
86+
87+
let server = RdpServer::builder()
88+
.with_addr(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)))
89+
.with_no_security()
90+
.with_no_input()
91+
.with_no_display()
92+
.build();
93+
94+
assert_eq!(server.autodetect_rtt_handle().load(Ordering::Relaxed), u32::MAX);
95+
}
96+
97+
#[test]
98+
fn with_autodetect_rtt_handle_round_trips_the_same_arc() {
99+
use core::net::{Ipv4Addr, SocketAddr};
100+
use core::sync::atomic::{AtomicU32, Ordering};
101+
use std::sync::Arc;
102+
103+
use ironrdp_server::RdpServer;
104+
105+
let handle = Arc::new(AtomicU32::new(42));
106+
let server = RdpServer::builder()
107+
.with_addr(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)))
108+
.with_no_security()
109+
.with_no_input()
110+
.with_no_display()
111+
.with_autodetect_rtt_handle(Arc::clone(&handle))
112+
.build();
113+
114+
assert!(Arc::ptr_eq(&handle, &server.autodetect_rtt_handle()));
115+
assert_eq!(server.autodetect_rtt_handle().load(Ordering::Relaxed), 42);
116+
}
117+
80118
#[test]
81119
fn stale_probe_expiry() {
82120
let mut mgr = AutoDetectManager::new();

0 commit comments

Comments
 (0)