Skip to content
Draft
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
58 changes: 58 additions & 0 deletions crates/ironrdp-server/src/autodetect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
//!
//! [MS-RDPBCGR 2.2.14]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dc672839-4f4e-40b1-a71c-cd6a959baa38

use core::sync::atomic::AtomicU32;
use std::collections::VecDeque;
use std::sync::Arc;

use ironrdp_pdu::rdp::autodetect::{AutoDetectRequest, AutoDetectResponse};

Expand Down Expand Up @@ -388,3 +390,59 @@ pub struct RttSnapshot {
/// Number of samples in the current window.
pub sample_count: usize,
}

/// The latest auto-detect measurements of one connection, written by the server
/// as they arrive.
///
/// [`Default`] creates handles at their initial values: no measurement yet,
/// generation 0. The server creates them that way for each connection, so a
/// new connection never sees the figures of the one before it. They stay at
/// those values while auto-detect is disabled (see
/// [`RdpServer::enable_autodetect`](crate::RdpServer::enable_autodetect)).
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct AutoDetectHandles {
/// Latest round-trip time in milliseconds, or `u32::MAX` until the first
/// measurement. Updated on each RTT Measure Response, so a reader gets a
/// fresh, frame-traffic-independent network RTT for flow control.
pub rtt: Arc<AtomicU32>,

/// Lowest RTT of the connection in milliseconds (`baseRTT` per
/// [MS-RDPBCGR] 2.2.14.1.5), or `u32::MAX` until the first measurement.
/// Updated at the same point as [`Self::rtt`], but unlike it this figure
/// never rises: pair it with that figure to derive queueing delay
/// (`averageRTT - baseRTT`), which `rtt` alone cannot give since it is a
/// sliding-window value that rises as low samples age out.
pub baseline_rtt: Arc<AtomicU32>,

/// Latest measured bandwidth in kilobits per second, or `u32::MAX` until
/// the first measurement completes. Updated whenever a Bandwidth Measure
/// Results response completes a measurement, with the figure the server
/// also reports to the client on the wire; a measurement without a usable
/// figure sets it back to `u32::MAX`.
pub bandwidth: Arc<AtomicU32>,

/// Pairs with [`Self::bandwidth`]: increments every time that figure is
/// republished, since the figure itself repeats too often to be its own
/// freshness signal. Load this with `Ordering::Acquire` to detect a fresh
/// measurement window, then read the bandwidth: the server increments this
/// with `Ordering::Release` after storing the value, so the bandwidth read
/// is at least as new as the generation observed. It is not an exact pair:
/// if the next window closes between the two reads, the value can already
/// belong to that later window.
///
/// Starts at 0 with each connection, so compare it only with generations
/// read from this same handle.
pub bandwidth_generation: Arc<AtomicU32>,
}

impl Default for AutoDetectHandles {
fn default() -> Self {
Self {
rtt: Arc::new(AtomicU32::new(u32::MAX)),
baseline_rtt: Arc::new(AtomicU32::new(u32::MAX)),
bandwidth: Arc::new(AtomicU32::new(u32::MAX)),
bandwidth_generation: Arc::new(AtomicU32::new(0)),
}
}
}
94 changes: 2 additions & 92 deletions crates/ironrdp-server/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use core::net::SocketAddr;
use core::sync::atomic::{AtomicBool, AtomicU32};
use std::sync::Arc;

use ironrdp_pdu::codecs::rfx::Quant;
Expand All @@ -8,7 +7,7 @@ use ironrdp_pdu::rdp::session_info::ServerAutoReconnect;
use tokio_rustls::TlsAcceptor;

