nomad_protocol/transport/
timing.rs1use std::time::{Duration, Instant};
6
7pub mod constants {
9 use std::time::Duration;
10
11 pub const INITIAL_RTO: Duration = Duration::from_millis(1000);
13
14 pub const MIN_RTO: Duration = Duration::from_millis(100);
16
17 pub const MAX_RTO: Duration = Duration::from_millis(60000);
19
20 pub const SRTT_ALPHA: f64 = 0.125;
22
23 pub const RTTVAR_BETA: f64 = 0.25;
25
26 pub const RTO_K: f64 = 4.0;
28
29 pub const MIN_RTO_GRANULARITY_MS: f64 = 100.0;
31}
32
33#[derive(Debug, Clone)]
38pub struct RttEstimator {
39 srtt: f64,
41 rttvar: f64,
43 rto: Duration,
45 initialized: bool,
47}
48
49impl Default for RttEstimator {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl RttEstimator {
56 pub fn new() -> Self {
58 Self {
59 srtt: 0.0,
60 rttvar: 0.0,
61 rto: constants::INITIAL_RTO,
62 initialized: false,
63 }
64 }
65
66 pub fn update(&mut self, sample: Duration) {
73 let sample_ms = sample.as_secs_f64() * 1000.0;
74
75 if !self.initialized {
76 self.srtt = sample_ms;
78 self.rttvar = sample_ms / 2.0;
79 self.initialized = true;
80 } else {
81 self.rttvar = (1.0 - constants::RTTVAR_BETA) * self.rttvar
84 + constants::RTTVAR_BETA * (self.srtt - sample_ms).abs();
85 self.srtt =
87 (1.0 - constants::SRTT_ALPHA) * self.srtt + constants::SRTT_ALPHA * sample_ms;
88 }
89
90 let rto_ms =
93 self.srtt + f64::max(constants::MIN_RTO_GRANULARITY_MS, constants::RTO_K * self.rttvar);
94
95 let rto_ms = rto_ms.clamp(
97 constants::MIN_RTO.as_millis() as f64,
98 constants::MAX_RTO.as_millis() as f64,
99 );
100
101 self.rto = Duration::from_millis(rto_ms as u64);
102 }
103
104 pub fn srtt(&self) -> Duration {
106 Duration::from_secs_f64(self.srtt / 1000.0)
107 }
108
109 pub fn srtt_ms(&self) -> f64 {
111 self.srtt
112 }
113
114 pub fn rttvar(&self) -> Duration {
116 Duration::from_secs_f64(self.rttvar / 1000.0)
117 }
118
119 pub fn rto(&self) -> Duration {
121 self.rto
122 }
123
124 pub fn is_initialized(&self) -> bool {
126 self.initialized
127 }
128
129 pub fn backoff(&mut self) -> Duration {
133 let new_rto_ms = (self.rto.as_millis() as u64).saturating_mul(2);
134 self.rto = Duration::from_millis(new_rto_ms).min(constants::MAX_RTO);
135 self.rto
136 }
137
138 pub fn reset_backoff(&mut self) {
140 if self.initialized {
141 let rto_ms = self.srtt
143 + f64::max(constants::MIN_RTO_GRANULARITY_MS, constants::RTO_K * self.rttvar);
144 let rto_ms = rto_ms.clamp(
145 constants::MIN_RTO.as_millis() as f64,
146 constants::MAX_RTO.as_millis() as f64,
147 );
148 self.rto = Duration::from_millis(rto_ms as u64);
149 } else {
150 self.rto = constants::INITIAL_RTO;
151 }
152 }
153}
154
155#[derive(Debug, Clone)]
160pub struct TimestampTracker {
161 session_start: Instant,
163 last_peer_timestamp: u32,
165 pending_timestamp: Option<u32>,
167 pending_send_time: Option<Instant>,
169}
170
171impl TimestampTracker {
172 pub fn new() -> Self {
174 Self {
175 session_start: Instant::now(),
176 last_peer_timestamp: 0,
177 pending_timestamp: None,
178 pending_send_time: None,
179 }
180 }
181
182 pub fn with_start(start: Instant) -> Self {
184 Self {
185 session_start: start,
186 last_peer_timestamp: 0,
187 pending_timestamp: None,
188 pending_send_time: None,
189 }
190 }
191
192 pub fn now(&self) -> u32 {
194 self.session_start.elapsed().as_millis() as u32
195 }
196
197 pub fn timestamp_echo(&self) -> u32 {
199 self.last_peer_timestamp
200 }
201
202 pub fn on_send(&mut self, timestamp: u32) {
204 self.pending_timestamp = Some(timestamp);
205 self.pending_send_time = Some(Instant::now());
206 }
207
208 pub fn on_receive(&mut self, peer_timestamp: u32, echo: u32) -> Option<Duration> {
212 self.last_peer_timestamp = peer_timestamp;
214
215 if let (Some(pending), Some(send_time)) = (self.pending_timestamp, self.pending_send_time)
217 && echo == pending
218 {
219 let rtt = send_time.elapsed();
220 self.pending_timestamp = None;
221 self.pending_send_time = None;
222 return Some(rtt);
223 }
224
225 None
226 }
227
228 pub fn clear_pending(&mut self) {
230 self.pending_timestamp = None;
231 self.pending_send_time = None;
232 }
233}
234
235impl Default for TimestampTracker {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn test_rtt_estimator_initial() {
247 let estimator = RttEstimator::new();
248 assert!(!estimator.is_initialized());
249 assert_eq!(estimator.rto(), constants::INITIAL_RTO);
250 }
251
252 #[test]
253 fn test_rtt_estimator_first_sample() {
254 let mut estimator = RttEstimator::new();
255 estimator.update(Duration::from_millis(100));
256
257 assert!(estimator.is_initialized());
258 assert!((estimator.srtt_ms() - 100.0).abs() < 0.01);
259 assert!((estimator.rttvar - 50.0).abs() < 0.01); }
261
262 #[test]
263 fn test_rtt_estimator_multiple_samples() {
264 let mut estimator = RttEstimator::new();
265
266 estimator.update(Duration::from_millis(100));
268 let srtt1 = estimator.srtt_ms();
269
270 estimator.update(Duration::from_millis(120));
272 let srtt2 = estimator.srtt_ms();
273
274 assert!(srtt2 > srtt1);
276 assert!(srtt2 < 120.0);
277 }
278
279 #[test]
280 fn test_rtt_estimator_backoff() {
281 let mut estimator = RttEstimator::new();
282 estimator.update(Duration::from_millis(100));
283
284 let rto1 = estimator.rto();
285 let rto2 = estimator.backoff();
286
287 assert!(rto2 > rto1);
289 assert!(rto2 <= constants::MAX_RTO);
290 }
291
292 #[test]
293 fn test_rtt_estimator_max_rto() {
294 let mut estimator = RttEstimator::new();
295 estimator.update(Duration::from_millis(100));
296
297 for _ in 0..20 {
299 estimator.backoff();
300 }
301
302 assert_eq!(estimator.rto(), constants::MAX_RTO);
303 }
304
305 #[test]
306 fn test_rtt_estimator_min_rto() {
307 let mut estimator = RttEstimator::new();
308
309 estimator.update(Duration::from_micros(100));
311
312 assert!(estimator.rto() >= constants::MIN_RTO);
314 }
315
316 #[test]
317 fn test_timestamp_tracker_echo() {
318 let start = Instant::now();
319 let mut tracker = TimestampTracker::with_start(start);
320
321 tracker.on_send(1000);
323
324 std::thread::sleep(Duration::from_millis(10));
326 let rtt = tracker.on_receive(2000, 1000);
327
328 assert!(rtt.is_some());
329 let rtt = rtt.unwrap();
330 assert!(rtt >= Duration::from_millis(10));
331 }
332
333 #[test]
334 fn test_timestamp_tracker_no_match() {
335 let start = Instant::now();
336 let mut tracker = TimestampTracker::with_start(start);
337
338 tracker.on_send(1000);
340
341 let rtt = tracker.on_receive(2000, 999);
343
344 assert!(rtt.is_none());
345 assert!(tracker.pending_timestamp.is_some());
347 }
348
349 #[test]
350 fn test_timestamp_tracker_peer_timestamp() {
351 let mut tracker = TimestampTracker::new();
352
353 assert_eq!(tracker.timestamp_echo(), 0);
355
356 tracker.on_receive(5000, 0);
358
359 assert_eq!(tracker.timestamp_echo(), 5000);
361 }
362}