Skip to content

Commit 4172582

Browse files
author
AKolenda
committed
refactor(session): answer auto-detect requests through one responder
Move the handling of RTT and bandwidth measurement requests out of the x224 processor into an `AutoDetectResponder`, so the message channel and a multitransport tunnel can answer them with the same code. Each transport keeps its own responder, because a continuous measurement counts the data received on its own transport ([MS-RDPBCGR] 3.2.5.14). No behavior change on the message channel.
1 parent d2d0ff9 commit 4172582

3 files changed

Lines changed: 134 additions & 87 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//! Client answers to the network auto-detect requests of [MS-RDPBCGR] 2.2.14.
2+
//!
3+
//! [MS-RDPBCGR]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dc672839-4f4e-40b1-a71c-cd6a959baa38
4+
5+
use ironrdp_core::MonotonicInstant;
6+
use ironrdp_pdu::rdp::autodetect::{
7+
AutoDetectRequest, AutoDetectResponse, BW_RESULTS_CONNECT_TIME, BW_RESULTS_CONTINUOUS, BW_START_CONNECT_TIME,
8+
BW_START_RELIABLE_UDP, BW_STOP_CONNECT_TIME, BW_STOP_RELIABLE_UDP,
9+
};
10+
use tracing::debug;
11+
12+
/// Answers the RTT and bandwidth measurement requests that arrive on one transport.
13+
///
14+
/// Each transport keeps its own, because a continuous measurement counts the data received on
15+
/// the transport it runs on ([MS-RDPBCGR] 3.2.5.14). Timestamps come from the caller, from the
16+
/// same monotonic clock for every request on that transport.
17+
///
18+
/// [MS-RDPBCGR]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/16ffa852-8aa7-481c-99a0-36c1a9a198f6
19+
#[derive(Debug, Default)]
20+
pub(crate) struct AutoDetectResponder {
21+
bandwidth: Option<BandwidthMeasurement>,
22+
}
23+
24+
#[derive(Debug)]
25+
struct BandwidthMeasurement {
26+
started_at: MonotonicInstant,
27+
bytes: u32,
28+
continuous: bool,
29+
}
30+
31+
impl AutoDetectResponder {
32+
/// Counts received bytes while a continuous bandwidth window is open.
33+
pub(crate) fn record_bytes(&mut self, bytes: usize) {
34+
if let Some(measurement) = self.bandwidth.as_mut().filter(|measurement| measurement.continuous) {
35+
measurement.bytes = measurement
36+
.bytes
37+
.saturating_add(u32::try_from(bytes).unwrap_or(u32::MAX));
38+
}
39+
}
40+
41+
/// Returns the response `request` calls for, if any.
42+
///
43+
/// Without an arrival time, a bandwidth measurement is answered with an untimed, zero-byte
44+
/// result.
45+
pub(crate) fn respond(
46+
&mut self,
47+
request: AutoDetectRequest,
48+
received_at: Option<MonotonicInstant>,
49+
) -> Option<AutoDetectResponse> {
50+
match request {
51+
AutoDetectRequest::RttRequest { sequence_number, .. } => {
52+
Some(AutoDetectResponse::RttResponse { sequence_number })
53+
}
54+
AutoDetectRequest::BandwidthMeasureStart { request_type, .. }
55+
if matches!(request_type, BW_START_CONNECT_TIME | BW_START_RELIABLE_UDP) =>
56+
{
57+
self.bandwidth = received_at.map(|started_at| BandwidthMeasurement {
58+
started_at,
59+
bytes: 0,
60+
continuous: request_type == BW_START_RELIABLE_UDP,
61+
});
62+
None
63+
}
64+
AutoDetectRequest::BandwidthMeasurePayload { payload, .. } => {
65+
if let Some(measurement) = self.bandwidth.as_mut().filter(|measurement| !measurement.continuous) {
66+
// [MS-RDPBCGR] 3.2.5.14 counts the eight-byte auto-detect
67+
// header as well as payloadLength, but not the security header.
68+
measurement.bytes = measurement
69+
.bytes
70+
.saturating_add(u32::try_from(payload.len()).unwrap_or(u32::MAX).saturating_add(8));
71+
}
72+
None
73+
}
74+
AutoDetectRequest::BandwidthMeasureStop {
75+
sequence_number,
76+
request_type,
77+
payload,
78+
} if matches!(request_type, BW_STOP_CONNECT_TIME | BW_STOP_RELIABLE_UDP) => {
79+
let continuous = request_type == BW_STOP_RELIABLE_UDP;
80+
let measurement = self
81+
.bandwidth
82+
.take()
83+
.filter(|measurement| measurement.continuous == continuous);
84+
let stop_bytes = if continuous {
85+
0
86+
} else {
87+
u32::try_from(payload.as_ref().map_or(0, Vec::len))
88+
.unwrap_or(u32::MAX)
89+
.saturating_add(8)
90+
};
91+
let (time_delta_ms, byte_count) = match (measurement, received_at) {
92+
(Some(measurement), Some(stopped_at)) => (
93+
u32::try_from(stopped_at.duration_since(measurement.started_at).as_millis())
94+
.unwrap_or(u32::MAX)
95+
.max(1),
96+
measurement.bytes.saturating_add(stop_bytes),
97+
),
98+
_ => (1, stop_bytes),
99+
};
100+
Some(AutoDetectResponse::BandwidthMeasureResults {
101+
sequence_number,
102+
response_type: if continuous {
103+
BW_RESULTS_CONTINUOUS
104+
} else {
105+
BW_RESULTS_CONNECT_TIME
106+
},
107+
time_delta_ms,
108+
byte_count,
109+
})
110+
}
111+
request @ AutoDetectRequest::NetworkCharacteristicsResult { .. } => {
112+
debug!(?request, "Received network characteristics from server");
113+
None
114+
}
115+
request => {
116+
// Measurements for a lossy transport are not answered here.
117+
debug!(?request, "Ignoring auto-detect request for another transport");
118+
None
119+
}
120+
}
121+
}
122+
}

