Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion crates/ironrdp-server/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use core::net::SocketAddr;
use core::sync::atomic::AtomicBool;
use core::sync::atomic::{AtomicBool, AtomicU32};
use std::sync::Arc;

use anyhow::Result;
Expand Down Expand Up @@ -41,6 +41,7 @@ pub struct BuilderDone {
#[cfg(feature = "egfx")]
gfx_factory: Option<Box<dyn GfxServerFactory>>,
display_suppressed: Option<Arc<AtomicBool>>,
autodetect_rtt: Option<Arc<AtomicU32>>,
}

pub struct RdpServerBuilder<State> {
Expand Down Expand Up @@ -140,6 +141,7 @@ impl RdpServerBuilder<WantsDisplay> {
#[cfg(feature = "egfx")]
gfx_factory: None,
display_suppressed: None,
autodetect_rtt: None,
},
}
}
Expand All @@ -160,6 +162,7 @@ impl RdpServerBuilder<WantsDisplay> {
#[cfg(feature = "egfx")]
gfx_factory: None,
display_suppressed: None,
autodetect_rtt: None,
},
}
}
Expand Down Expand Up @@ -241,6 +244,17 @@ impl RdpServerBuilder<BuilderDone> {
self
}

/// Inject a shared NetworkAutoDetect RTT handle (milliseconds, `u32::MAX`
/// until the first measurement). The server writes the latest measured RTT
/// to the same instance the backend reads. When not called, the server
/// allocates its own (still readable via
/// [`RdpServer::autodetect_rtt_handle`]). The value stays `u32::MAX` unless
/// auto-detect is enabled via [`RdpServer::enable_autodetect`].
pub fn with_autodetect_rtt_handle(mut self, handle: Arc<AtomicU32>) -> Self {
self.state.autodetect_rtt = Some(handle);
self
}