use super::clipboard::CliprdrServerFactory;
use super::display::{DesktopSize, RdpServerDisplay};
use super::display::{DesktopSize, DisplayContext, RdpServerDisplay};
#[cfg(feature = "egfx")]
use super::gfx::GfxServerFactory;
use super::handler::{KeyboardEvent, MouseEvent, RdpServerInputHandler};
Expand Down Expand Up @@ -56,11 +55,6 @@ pub struct BuilderDone {
gfx_factory: Option<Box<dyn GfxServerFactory>>,
#[cfg(feature = "usb")]
usb_factory: Option<Box<dyn DeviceFactory>>,
display_suppressed: Option<Arc<AtomicBool>>,
autodetect_rtt: Option<Arc<AtomicU32>>,
autodetect_baseline_rtt: Option<Arc<AtomicU32>>,
autodetect_bandwidth: Option<Arc<AtomicU32>>,
autodetect_bandwidth_generation: Option<Arc<AtomicU32>>,
honor_client_desktop_size: Option<DesktopSize>,
auto_reconnect_cookie: Option<ServerAutoReconnect>,
connection_policy: ConnectionPolicy,
Expand Down Expand Up @@ -171,11 +165,6 @@ impl RdpServerBuilder<WantsDisplay> {
gfx_factory: None,
#[cfg(feature = "usb")]
usb_factory: None,
display_suppressed: None,
autodetect_rtt: None,
autodetect_baseline_rtt: None,
autodetect_bandwidth: None,
autodetect_bandwidth_generation: None,
honor_client_desktop_size: None,
connection_policy: ConnectionPolicy::default(),
auto_reconnect_cookie: None,
Expand Down Expand Up @@ -207,11 +196,6 @@ impl RdpServerBuilder<WantsDisplay> {
gfx_factory: None,
#[cfg(feature = "usb")]
usb_factory: None,
display_suppressed: None,
autodetect_rtt: None,
autodetect_baseline_rtt: None,
autodetect_bandwidth: None,
autodetect_bandwidth_generation: None,
honor_client_desktop_size: None,
connection_policy: ConnectionPolicy::default(),
auto_reconnect_cookie: None,
Expand Down Expand Up @@ -293,26 +277,6 @@ impl RdpServerBuilder<BuilderDone> {
self
}

/// Share the server's "display suppressed" flag with the display
/// backend before construction.
///
/// The flag is `true` while the connected client has sent
/// `SuppressOutput { desktop_rect: None }` (e.g., mstsc minimized).
/// Display backends that want to skip frame emission while the
/// client is minimized create one `Arc<AtomicBool>` in the
/// application, hand a clone to the display, and pass the same
/// `Arc` here so the server's per-connection PDU handler writes to
/// the same instance the backend reads.
///
/// When this is not called, the server allocates its own internal
/// flag (still readable via [`RdpServer::display_suppressed_handle`])
/// — useful when the backend can call `display_suppressed_handle()`
/// after construction to obtain a handle, rather than sharing one in.
pub fn with_display_suppressed_handle(mut self, handle: Arc<AtomicBool>) -> Self {
self.state.display_suppressed = Some(handle);
self
}

/// Negotiate each session at the desktop size the client requests in its
/// Client Core Data, rather than the size reported by the display handler.
///
Expand Down Expand Up @@ -387,55 +351,6 @@ 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
}

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

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

/// Inject a shared handle that increments every time a Bandwidth Measure
/// transaction completes, whether or not it produced a usable figure.
/// Pairs with [`Self::with_autodetect_bandwidth_handle`]: the bandwidth
/// figure alone repeats too often to tell a fresh measurement window
/// apart from a stale one. When not called, the server allocates its own
/// (still readable via
/// [`RdpServer::autodetect_bandwidth_generation_handle`]).
pub fn with_autodetect_bandwidth_generation_handle(mut self, handle: Arc<AtomicU32>) -> Self {
self.state.autodetect_bandwidth_generation = Some(handle);
self
}

/// Provision the Server Auto-Reconnect Cookie (MS-RDPBCGR 2.2.4.2
/// `ARC_SC_PRIVATE_PACKET`) handed to the client during logon.
///
Expand Down Expand Up @@ -538,13 +453,8 @@ impl RdpServerBuilder<BuilderDone> {
self.state.connection_handler,
#[cfg(feature = "egfx")]
self.state.gfx_factory,
self.state.display_suppressed,
#[cfg(feature = "usb")]
self.state.usb_factory,
self.state.autodetect_rtt,
self.state.autodetect_baseline_rtt,
self.state.autodetect_bandwidth,
self.state.autodetect_bandwidth_generation,
);
server.set_credential_validator(self.state.credential_validator);
server.set_auto_reconnect_cookie(self.state.auto_reconnect_cookie);
Expand Down Expand Up @@ -577,7 +487,7 @@ impl RdpServerDisplay for NoopDisplay {
DesktopSize { width: 0, height: 0 }
}

async fn updates(&mut self) -> ServerResult<Box<dyn RdpServerDisplayUpdates>> {
async fn updates(&mut self, _: DisplayContext) -> ServerResult<Box<dyn RdpServerDisplayUpdates>> {
Ok(Box::new(NoopDisplayUpdates {}))
}
}
51 changes: 48 additions & 3 deletions crates/ironrdp-server/src/display.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use core::num::{NonZeroU16, NonZeroUsize};
use core::sync::atomic::AtomicBool;
use std::sync::Arc;

use bytes::{Bytes, BytesMut};
use ironrdp_displaycontrol::pdu::DisplayControlMonitorLayout;
use ironrdp_graphics::diff;
use ironrdp_pdu::pointer::PointerPositionAttribute;
use tracing::{debug, warn};

use crate::autodetect::AutoDetectHandles;
use crate::error::ServerResult;

#[rustfmt::skip]
Expand Down Expand Up @@ -281,12 +284,50 @@ pub trait RdpServerDisplayUpdates {
async fn next_update(&mut self) -> ServerResult<Option<DisplayUpdate>>;
}

/// What a connection publishes to its display backend, handed to
/// [`RdpServerDisplay::updates`].
///
/// The server writes these values while the connection runs; the backend keeps
/// the handles it needs and reads them. Each connection has its own handles,
/// starting from the initial values documented on each field, so nothing the
/// previous connection set or measured carries over. A
/// Deactivation-Reactivation Sequence keeps the connection, so `updates` is
/// then called again with a context holding the same handles.
#[derive(Debug)]
#[non_exhaustive]
pub struct DisplayContext {
/// `true` while the client has sent `SuppressOutput { desktop_rect: None }`
/// (e.g., mstsc minimized), `false` at the start of the connection. Cleared
/// on `SuppressOutput { Some(rect) }` or `RefreshRectangle`.
///
/// A backend can skip frame emission while it's set, so the client doesn't
/// accumulate a backlog of frames it can't present until refocus.
///
/// **Caveat:** some clients (notably mstsc) send
/// `SuppressOutput { desktop_rect: None }` during their connect
/// handshake *before* their display surface is fully initialized; a
/// backend that honors the flag blindly will block that first frame
/// and leave the client with a half-initialized surface that doesn't
/// recover on un-suppress (visible as a frozen desktop on first
/// connect). Backends are advised to defer acting on the flag until
/// after the first frame has been delivered to the client, and to
/// debounce transient flaps (some clients pulse this PDU under wire
/// pressure on heavy CPU/IO loads) — e.g., only engage the gate once
/// the flag has been steady-`true` for ~1 s.
pub display_suppressed: Arc<AtomicBool>,

/// The connection's auto-detect measurements, for flow control.
pub autodetect: AutoDetectHandles,
}

/// Display for an RDP server
///
/// # Example
///
/// ```
/// use ironrdp_server::{DesktopSize, DisplayUpdate, RdpServerDisplay, RdpServerDisplayUpdates, ServerResult};
/// use ironrdp_server::{
/// DesktopSize, DisplayContext, DisplayUpdate, RdpServerDisplay, RdpServerDisplayUpdates, ServerResult,
/// };
///
/// pub struct DisplayUpdates {
/// receiver: tokio::sync::mpsc::Receiver<DisplayUpdate>,
Expand All @@ -310,7 +351,7 @@ pub trait RdpServerDisplayUpdates {
/// DesktopSize { width: self.width, height: self.height }
/// }
///
/// async fn updates(&mut self) -> ServerResult<Box<dyn RdpServerDisplayUpdates>> {
/// async fn updates(&mut self, _ctx: DisplayContext) -> ServerResult<Box<dyn RdpServerDisplayUpdates>> {
/// Ok(Box::new(DisplayUpdates { receiver: todo!() }))
/// }
/// }
Expand All @@ -329,7 +370,11 @@ pub trait RdpServerDisplay: Send {
}

/// Return a display updates receiver
async fn updates(&mut self) -> ServerResult<Box<dyn RdpServerDisplayUpdates>>;
///
/// Called when a connection's session starts, and again after each
/// Deactivation-Reactivation Sequence of that connection. `ctx` carries
/// what the connection publishes to the display; see [`DisplayContext`].
async fn updates(&mut self, ctx: DisplayContext) -> ServerResult<Box<dyn RdpServerDisplayUpdates>>;

/// Request a new size for the display
fn request_layout(&mut self, layout: DisplayControlMonitorLayout) {
Expand Down
4 changes: 2 additions & 2 deletions crates/ironrdp-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ mod urbdrc;

pub use clipboard::CliprdrServerFactory;
pub use display::{
BitmapUpdate, ColorPointer, DesktopSize, DisplayUpdate, Framebuffer, LargePointer, PixelFormat, RGBAPointer,
RdpServerDisplay, RdpServerDisplayUpdates,
BitmapUpdate, ColorPointer, DesktopSize, DisplayContext, DisplayUpdate, Framebuffer, LargePointer, PixelFormat,
RGBAPointer, RdpServerDisplay, RdpServerDisplayUpdates,
};
pub use echo::{EchoDvcBridge, EchoRoundTripMeasurement, EchoServerHandle, EchoServerMessage};
pub use error::{ServerError, ServerErrorExt, ServerErrorKind, ServerResult, ServerResultExt};
Expand Down
Loading
Loading