‎crates/ironrdp-session/src/lib.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub mod rfx; // FIXME: maybe this module should not be in this crate
1111
pub mod x224;
1212

1313
mod active_stage;
14+
mod autodetect;
1415
mod palette;
1516

1617
use core::fmt;

‎crates/ironrdp-session/src/x224/mod.rs‎

Lines changed: 11 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ use ironrdp_core::{Decode as _, MonotonicInstant, ReadCursor, WriteBuf, decode};
33
use ironrdp_dvc::{DrdynvcClient, DvcClientProcessor, DynamicChannelMut, DynamicChannelRef};
44
use ironrdp_pdu::gcc::{ChannelName, Monitor};
55
use ironrdp_pdu::mcs::{DisconnectProviderUltimatum, DisconnectReason, McsMessage, SendDataIndicationCtx};
6-
use ironrdp_pdu::rdp::autodetect::{
7-
AutoDetectReqPdu, AutoDetectRequest, AutoDetectResponse, AutoDetectRspPdu, BW_RESULTS_CONNECT_TIME,
8-
BW_RESULTS_CONTINUOUS, BW_START_CONNECT_TIME, BW_START_RELIABLE_UDP, BW_STOP_CONNECT_TIME, BW_STOP_RELIABLE_UDP,
9-
};
6+
use ironrdp_pdu::rdp::autodetect::{AutoDetectReqPdu, AutoDetectRequest, AutoDetectRspPdu};
107
use ironrdp_pdu::rdp::client_info::CompressionType;
118
use ironrdp_pdu::rdp::headers::{
129
BasicSecurityHeader, BasicSecurityHeaderFlags, CompressionFlags, IoChannelPdu, ShareDataCtx, ShareDataPdu,
@@ -21,6 +18,7 @@ use ironrdp_svc::{
2118
};
2219
use tracing::debug;
2320

21+
use crate::autodetect::AutoDetectResponder;
2422
use crate::{SessionError, SessionErrorExt as _, SessionResult, reason_err};
2523

2624
/// X224 Processor output
@@ -110,13 +108,7 @@ pub struct Processor {
110108
io_channel_id: u16,
111109
message_channel_id: Option<u16>,
112110
share_id: u32,
113-
bandwidth: Option<BandwidthMeasurement>,
114-
}
115-
116-
struct BandwidthMeasurement {
117-
started_at: MonotonicInstant,
118-
bytes: u32,
119-
continuous: bool,
111+
auto_detect: AutoDetectResponder,
120112
}
121113

122114
impl Processor {
@@ -133,7 +125,7 @@ impl Processor {
133125
io_channel_id,
134126
message_channel_id,
135127
share_id,
136-
bandwidth: None,
128+
auto_detect: AutoDetectResponder::default(),
137129
}
138130
}
139131

@@ -477,11 +469,7 @@ impl Processor {
477469
/// Counts session bytes after transport/security headers while a continuous
478470
/// bandwidth window is open ([MS-RDPBCGR] 3.2.5.14).
479471
pub(crate) fn record_bandwidth_bytes(&mut self, bytes: usize) {
480-
if let Some(measurement) = self.bandwidth.as_mut().filter(|measurement| measurement.continuous) {
481-
measurement.bytes = measurement
482-
.bytes
483-
.saturating_add(u32::try_from(bytes).unwrap_or(u32::MAX));
484-
}
472+
self.auto_detect.record_bytes(bytes);
485473
}
486474

487475
/// Process a PDU received on the MCS message channel: auto-detect
@@ -539,76 +527,12 @@ impl Processor {
539527

540528
let req = decode::<AutoDetectReqPdu>(data_ctx.user_data).map_err(SessionError::decode)?;
541529

542-
let response = match req.request {
543-
AutoDetectRequest::RttRequest { sequence_number, .. } => {
544-
AutoDetectResponse::RttResponse { sequence_number }
545-
}
546-
AutoDetectRequest::BandwidthMeasureStart { request_type, .. }
547-
if matches!(request_type, BW_START_CONNECT_TIME | BW_START_RELIABLE_UDP) =>
548-
{
549-
self.bandwidth = received_at.map(|started_at| BandwidthMeasurement {
550-
started_at,
551-
bytes: 0,
552-
continuous: request_type == BW_START_RELIABLE_UDP,
553-
});
554-
return Ok(Vec::new());
555-
}
556-
AutoDetectRequest::BandwidthMeasurePayload { payload, .. } => {
557-
if let Some(measurement) = self.bandwidth.as_mut().filter(|measurement| !measurement.continuous) {
558-
// [MS-RDPBCGR] 3.2.5.14 counts the eight-byte auto-detect
559-
// header as well as payloadLength, but not the security header.
560-
measurement.bytes = measurement
561-
.bytes
562-
.saturating_add(u32::try_from(payload.len()).unwrap_or(u32::MAX).saturating_add(8));
563-
}
564-
return Ok(Vec::new());
565-
}
566-
AutoDetectRequest::BandwidthMeasureStop {
567-
sequence_number,
568-
request_type,
569-
payload,
570-
} if matches!(request_type, BW_STOP_CONNECT_TIME | BW_STOP_RELIABLE_UDP) => {
571-
let continuous = request_type == BW_STOP_RELIABLE_UDP;
572-
let measurement = self
573-
.bandwidth
574-
.take()
575-
.filter(|measurement| measurement.continuous == continuous);
576-
let stop_bytes = if continuous {
577-
0
578-
} else {
579-
u32::try_from(payload.as_ref().map_or(0, Vec::len))
580-
.unwrap_or(u32::MAX)
581-
.saturating_add(8)
582-
};
583-
let (time_delta_ms, byte_count) = match (measurement, received_at) {
584-
(Some(measurement), Some(stopped_at)) => (
585-
u32::try_from(stopped_at.duration_since(measurement.started_at).as_millis())
586-
.unwrap_or(u32::MAX)
587-
.max(1),
588-
measurement.bytes.saturating_add(stop_bytes),
589-
),
590-
_ => (1, stop_bytes),
591-
};
592-
AutoDetectResponse::BandwidthMeasureResults {
593-
sequence_number,
594-
response_type: if continuous {
595-
BW_RESULTS_CONTINUOUS
596-
} else {
597-
BW_RESULTS_CONNECT_TIME
598-
},
599-
time_delta_ms,
600-
byte_count,
601-
}
602-
}
603-
req @ AutoDetectRequest::NetworkCharacteristicsResult { .. } => {
604-
debug!(?req, "Received network characteristics from server");
605-
return Ok(vec![ProcessorOutput::AutoDetect(req)]);
606-
}
607-
req => {
608-
// Lossy variants belong to a lossy tunnel, never this channel.
609-
debug!(?req, "Ignoring auto-detect request for another transport");
610-
return Ok(Vec::new());
611-
}
530+
if let request @ AutoDetectRequest::NetworkCharacteristicsResult { .. } = req.request {
531+
debug!(?request, "Received network characteristics from server");
532+
return Ok(vec![ProcessorOutput::AutoDetect(request)]);
533+
}
534+
let Some(response) = self.auto_detect.respond(req.request, received_at) else {
535+
return Ok(Vec::new());
612536
};
613537
debug!(?response, "Responding to an auto-detect request");
614538
let mut frame = WriteBuf::new();

0 commit comments

Comments
 (0)