1use serde::{Deserialize, Serialize};
14use std::fmt::{self, Debug};
15use std::time::{Duration, Instant};
16
17pub mod algorithms;
19pub mod metrics;
20pub mod receiver;
21pub mod report;
22pub mod sender;
23
24pub use algorithms::{
26 DualEwma, JitterEstimator, OwdTrendDetector, SpinBitState, SrttEstimator, compute_etx,
27};
28pub use metrics::MmpMetrics;
29pub use receiver::ReceiverState;
30pub use report::{ReceiverReport, SenderReport};
31pub use sender::SenderState;
32
33pub const SENDER_REPORT_BODY_SIZE: usize = 47;
42
43pub const RECEIVER_REPORT_BODY_SIZE: usize = 67;
45
46pub const SENDER_REPORT_WIRE_SIZE: usize = 52;
48
49pub const RECEIVER_REPORT_WIRE_SIZE: usize = 72;
51
52pub const MIN_ACTIONABLE_PATH_MTU: u16 = 256;
54
55pub const JITTER_ALPHA_SHIFT: u32 = 4;
59
60pub const SRTT_ALPHA_SHIFT: u32 = 3;
62
63pub const RTTVAR_BETA_SHIFT: u32 = 2;
65
66pub const EWMA_SHORT_ALPHA: f64 = 0.25;
68
69pub const EWMA_LONG_ALPHA: f64 = 1.0 / 32.0;
71
72pub const DEFAULT_COLD_START_INTERVAL_MS: u64 = 200;
76
77pub const MIN_REPORT_INTERVAL_MS: u64 = 1_000;
84
85pub const MAX_REPORT_INTERVAL_MS: u64 = 5_000;
87
88pub const COLD_START_SAMPLES: u32 = 5;
94
95pub const DEFAULT_OWD_WINDOW_SIZE: usize = 32;
97
98pub const DEFAULT_LOG_INTERVAL_SECS: u64 = 30;
100
101pub const MIN_SESSION_REPORT_INTERVAL_MS: u64 = 500;
107
108pub const MAX_SESSION_REPORT_INTERVAL_MS: u64 = 10_000;
110
111pub const SESSION_COLD_START_INTERVAL_MS: u64 = 1_000;
113
114#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
120#[serde(rename_all = "lowercase")]
121pub enum MmpMode {
122 #[default]
124 Full,
125 Lightweight,
127 Minimal,
129}
130
131impl fmt::Display for MmpMode {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 match self {
134 MmpMode::Full => write!(f, "full"),
135 MmpMode::Lightweight => write!(f, "lightweight"),
136 MmpMode::Minimal => write!(f, "minimal"),
137 }
138 }
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct MmpConfig {
148 #[serde(default)]
150 pub mode: MmpMode,
151
152 #[serde(default = "MmpConfig::default_log_interval_secs")]
154 pub log_interval_secs: u64,
155
156 #[serde(default = "MmpConfig::default_owd_window_size")]
158 pub owd_window_size: usize,
159}
160
161impl Default for MmpConfig {
162 fn default() -> Self {
163 Self {
164 mode: MmpMode::default(),
165 log_interval_secs: DEFAULT_LOG_INTERVAL_SECS,
166 owd_window_size: DEFAULT_OWD_WINDOW_SIZE,
167 }
168 }
169}
170
171impl MmpConfig {
172 fn default_log_interval_secs() -> u64 {
173 DEFAULT_LOG_INTERVAL_SECS
174 }
175 fn default_owd_window_size() -> usize {
176 DEFAULT_OWD_WINDOW_SIZE
177 }
178}
179
180pub struct MmpPeerState {
189 pub sender: SenderState,
190 pub receiver: ReceiverState,
191 pub metrics: MmpMetrics,
192 pub spin_bit: SpinBitState,
193 mode: MmpMode,
194 log_interval: Duration,
195 last_log_time: Option<Instant>,
196}
197
198impl MmpPeerState {
199 pub fn new(config: &MmpConfig, is_initiator: bool) -> Self {
204 Self {
205 sender: SenderState::new(),
206 receiver: ReceiverState::new(config.owd_window_size),
207 metrics: MmpMetrics::new(),
208 spin_bit: SpinBitState::new(is_initiator),
209 mode: config.mode,
210 log_interval: Duration::from_secs(config.log_interval_secs),
211 last_log_time: None,
212 }
213 }
214
215 pub fn reset_for_rekey(&mut self, now: Instant) {
217 self.receiver.reset_for_rekey(now);
218 self.metrics.reset_for_rekey();
219 }
220
221 pub fn mode(&self) -> MmpMode {
223 self.mode
224 }
225
226 pub fn should_log(&self, now: Instant) -> bool {
228 match self.last_log_time {
229 None => true,
230 Some(last) => now.duration_since(last) >= self.log_interval,
231 }
232 }
233
234 pub fn mark_logged(&mut self, now: Instant) {
236 self.last_log_time = Some(now);
237 }
238}
239
240pub struct MmpSessionState {
249 pub sender: SenderState,
250 pub receiver: ReceiverState,
251 pub metrics: MmpMetrics,
252 pub spin_bit: SpinBitState,
253 mode: MmpMode,
254 log_interval: Duration,
255 last_log_time: Option<Instant>,
256 pub path_mtu: PathMtuState,
257}
258
259impl MmpSessionState {
260 pub fn new(config: &crate::config::SessionMmpConfig, is_initiator: bool) -> Self {
265 Self {
266 sender: SenderState::new_with_cold_start(SESSION_COLD_START_INTERVAL_MS),
267 receiver: ReceiverState::new_with_cold_start(
268 config.owd_window_size,
269 SESSION_COLD_START_INTERVAL_MS,
270 ),
271 metrics: MmpMetrics::new(),
272 spin_bit: SpinBitState::new(is_initiator),
273 mode: config.mode,
274 log_interval: Duration::from_secs(config.log_interval_secs),
275 last_log_time: None,
276 path_mtu: PathMtuState::new(),
277 }
278 }
279
280 pub fn reset_for_rekey(&mut self, now: Instant) {
282 self.receiver.reset_for_rekey(now);
283 self.metrics.reset_for_rekey();
284 }
285
286 pub fn mode(&self) -> MmpMode {
288 self.mode
289 }
290
291 pub fn should_log(&self, now: Instant) -> bool {
293 match self.last_log_time {
294 None => true,
295 Some(last) => now.duration_since(last) >= self.log_interval,
296 }
297 }
298
299 pub fn mark_logged(&mut self, now: Instant) {
301 self.last_log_time = Some(now);
302 }
303}
304
305impl Debug for MmpSessionState {
306 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307 f.debug_struct("MmpSessionState")
308 .field("mode", &self.mode)
309 .field("path_mtu", &self.path_mtu.current_mtu())
310 .finish_non_exhaustive()
311 }
312}
313
314pub struct PathMtuState {
326 current_mtu: u16,
328 last_observed_mtu: u16,
330 observed_changed: bool,
332 last_notification_time: Option<Instant>,
334 notification_interval: Duration,
336 consecutive_increase_count: u8,
338 first_increase_time: Option<Instant>,
340 pending_increase_mtu: u16,
342}
343
344impl PathMtuState {
345 pub fn new() -> Self {
347 Self {
348 current_mtu: u16::MAX,
349 last_observed_mtu: u16::MAX,
350 observed_changed: false,
351 last_notification_time: None,
352 notification_interval: Duration::from_secs(10),
353 consecutive_increase_count: 0,
354 first_increase_time: None,
355 pending_increase_mtu: 0,
356 }
357 }
358
359 pub fn current_mtu(&self) -> u16 {
361 self.current_mtu
362 }
363
364 pub fn last_observed_mtu(&self) -> u16 {
366 self.last_observed_mtu
367 }
368
369 pub fn update_interval_from_srtt(&mut self, srtt_ms: f64) {
371 let five_srtt = Duration::from_millis((srtt_ms * 5.0) as u64);
372 self.notification_interval = five_srtt.max(Duration::from_secs(10));
373 }
374
375 pub fn seed_source_mtu(&mut self, outbound_mtu: u16) {
382 if outbound_mtu < self.current_mtu {
383 self.current_mtu = outbound_mtu;
384 }
385 }
386
387 pub fn observe_incoming_mtu(&mut self, path_mtu: u16) {
393 if path_mtu != self.last_observed_mtu {
394 self.observed_changed = true;
395 self.last_observed_mtu = path_mtu;
396 }
397 }
398
399 pub fn should_send_notification(&self, now: Instant) -> bool {
404 if self.last_observed_mtu == u16::MAX {
405 return false; }
407 match self.last_notification_time {
408 None => true, Some(last) => {
410 if self.observed_changed && self.last_observed_mtu < self.current_mtu {
412 return true;
413 }
414 now.duration_since(last) >= self.notification_interval
416 }
417 }
418 }
419
420 pub fn build_notification(&mut self, now: Instant) -> Option<u16> {
424 if self.last_observed_mtu == u16::MAX {
425 return None;
426 }
427 self.last_notification_time = Some(now);
428 self.observed_changed = false;
429 Some(self.last_observed_mtu)
430 }
431
432 pub fn apply_notification(&mut self, reported_mtu: u16, now: Instant) -> bool {
442 if reported_mtu < MIN_ACTIONABLE_PATH_MTU {
443 return false;
444 }
445 if reported_mtu < self.current_mtu {
446 self.current_mtu = reported_mtu;
448 self.consecutive_increase_count = 0;
449 self.first_increase_time = None;
450 return true;
451 }
452
453 if reported_mtu > self.current_mtu {
454 if reported_mtu == self.pending_increase_mtu {
456 self.consecutive_increase_count += 1;
457 } else {
458 self.pending_increase_mtu = reported_mtu;
460 self.consecutive_increase_count = 1;
461 self.first_increase_time = Some(now);
462 }
463
464 if self.consecutive_increase_count >= 3
466 && let Some(first_time) = self.first_increase_time
467 {
468 let required = self.notification_interval * 2;
469 if now.duration_since(first_time) >= required {
470 self.current_mtu = reported_mtu;
471 self.consecutive_increase_count = 0;
472 self.first_increase_time = None;
473 return true;
474 }
475 }
476 }
477
478 false
480 }
481}
482
483impl Default for PathMtuState {
484 fn default() -> Self {
485 Self::new()
486 }
487}
488
489impl Debug for MmpPeerState {
490 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
491 f.debug_struct("MmpPeerState")
492 .field("mode", &self.mode)
493 .finish_non_exhaustive()
494 }
495}
496
497#[cfg(test)]
502mod tests {
503 use super::*;
504
505 #[test]
506 fn test_mode_default() {
507 assert_eq!(MmpMode::default(), MmpMode::Full);
508 }
509
510 #[test]
511 fn test_mode_display() {
512 assert_eq!(MmpMode::Full.to_string(), "full");
513 assert_eq!(MmpMode::Lightweight.to_string(), "lightweight");
514 assert_eq!(MmpMode::Minimal.to_string(), "minimal");
515 }
516
517 #[test]
518 fn test_mode_serde_roundtrip() {
519 let yaml = "full";
520 let mode: MmpMode = serde_yaml::from_str(yaml).unwrap();
521 assert_eq!(mode, MmpMode::Full);
522
523 let yaml = "lightweight";
524 let mode: MmpMode = serde_yaml::from_str(yaml).unwrap();
525 assert_eq!(mode, MmpMode::Lightweight);
526
527 let yaml = "minimal";
528 let mode: MmpMode = serde_yaml::from_str(yaml).unwrap();
529 assert_eq!(mode, MmpMode::Minimal);
530 }
531
532 #[test]
533 fn test_config_default() {
534 let config = MmpConfig::default();
535 assert_eq!(config.mode, MmpMode::Full);
536 assert_eq!(config.log_interval_secs, 30);
537 assert_eq!(config.owd_window_size, 32);
538 }
539
540 #[test]
541 fn test_config_yaml_parse() {
542 let yaml = r#"
543mode: lightweight
544log_interval_secs: 60
545owd_window_size: 48
546"#;
547 let config: MmpConfig = serde_yaml::from_str(yaml).unwrap();
548 assert_eq!(config.mode, MmpMode::Lightweight);
549 assert_eq!(config.log_interval_secs, 60);
550 assert_eq!(config.owd_window_size, 48);
551 }
552
553 #[test]
554 fn test_config_yaml_partial() {
555 let yaml = "mode: minimal";
556 let config: MmpConfig = serde_yaml::from_str(yaml).unwrap();
557 assert_eq!(config.mode, MmpMode::Minimal);
558 assert_eq!(config.log_interval_secs, DEFAULT_LOG_INTERVAL_SECS);
559 assert_eq!(config.owd_window_size, DEFAULT_OWD_WINDOW_SIZE);
560 }
561}