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 JITTER_ALPHA_SHIFT: u32 = 4;
56
57pub const SRTT_ALPHA_SHIFT: u32 = 3;
59
60pub const RTTVAR_BETA_SHIFT: u32 = 2;
62
63pub const EWMA_SHORT_ALPHA: f64 = 0.25;
65
66pub const EWMA_LONG_ALPHA: f64 = 1.0 / 32.0;
68
69pub const DEFAULT_COLD_START_INTERVAL_MS: u64 = 200;
73
74pub const MIN_REPORT_INTERVAL_MS: u64 = 1_000;
81
82pub const MAX_REPORT_INTERVAL_MS: u64 = 5_000;
84
85pub const COLD_START_SAMPLES: u32 = 5;
91
92pub const DEFAULT_OWD_WINDOW_SIZE: usize = 32;
94
95pub const DEFAULT_LOG_INTERVAL_SECS: u64 = 30;
97
98pub const MIN_SESSION_REPORT_INTERVAL_MS: u64 = 500;
104
105pub const MAX_SESSION_REPORT_INTERVAL_MS: u64 = 10_000;
107
108pub const SESSION_COLD_START_INTERVAL_MS: u64 = 1_000;
110
111#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(rename_all = "lowercase")]
118pub enum MmpMode {
119 #[default]
121 Full,
122 Lightweight,
124 Minimal,
126}
127
128impl fmt::Display for MmpMode {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 match self {
131 MmpMode::Full => write!(f, "full"),
132 MmpMode::Lightweight => write!(f, "lightweight"),
133 MmpMode::Minimal => write!(f, "minimal"),
134 }
135 }
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct MmpConfig {
145 #[serde(default)]
147 pub mode: MmpMode,
148
149 #[serde(default = "MmpConfig::default_log_interval_secs")]
151 pub log_interval_secs: u64,
152
153 #[serde(default = "MmpConfig::default_owd_window_size")]
155 pub owd_window_size: usize,
156}
157
158impl Default for MmpConfig {
159 fn default() -> Self {
160 Self {
161 mode: MmpMode::default(),
162 log_interval_secs: DEFAULT_LOG_INTERVAL_SECS,
163 owd_window_size: DEFAULT_OWD_WINDOW_SIZE,
164 }
165 }
166}
167
168impl MmpConfig {
169 fn default_log_interval_secs() -> u64 {
170 DEFAULT_LOG_INTERVAL_SECS
171 }
172 fn default_owd_window_size() -> usize {
173 DEFAULT_OWD_WINDOW_SIZE
174 }
175}
176
177pub struct MmpPeerState {
186 pub sender: SenderState,
187 pub receiver: ReceiverState,
188 pub metrics: MmpMetrics,
189 pub spin_bit: SpinBitState,
190 mode: MmpMode,
191 log_interval: Duration,
192 last_log_time: Option<Instant>,
193}
194
195impl MmpPeerState {
196 pub fn new(config: &MmpConfig, is_initiator: bool) -> Self {
201 Self {
202 sender: SenderState::new(),
203 receiver: ReceiverState::new(config.owd_window_size),
204 metrics: MmpMetrics::new(),
205 spin_bit: SpinBitState::new(is_initiator),
206 mode: config.mode,
207 log_interval: Duration::from_secs(config.log_interval_secs),
208 last_log_time: None,
209 }
210 }
211
212 pub fn reset_for_rekey(&mut self, now: Instant) {
214 self.receiver.reset_for_rekey(now);
215 self.metrics.reset_for_rekey();
216 }
217
218 pub fn mode(&self) -> MmpMode {
220 self.mode
221 }
222
223 pub fn should_log(&self, now: Instant) -> bool {
225 match self.last_log_time {
226 None => true,
227 Some(last) => now.duration_since(last) >= self.log_interval,
228 }
229 }
230
231 pub fn mark_logged(&mut self, now: Instant) {
233 self.last_log_time = Some(now);
234 }
235}
236
237pub struct MmpSessionState {
246 pub sender: SenderState,
247 pub receiver: ReceiverState,
248 pub metrics: MmpMetrics,
249 pub spin_bit: SpinBitState,
250 mode: MmpMode,
251 log_interval: Duration,
252 last_log_time: Option<Instant>,
253 pub path_mtu: PathMtuState,
254}
255
256impl MmpSessionState {
257 pub fn new(config: &crate::config::SessionMmpConfig, is_initiator: bool) -> Self {
262 Self {
263 sender: SenderState::new_with_cold_start(SESSION_COLD_START_INTERVAL_MS),
264 receiver: ReceiverState::new_with_cold_start(
265 config.owd_window_size,
266 SESSION_COLD_START_INTERVAL_MS,
267 ),
268 metrics: MmpMetrics::new(),
269 spin_bit: SpinBitState::new(is_initiator),
270 mode: config.mode,
271 log_interval: Duration::from_secs(config.log_interval_secs),
272 last_log_time: None,
273 path_mtu: PathMtuState::new(),
274 }
275 }
276
277 pub fn reset_for_rekey(&mut self, now: Instant) {
279 self.receiver.reset_for_rekey(now);
280 self.metrics.reset_for_rekey();
281 }
282
283 pub fn mode(&self) -> MmpMode {
285 self.mode
286 }
287
288 pub fn should_log(&self, now: Instant) -> bool {
290 match self.last_log_time {
291 None => true,
292 Some(last) => now.duration_since(last) >= self.log_interval,
293 }
294 }
295
296 pub fn mark_logged(&mut self, now: Instant) {
298 self.last_log_time = Some(now);
299 }
300}
301
302impl Debug for MmpSessionState {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 f.debug_struct("MmpSessionState")
305 .field("mode", &self.mode)
306 .field("path_mtu", &self.path_mtu.current_mtu())
307 .finish_non_exhaustive()
308 }
309}
310
311pub struct PathMtuState {
323 current_mtu: u16,
325 last_observed_mtu: u16,
327 observed_changed: bool,
329 last_notification_time: Option<Instant>,
331 notification_interval: Duration,
333 consecutive_increase_count: u8,
335 first_increase_time: Option<Instant>,
337 pending_increase_mtu: u16,
339}
340
341impl PathMtuState {
342 pub fn new() -> Self {
344 Self {
345 current_mtu: u16::MAX,
346 last_observed_mtu: u16::MAX,
347 observed_changed: false,
348 last_notification_time: None,
349 notification_interval: Duration::from_secs(10),
350 consecutive_increase_count: 0,
351 first_increase_time: None,
352 pending_increase_mtu: 0,
353 }
354 }
355
356 pub fn current_mtu(&self) -> u16 {
358 self.current_mtu
359 }
360
361 pub fn last_observed_mtu(&self) -> u16 {
363 self.last_observed_mtu
364 }
365
366 pub fn update_interval_from_srtt(&mut self, srtt_ms: f64) {
368 let five_srtt = Duration::from_millis((srtt_ms * 5.0) as u64);
369 self.notification_interval = five_srtt.max(Duration::from_secs(10));
370 }
371
372 pub fn seed_source_mtu(&mut self, outbound_mtu: u16) {
379 if outbound_mtu < self.current_mtu {
380 self.current_mtu = outbound_mtu;
381 }
382 }
383
384 pub fn observe_incoming_mtu(&mut self, path_mtu: u16) {
390 if path_mtu != self.last_observed_mtu {
391 self.observed_changed = true;
392 self.last_observed_mtu = path_mtu;
393 }
394 }
395
396 pub fn should_send_notification(&self, now: Instant) -> bool {
401 if self.last_observed_mtu == u16::MAX {
402 return false; }
404 match self.last_notification_time {
405 None => true, Some(last) => {
407 if self.observed_changed && self.last_observed_mtu < self.current_mtu {
409 return true;
410 }
411 now.duration_since(last) >= self.notification_interval
413 }
414 }
415 }
416
417 pub fn build_notification(&mut self, now: Instant) -> Option<u16> {
421 if self.last_observed_mtu == u16::MAX {
422 return None;
423 }
424 self.last_notification_time = Some(now);
425 self.observed_changed = false;
426 Some(self.last_observed_mtu)
427 }
428
429 pub fn apply_notification(&mut self, reported_mtu: u16, now: Instant) -> bool {
439 if reported_mtu < self.current_mtu {
440 self.current_mtu = reported_mtu;
442 self.consecutive_increase_count = 0;
443 self.first_increase_time = None;
444 return true;
445 }
446
447 if reported_mtu > self.current_mtu {
448 if reported_mtu == self.pending_increase_mtu {
450 self.consecutive_increase_count += 1;
451 } else {
452 self.pending_increase_mtu = reported_mtu;
454 self.consecutive_increase_count = 1;
455 self.first_increase_time = Some(now);
456 }
457
458 if self.consecutive_increase_count >= 3
460 && let Some(first_time) = self.first_increase_time
461 {
462 let required = self.notification_interval * 2;
463 if now.duration_since(first_time) >= required {
464 self.current_mtu = reported_mtu;
465 self.consecutive_increase_count = 0;
466 self.first_increase_time = None;
467 return true;
468 }
469 }
470 }
471
472 false
474 }
475}
476
477impl Default for PathMtuState {
478 fn default() -> Self {
479 Self::new()
480 }
481}
482
483impl Debug for MmpPeerState {
484 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
485 f.debug_struct("MmpPeerState")
486 .field("mode", &self.mode)
487 .finish_non_exhaustive()
488 }
489}
490
491#[cfg(test)]
496mod tests {
497 use super::*;
498
499 #[test]
500 fn test_mode_default() {
501 assert_eq!(MmpMode::default(), MmpMode::Full);
502 }
503
504 #[test]
505 fn test_mode_display() {
506 assert_eq!(MmpMode::Full.to_string(), "full");
507 assert_eq!(MmpMode::Lightweight.to_string(), "lightweight");
508 assert_eq!(MmpMode::Minimal.to_string(), "minimal");
509 }
510
511 #[test]
512 fn test_mode_serde_roundtrip() {
513 let yaml = "full";
514 let mode: MmpMode = serde_yaml::from_str(yaml).unwrap();
515 assert_eq!(mode, MmpMode::Full);
516
517 let yaml = "lightweight";
518 let mode: MmpMode = serde_yaml::from_str(yaml).unwrap();
519 assert_eq!(mode, MmpMode::Lightweight);
520
521 let yaml = "minimal";
522 let mode: MmpMode = serde_yaml::from_str(yaml).unwrap();
523 assert_eq!(mode, MmpMode::Minimal);
524 }
525
526 #[test]
527 fn test_config_default() {
528 let config = MmpConfig::default();
529 assert_eq!(config.mode, MmpMode::Full);
530 assert_eq!(config.log_interval_secs, 30);
531 assert_eq!(config.owd_window_size, 32);
532 }
533
534 #[test]
535 fn test_config_yaml_parse() {
536 let yaml = r#"
537mode: lightweight
538log_interval_secs: 60
539owd_window_size: 48
540"#;
541 let config: MmpConfig = serde_yaml::from_str(yaml).unwrap();
542 assert_eq!(config.mode, MmpMode::Lightweight);
543 assert_eq!(config.log_interval_secs, 60);
544 assert_eq!(config.owd_window_size, 48);
545 }
546
547 #[test]
548 fn test_config_yaml_partial() {
549 let yaml = "mode: minimal";
550 let config: MmpConfig = serde_yaml::from_str(yaml).unwrap();
551 assert_eq!(config.mode, MmpMode::Minimal);
552 assert_eq!(config.log_interval_secs, DEFAULT_LOG_INTERVAL_SECS);
553 assert_eq!(config.owd_window_size, DEFAULT_OWD_WINDOW_SIZE);
554 }
555}