mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-22 07:48:26 +00:00
Tune MMP link-layer report intervals for constrained transports
Raise the report interval floor from 100ms to 1000ms and ceiling from 2000ms to 5000ms. The old 100ms floor produced ~600 reports per 60s parent evaluation cycle — far more than the ~10 needed for EWMA convergence. The new floor yields ~60 reports/cycle, still well above the convergence threshold, while reducing BLE overhead by 10×. Add cold-start transition: first 5 SRTT samples use the 200ms floor for fast initial convergence, then switch to the 1000ms steady-state floor. Session-layer intervals unchanged (500ms–10000ms).
This commit is contained in:
@@ -72,10 +72,22 @@ pub const EWMA_LONG_ALPHA: f64 = 1.0 / 32.0;
|
||||
pub const DEFAULT_COLD_START_INTERVAL_MS: u64 = 200;
|
||||
|
||||
/// Minimum report interval (SRTT clamp floor).
|
||||
pub const MIN_REPORT_INTERVAL_MS: u64 = 100;
|
||||
///
|
||||
/// Raised from 100ms to 1000ms: parent re-evaluation runs every 60s,
|
||||
/// so 60 samples/cycle is more than sufficient for EWMA convergence (~10).
|
||||
/// The cold-start phase uses `DEFAULT_COLD_START_INTERVAL_MS` (200ms) for
|
||||
/// fast initial SRTT convergence before transitioning to this floor.
|
||||
pub const MIN_REPORT_INTERVAL_MS: u64 = 1_000;
|
||||
|
||||
/// Maximum report interval (SRTT clamp ceiling).
|
||||
pub const MAX_REPORT_INTERVAL_MS: u64 = 2_000;
|
||||
pub const MAX_REPORT_INTERVAL_MS: u64 = 5_000;
|
||||
|
||||
/// Number of SRTT samples before transitioning from cold-start to normal floor.
|
||||
///
|
||||
/// During cold-start, report intervals use `DEFAULT_COLD_START_INTERVAL_MS` as
|
||||
/// the floor to gather SRTT samples quickly. After this many updates, the floor
|
||||
/// switches to `MIN_REPORT_INTERVAL_MS`.
|
||||
pub const COLD_START_SAMPLES: u32 = 5;
|
||||
|
||||
/// Default OWD ring buffer capacity.
|
||||
pub const DEFAULT_OWD_WINDOW_SIZE: usize = 32;
|
||||
|
||||
@@ -7,8 +7,9 @@ use std::time::{Duration, Instant};
|
||||
|
||||
use crate::mmp::algorithms::{JitterEstimator, OwdTrendDetector};
|
||||
use crate::mmp::report::ReceiverReport;
|
||||
use crate::mmp::{DEFAULT_COLD_START_INTERVAL_MS, DEFAULT_OWD_WINDOW_SIZE,
|
||||
MAX_REPORT_INTERVAL_MS, MIN_REPORT_INTERVAL_MS};
|
||||
use crate::mmp::{COLD_START_SAMPLES, DEFAULT_COLD_START_INTERVAL_MS,
|
||||
DEFAULT_OWD_WINDOW_SIZE, MAX_REPORT_INTERVAL_MS,
|
||||
MIN_REPORT_INTERVAL_MS};
|
||||
|
||||
/// Grace period after rekey before resuming jitter calculation.
|
||||
///
|
||||
@@ -179,6 +180,10 @@ pub struct ReceiverState {
|
||||
report_interval: Duration,
|
||||
/// Whether any frames have been received since the last report.
|
||||
interval_has_data: bool,
|
||||
|
||||
// --- Cold-start tracking ---
|
||||
/// Number of SRTT-based interval updates received.
|
||||
srtt_sample_count: u32,
|
||||
}
|
||||
|
||||
impl ReceiverState {
|
||||
@@ -209,6 +214,7 @@ impl ReceiverState {
|
||||
last_report_time: None,
|
||||
report_interval: Duration::from_millis(cold_start_ms),
|
||||
interval_has_data: false,
|
||||
srtt_sample_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,9 +369,18 @@ impl ReceiverState {
|
||||
|
||||
/// Update the report interval based on SRTT (link-layer defaults).
|
||||
///
|
||||
/// Receiver reports at 1× SRTT, clamped to [MIN, MAX].
|
||||
/// Receiver reports at 1× SRTT clamped to [floor, MAX]. During cold-start
|
||||
/// (first `COLD_START_SAMPLES` updates), the floor is the cold-start
|
||||
/// interval (200ms) for fast SRTT convergence. After that, it rises to
|
||||
/// `MIN_REPORT_INTERVAL_MS` (1000ms) for steady-state efficiency.
|
||||
pub fn update_report_interval_from_srtt(&mut self, srtt_us: i64) {
|
||||
self.update_report_interval_with_bounds(srtt_us, MIN_REPORT_INTERVAL_MS, MAX_REPORT_INTERVAL_MS);
|
||||
self.srtt_sample_count = self.srtt_sample_count.saturating_add(1);
|
||||
let floor = if self.srtt_sample_count <= COLD_START_SAMPLES {
|
||||
DEFAULT_COLD_START_INTERVAL_MS
|
||||
} else {
|
||||
MIN_REPORT_INTERVAL_MS
|
||||
};
|
||||
self.update_report_interval_with_bounds(srtt_us, floor, MAX_REPORT_INTERVAL_MS);
|
||||
}
|
||||
|
||||
/// Update the report interval based on SRTT with custom bounds.
|
||||
@@ -566,17 +581,36 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_report_interval() {
|
||||
fn test_update_report_interval_cold_start() {
|
||||
let mut r = ReceiverState::new(32);
|
||||
// 50ms SRTT → 100ms receiver interval (1× SRTT, clamped to min)
|
||||
// During cold-start, floor is 200ms (DEFAULT_COLD_START_INTERVAL_MS)
|
||||
// 50ms SRTT → 50ms receiver interval (1× SRTT), clamped to cold-start floor 200ms
|
||||
r.update_report_interval_from_srtt(50_000);
|
||||
assert_eq!(r.report_interval(), Duration::from_millis(100));
|
||||
assert_eq!(r.report_interval(), Duration::from_millis(200));
|
||||
|
||||
// 500ms SRTT → 500ms
|
||||
// 500ms SRTT → 500ms (above cold-start floor)
|
||||
r.update_report_interval_from_srtt(500_000);
|
||||
assert_eq!(r.report_interval(), Duration::from_millis(500));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_report_interval_after_cold_start() {
|
||||
let mut r = ReceiverState::new(32);
|
||||
// Burn through cold-start samples
|
||||
for _ in 0..COLD_START_SAMPLES {
|
||||
r.update_report_interval_from_srtt(500_000);
|
||||
}
|
||||
|
||||
// 6th sample: steady state, floor is MIN_REPORT_INTERVAL_MS (1000ms)
|
||||
// 50ms SRTT → 50ms receiver interval (1× SRTT), clamped to 1000ms
|
||||
r.update_report_interval_from_srtt(50_000);
|
||||
assert_eq!(r.report_interval(), Duration::from_millis(MIN_REPORT_INTERVAL_MS));
|
||||
|
||||
// 3s SRTT → 3000ms, within [1000, 5000]
|
||||
r.update_report_interval_from_srtt(3_000_000);
|
||||
assert_eq!(r.report_interval(), Duration::from_millis(3000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rekey_jitter_grace_suppresses_spikes() {
|
||||
let mut r = ReceiverState::new(32);
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::mmp::report::SenderReport;
|
||||
use crate::mmp::{DEFAULT_COLD_START_INTERVAL_MS, MAX_REPORT_INTERVAL_MS, MIN_REPORT_INTERVAL_MS};
|
||||
use crate::mmp::{COLD_START_SAMPLES, DEFAULT_COLD_START_INTERVAL_MS,
|
||||
MAX_REPORT_INTERVAL_MS, MIN_REPORT_INTERVAL_MS};
|
||||
|
||||
/// Per-peer sender-side MMP state.
|
||||
///
|
||||
@@ -35,6 +36,10 @@ pub struct SenderState {
|
||||
// --- Send failure backoff ---
|
||||
/// Consecutive send failure count for backoff calculation.
|
||||
consecutive_send_failures: u32,
|
||||
|
||||
// --- Cold-start tracking ---
|
||||
/// Number of SRTT-based interval updates received.
|
||||
srtt_sample_count: u32,
|
||||
}
|
||||
|
||||
impl SenderState {
|
||||
@@ -59,6 +64,7 @@ impl SenderState {
|
||||
last_report_time: None,
|
||||
report_interval: Duration::from_millis(cold_start_ms),
|
||||
consecutive_send_failures: 0,
|
||||
srtt_sample_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,9 +156,18 @@ impl SenderState {
|
||||
|
||||
/// Update the report interval based on SRTT (link-layer defaults).
|
||||
///
|
||||
/// Sender reports at 2× SRTT clamped to [MIN, MAX].
|
||||
/// Sender reports at 2× SRTT clamped to [floor, MAX]. During cold-start
|
||||
/// (first `COLD_START_SAMPLES` updates), the floor is the cold-start
|
||||
/// interval (200ms) for fast SRTT convergence. After that, it rises to
|
||||
/// `MIN_REPORT_INTERVAL_MS` (1000ms) for steady-state efficiency.
|
||||
pub fn update_report_interval_from_srtt(&mut self, srtt_us: i64) {
|
||||
self.update_report_interval_with_bounds(srtt_us, MIN_REPORT_INTERVAL_MS, MAX_REPORT_INTERVAL_MS);
|
||||
self.srtt_sample_count = self.srtt_sample_count.saturating_add(1);
|
||||
let floor = if self.srtt_sample_count <= COLD_START_SAMPLES {
|
||||
DEFAULT_COLD_START_INTERVAL_MS
|
||||
} else {
|
||||
MIN_REPORT_INTERVAL_MS
|
||||
};
|
||||
self.update_report_interval_with_bounds(srtt_us, floor, MAX_REPORT_INTERVAL_MS);
|
||||
}
|
||||
|
||||
/// Update the report interval based on SRTT with custom bounds.
|
||||
@@ -289,18 +304,33 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_report_interval() {
|
||||
fn test_update_report_interval_cold_start() {
|
||||
let mut s = SenderState::new();
|
||||
// 50ms RTT → 100ms sender interval (2× SRTT), clamped to min 100ms
|
||||
// During cold-start, floor is 200ms (DEFAULT_COLD_START_INTERVAL_MS)
|
||||
// 50ms RTT → 100ms sender interval (2× SRTT), clamped to cold-start floor 200ms
|
||||
s.update_report_interval_from_srtt(50_000);
|
||||
assert_eq!(s.report_interval(), Duration::from_millis(100));
|
||||
assert_eq!(s.report_interval(), Duration::from_millis(200));
|
||||
|
||||
// 500ms RTT → 1000ms sender interval
|
||||
// 500ms RTT → 1000ms sender interval (above cold-start floor)
|
||||
s.update_report_interval_from_srtt(500_000);
|
||||
assert_eq!(s.report_interval(), Duration::from_millis(1000));
|
||||
}
|
||||
|
||||
// 2s RTT → 4s, clamped to max 2s
|
||||
s.update_report_interval_from_srtt(2_000_000);
|
||||
#[test]
|
||||
fn test_update_report_interval_after_cold_start() {
|
||||
let mut s = SenderState::new();
|
||||
// Burn through cold-start samples (COLD_START_SAMPLES = 5)
|
||||
for _ in 0..COLD_START_SAMPLES {
|
||||
s.update_report_interval_from_srtt(500_000);
|
||||
}
|
||||
|
||||
// 6th sample: now in steady state, floor is MIN_REPORT_INTERVAL_MS (1000ms)
|
||||
// 50ms RTT → 100ms sender interval (2× SRTT), clamped to 1000ms
|
||||
s.update_report_interval_from_srtt(50_000);
|
||||
assert_eq!(s.report_interval(), Duration::from_millis(MIN_REPORT_INTERVAL_MS));
|
||||
|
||||
// 3s RTT → 6s, clamped to max 5s
|
||||
s.update_report_interval_from_srtt(3_000_000);
|
||||
assert_eq!(s.report_interval(), Duration::from_millis(MAX_REPORT_INTERVAL_MS));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user