1use std::time::Duration;
20
21pub const MRP_MAX_TRANSMISSIONS: u32 = 5;
23pub const MRP_BACKOFF_MARGIN: f64 = 1.1;
24pub const MRP_BACKOFF_BASE: f64 = 1.6;
25pub const MRP_BACKOFF_THRESHOLD: u32 = 1;
26pub const MRP_BACKOFF_JITTER: f64 = 0.25;
27pub const MRP_MAX_INTERVAL_MS: u32 = 3_600_000;
29
30const DEFAULT_IDLE_INTERVAL_MS: u32 = 500;
31const DEFAULT_ACTIVE_INTERVAL_MS: u32 = 300;
32const DEFAULT_ACTIVE_THRESHOLD_MS: u32 = 4000;
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct MrpParameters {
37 pub session_idle_interval: Duration,
39 pub session_active_interval: Duration,
41 pub session_active_threshold: Duration,
43}
44
45impl Default for MrpParameters {
46 fn default() -> Self {
47 Self {
48 session_idle_interval: Duration::from_millis(DEFAULT_IDLE_INTERVAL_MS as u64),
49 session_active_interval: Duration::from_millis(DEFAULT_ACTIVE_INTERVAL_MS as u64),
50 session_active_threshold: Duration::from_millis(DEFAULT_ACTIVE_THRESHOLD_MS as u64),
51 }
52 }
53}
54
55impl MrpParameters {
56 pub fn from_txt_ms(sii: Option<u32>, sai: Option<u32>, sat: Option<u32>) -> Self {
60 let clamp = |v: u32| v.min(MRP_MAX_INTERVAL_MS) as u64;
61 Self {
62 session_idle_interval: Duration::from_millis(clamp(
63 sii.unwrap_or(DEFAULT_IDLE_INTERVAL_MS),
64 )),
65 session_active_interval: Duration::from_millis(clamp(
66 sai.unwrap_or(DEFAULT_ACTIVE_INTERVAL_MS),
67 )),
68 session_active_threshold: Duration::from_millis(
69 sat.unwrap_or(DEFAULT_ACTIVE_THRESHOLD_MS) as u64,
70 ),
71 }
72 }
73}
74
75pub fn base_interval(params: &MrpParameters, last_rx_elapsed: Option<Duration>) -> Duration {
78 match last_rx_elapsed {
79 Some(elapsed) if elapsed < params.session_active_threshold => {
80 params.session_active_interval
81 }
82 _ => params.session_idle_interval,
83 }
84}
85
86pub fn backoff_interval(base: Duration, retransmission_index: u32) -> Duration {
89 let exponent = retransmission_index.saturating_sub(MRP_BACKOFF_THRESHOLD);
90 let t = base.as_secs_f64()
91 * MRP_BACKOFF_MARGIN
92 * MRP_BACKOFF_BASE.powi(exponent as i32)
93 * (1.0 + rand::random::<f64>() * MRP_BACKOFF_JITTER);
94 Duration::from_secs_f64(t)
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_defaults() {
103 let p = MrpParameters::default();
104 assert_eq!(p.session_idle_interval, Duration::from_millis(500));
105 assert_eq!(p.session_active_interval, Duration::from_millis(300));
106 assert_eq!(p.session_active_threshold, Duration::from_millis(4000));
107 assert_eq!(p, MrpParameters::from_txt_ms(None, None, None));
108 }
109
110 #[test]
111 fn test_from_txt_ms_clamps() {
112 let p = MrpParameters::from_txt_ms(Some(4_000_000), Some(300), Some(4000));
113 assert_eq!(
114 p.session_idle_interval,
115 Duration::from_millis(MRP_MAX_INTERVAL_MS as u64)
116 );
117 let p = MrpParameters::from_txt_ms(Some(5000), None, None);
118 assert_eq!(p.session_idle_interval, Duration::from_millis(5000));
119 assert_eq!(p.session_active_interval, Duration::from_millis(300));
120 }
121
122 #[test]
123 fn test_base_interval_selection() {
124 let p = MrpParameters::default();
125 assert_eq!(base_interval(&p, None), p.session_idle_interval);
126 assert_eq!(
127 base_interval(&p, Some(Duration::from_millis(1000))),
128 p.session_active_interval
129 );
130 assert_eq!(
131 base_interval(&p, Some(Duration::from_millis(4000))),
132 p.session_idle_interval
133 );
134 }
135
136 #[test]
137 fn test_backoff_interval_bounds() {
138 let base = Duration::from_millis(500);
139 let mut prev_lower = 0.0f64;
140 for n in 0..MRP_MAX_TRANSMISSIONS {
141 let exponent = n.saturating_sub(MRP_BACKOFF_THRESHOLD);
142 let lower = 0.5 * MRP_BACKOFF_MARGIN * MRP_BACKOFF_BASE.powi(exponent as i32);
143 let upper = lower * (1.0 + MRP_BACKOFF_JITTER);
144 for _ in 0..50 {
145 let t = backoff_interval(base, n).as_secs_f64();
146 assert!(t >= lower - 1e-9, "n={} t={} lower={}", n, t, lower);
147 assert!(t <= upper + 1e-9, "n={} t={} upper={}", n, t, upper);
148 }
149 assert!(lower >= prev_lower);
150 prev_lower = lower;
151 }
152 }
153}