pub fn build(self) -> RdpServer {
let mut server = RdpServer::new(
RdpServerOptions {
Expand All @@ -257,6 +271,7 @@ impl RdpServerBuilder<BuilderDone> {
#[cfg(feature = "egfx")]
self.state.gfx_factory,
self.state.display_suppressed,
self.state.autodetect_rtt,
);
server.set_credential_validator(self.state.credential_validator);
server
Expand Down
42 changes: 31 additions & 11 deletions crates/ironrdp-server/src/server.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use core::fmt;
use core::net::SocketAddr;
use core::sync::atomic::{AtomicBool, Ordering};
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use core::time::Duration;
use std::rc::Rc;
use std::sync::Arc;
Expand Down Expand Up @@ -441,6 +441,14 @@ pub struct RdpServer {
/// and locks up its input dispatch for seconds on refocus while it
/// chews through the backlog.
display_suppressed: Arc<AtomicBool>,

/// Latest NetworkAutoDetect round-trip time in milliseconds, or `u32::MAX`
/// until the first measurement (and while auto-detect is disabled). Updated
/// on each RTT Measure Response when auto-detect is enabled (see
/// [`Self::enable_autodetect`]). Exposed via [`Self::autodetect_rtt_handle`]
/// so display backends can read a fresh, frame-traffic-independent network
/// RTT for flow control.
autodetect_rtt: Arc<AtomicU32>,
}

#[derive(Debug)]
Expand Down Expand Up @@ -475,17 +483,11 @@ enum RunState {
}

impl RdpServer {
// The lint only fires with the `egfx` feature on (8 args including
// `gfx_factory`); without it the parameter count is 7 and the lint
// is satisfied. `cfg_attr` keeps `#[expect]` strict in both modes.
#[cfg_attr(
feature = "egfx",
expect(
clippy::too_many_arguments,
reason = "called via the builder; positional parameters are an internal detail"
)
#[expect(
clippy::too_many_arguments,
reason = "called via the builder; positional parameters are an internal detail"
)]
pub fn new(
pub(crate) fn new(
opts: RdpServerOptions,
handler: Box<dyn RdpServerInputHandler>,
display: Box<dyn RdpServerDisplay>,
Expand All @@ -494,6 +496,7 @@ impl RdpServer {
connection_handler: Option<Box<dyn ConnectionHandler>>,
#[cfg(feature = "egfx")] mut gfx_factory: Option<Box<dyn GfxServerFactory>>,
display_suppressed: Option<Arc<AtomicBool>>,
autodetect_rtt: Option<Arc<AtomicU32>>,
) -> Self {
let (ev_sender, ev_receiver) = ServerEvent::create_channel();
if let Some(cliprdr) = cliprdr_factory.as_mut() {
Expand Down Expand Up @@ -526,6 +529,12 @@ impl RdpServer {
autodetect: None,
connection_handler,
display_suppressed: display_suppressed.unwrap_or_else(|| Arc::new(AtomicBool::new(false))),
autodetect_rtt: {
// Reset to the sentinel: an injected handle must not expose a stale value before the first measurement.
let handle = autodetect_rtt.unwrap_or_else(|| Arc::new(AtomicU32::new(u32::MAX)));
handle.store(u32::MAX, Ordering::Relaxed);
handle
},
}
Comment thread
glamberson marked this conversation as resolved.
Comment thread
glamberson marked this conversation as resolved.
}

Expand Down Expand Up @@ -589,6 +598,16 @@ impl RdpServer {
Arc::clone(&self.display_suppressed)
}

/// Returns a handle to the latest NetworkAutoDetect RTT in milliseconds
/// (`u32::MAX` until the first measurement, and while auto-detect is
/// disabled). The server updates it on each RTT Measure Response; backends
/// clone the handle to read a fresh network RTT for flow control. Inject a
/// shared instance at construction with
/// [`RdpServerBuilder::with_autodetect_rtt_handle`](crate::RdpServerBuilder::with_autodetect_rtt_handle).
pub fn autodetect_rtt_handle(&self) -> Arc<AtomicU32> {
Arc::clone(&self.autodetect_rtt)
}

/// Returns the shared ECHO server handle for runtime probe requests and RTT measurements.
pub fn echo_handle(&self) -> &EchoServerHandle {
&self.echo_handle
Expand Down Expand Up @@ -1408,6 +1427,7 @@ impl RdpServer {
rdp::headers::ShareDataPdu::AutoDetectRsp(response) => {
if let Some(ref mut ad) = self.autodetect {
if let Some(rtt_ms) = ad.handle_response(&response) {
self.autodetect_rtt.store(rtt_ms, Ordering::Relaxed);
debug!(rtt_ms, seq = response.sequence_number(), "RTT measured");
} else {
trace!(seq = response.sequence_number(), "Unmatched auto-detect response");
Expand Down
42 changes: 42 additions & 0 deletions crates/ironrdp-testsuite-core/tests/server/autodetect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,48 @@ fn sequence_number_wraps_at_u16_max() {
assert_eq!(req2.sequence_number(), 0, "should wrap around");
}

#[test]
fn autodetect_rtt_handle_defaults_to_sentinel() {
use core::net::{Ipv4Addr, SocketAddr};
use core::sync::atomic::Ordering;

use ironrdp_server::RdpServer;

let server = RdpServer::builder()
.with_addr(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)))
.with_no_security()
.with_no_input()
.with_no_display()
.build();

assert_eq!(server.autodetect_rtt_handle().load(Ordering::Relaxed), u32::MAX);
}

#[test]
fn with_autodetect_rtt_handle_round_trips_the_same_arc() {
use core::net::{Ipv4Addr, SocketAddr};
use core::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;

use ironrdp_server::RdpServer;

let handle = Arc::new(AtomicU32::new(42));
let server = RdpServer::builder()
.with_addr(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)))
.with_no_security()
.with_no_input()
.with_no_display()
.with_autodetect_rtt_handle(Arc::clone(&handle))
.build();

assert!(Arc::ptr_eq(&handle, &server.autodetect_rtt_handle()));
// The server resets an injected handle to the sentinel at construction.
assert_eq!(server.autodetect_rtt_handle().load(Ordering::Relaxed), u32::MAX);
// The Arc is shared: mutating the original is visible through the server's handle.
handle.store(42, Ordering::Relaxed);
assert_eq!(server.autodetect_rtt_handle().load(Ordering::Relaxed), 42);
}

#[test]
fn stale_probe_expiry() {
let mut mgr = AutoDetectManager::new();
Expand Down
Loading