proto/mmp: split state into per-role modules and dissolve the vestigial mmp shell

Reorganize the MMP subsystem, behavior unchanged throughout:

- Restore the pre-migration role split: move SenderState into sender.rs,
  ReceiverState (with its GapTracker helper) into receiver.rs, MmpMetrics and
  RrLog into metrics.rs, and PathMtuState into path_mtu.rs, leaving the Mmp
  aggregate plus the peer/session state in state.rs (1339 to 196 lines). Home
  the module constants in a new limits.rs and re-export them at the same paths.
  Pure code-motion with module rewiring; visibility unchanged.

- Dissolve the 97-line src/mmp shell, which held only MmpConfig and the
  monotonic mono_ms() clock: move MmpConfig into config/node.rs (re-exported as
  crate::config::MmpConfig), move mono_ms() into a new top-level src/time.rs
  shell time seam, repoint all callers, and delete src/mmp/. Also correct stale
  src/mmp/ doc-comment path labels to proto/mmp/ where they name the protocol
  primitives.
This commit is contained in:
Johnathan Corgan
2026-07-08 19:00:24 +00:00
parent c1ddbf053c
commit 309a91d293
23 changed files with 1380 additions and 1347 deletions
+2 -2
View File
@@ -258,7 +258,7 @@ impl Node {
};
// MMP per-frame processing and statistics
let now_ms = crate::mmp::mono_ms();
let now_ms = crate::time::mono_ms();
let ce_flag = header.flags & FLAG_CE != 0;
let sp_flag = header.flags & FLAG_SP != 0;
@@ -354,7 +354,7 @@ impl Node {
} else {
return;
};
let now_ms = crate::mmp::mono_ms();
let now_ms = crate::time::mono_ms();
let mut address_changed = false;
if let Some(peer) = self.peers.get_mut(node_addr) {
peer.reset_decrypt_failures();
+4 -4
View File
@@ -152,7 +152,7 @@ impl Node {
// Process the report: computes RTT from timestamp echo, updates
// loss rate, goodput rate, jitter trend, and ETX.
let now_ms = crate::mmp::mono_ms();
let now_ms = crate::time::mono_ms();
let (first_rtt, rr_log) =
mmp.metrics
.process_receiver_report(&rr, our_timestamp_ms, now_ms);
@@ -197,7 +197,7 @@ impl Node {
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let mono_now_ms = crate::mmp::mono_ms();
let mono_now_ms = crate::time::mono_ms();
if let Some(new_parent) = self.tree_state.evaluate_parent(
&peer_costs,
&std::collections::BTreeSet::new(),
@@ -277,7 +277,7 @@ impl Node {
///
/// Called from the tick handler. Also emits periodic operator logs.
pub(in crate::node) async fn check_mmp_reports(&mut self) {
let now_ms = crate::mmp::mono_ms();
let now_ms = crate::time::mono_ms();
// Build one report-gating snapshot per peer, resolving every timing read
// shell-side into a `bool`. `send_sr`/`send_rr` are `true` on the master
@@ -427,7 +427,7 @@ impl Node {
// Monotonic ms for the MMP receiver's injected-`u64` liveness clock; the
// Instant `now` is still used for the shell-owned heartbeat timing and
// the session-start fallback (both `ActivePeer` Instants).
let now_ms = crate::mmp::mono_ms();
let now_ms = crate::time::mono_ms();
let heartbeat_interval = Duration::from_secs(self.config().node.heartbeat_interval_secs);
let dead_timeout = Duration::from_secs(self.config().node.link_dead_timeout_secs);
let dead_timeout_ms = dead_timeout.as_millis() as u64;
+5 -5
View File
@@ -324,7 +324,7 @@ impl Node {
if let Some(entry) = self.sessions.get_mut(src_addr)
&& let Some(mmp) = entry.mmp_mut()
{
let now_ms = crate::mmp::mono_ms();
let now_ms = crate::time::mono_ms();
mmp.receiver
.record_recv(header.counter, timestamp, plaintext.len(), ce_flag, now_ms);
// Spin bit: advance state machine for correct TX reflection.
@@ -946,7 +946,7 @@ impl Node {
/// Called from the tick handler. Also emits periodic session MMP logs.
/// Uses the collect-then-send pattern to avoid borrowing conflicts.
pub(in crate::node) async fn check_session_mmp_reports(&mut self) {
let now_ms = crate::mmp::mono_ms();
let now_ms = crate::time::mono_ms();
// Build one report-gating snapshot per session, resolving every timing
// read shell-side into a `bool`. The snapshots own only
@@ -1213,7 +1213,7 @@ impl Node {
let (_first_rtt, rr_log) =
mmp.metrics
.process_receiver_report(&rr, our_timestamp_ms, crate::mmp::mono_ms());
.process_receiver_report(&rr, our_timestamp_ms, crate::time::mono_ms());
// Re-emit the operator trace the core used to log mid-decision.
super::mmp::log_rr_outcome(&rr, our_timestamp_ms, rr_log);
@@ -1283,7 +1283,7 @@ impl Node {
let old_mtu = mmp.path_mtu.current_mtu();
let changed = mmp
.path_mtu
.apply_notification(notif.path_mtu, crate::mmp::mono_ms());
.apply_notification(notif.path_mtu, crate::time::mono_ms());
let new_mtu = mmp.path_mtu.current_mtu();
if !changed {
@@ -1521,7 +1521,7 @@ impl Node {
let old_mtu = mmp.path_mtu.current_mtu();
if mmp
.path_mtu
.apply_notification(msg.mtu, crate::mmp::mono_ms())
.apply_notification(msg.mtu, crate::time::mono_ms())
{
let new_mtu = mmp.path_mtu.current_mtu();
info!(
+1 -1
View File
@@ -668,7 +668,7 @@ impl SessionEntry {
self.rekey_jitter_secs = draw_rekey_jitter();
// Reset MMP counters to avoid metric discontinuity
let now_ms = crate::mmp::mono_ms();
let now_ms = crate::time::mono_ms();
if let Some(mmp) = &mut self.mmp {
mmp.reset_for_rekey(now_ms);
}
+12 -12
View File
@@ -12,12 +12,12 @@
//!
//! Assertions capture what the code does today, surprising or not.
//!
//! Report-generation is probed through the reused `src/mmp/` primitives
//! Report-generation is probed through the reused `proto/mmp/` primitives
//! (`should_send_report` / `should_send_notification`): after a handler tick,
//! a *consumed* interval reads as "not due" (the report was built) while an
//! *ungated* interval still reads as "due" (the report was suppressed by the
//! mode/flag gate). This survives the later refactor because those primitives
//! stay in `src/mmp/` unchanged.
//! stay in `proto/mmp/` unchanged.
//!
//! Two `#[cfg(test)]` production seams are used, both on `ActivePeer`:
//! * `test_init_mmp(mode)` — attach link MMP with a chosen mode to a
@@ -55,7 +55,7 @@ fn arm_link_mmp(node: &mut Node, addr: &NodeAddr) {
let mmp = node.get_peer_mut(addr).unwrap().mmp_mut().unwrap();
mmp.sender.record_sent(1, 100, 500);
mmp.receiver
.record_recv(1, 100, 500, false, crate::mmp::mono_ms());
.record_recv(1, 100, 500, false, crate::time::mono_ms());
}
/// Complete an in-memory Noise IK handshake, returning the initiator session.
@@ -109,7 +109,7 @@ fn arm_session_mmp(node: &mut Node, addr: &NodeAddr) {
let mmp = node.sessions.get_mut(addr).unwrap().mmp_mut().unwrap();
mmp.sender.record_sent(1, 100, 500);
mmp.receiver
.record_recv(1, 100, 500, false, crate::mmp::mono_ms());
.record_recv(1, 100, 500, false, crate::time::mono_ms());
}
// ===========================================================================
@@ -127,7 +127,7 @@ async fn mmp_full_mode_builds_sender_and_receiver_reports() {
node.check_mmp_reports().await;
let mmp = node.get_peer(&addr).unwrap().mmp().unwrap();
let now = crate::mmp::mono_ms();
let now = crate::time::mono_ms();
assert!(
!mmp.sender.should_send_report(now),
"Full mode consumes the sender interval (SenderReport built)"
@@ -149,7 +149,7 @@ async fn mmp_lightweight_mode_builds_receiver_report_only() {
node.check_mmp_reports().await;
let mmp = node.get_peer(&addr).unwrap().mmp().unwrap();
let now = crate::mmp::mono_ms();
let now = crate::time::mono_ms();
assert!(
mmp.sender.should_send_report(now),
"Lightweight mode suppresses the SenderReport (sender interval intact)"
@@ -170,7 +170,7 @@ async fn mmp_minimal_mode_builds_nothing() {
node.check_mmp_reports().await;
let mmp = node.get_peer(&addr).unwrap().mmp().unwrap();
let now = crate::mmp::mono_ms();
let now = crate::time::mono_ms();
assert!(
mmp.sender.should_send_report(now),
"Minimal mode suppresses the SenderReport"
@@ -194,7 +194,7 @@ async fn mmp_should_log_marks_logged_once_per_interval() {
.unwrap()
.mmp()
.unwrap()
.should_log(crate::mmp::mono_ms()),
.should_log(crate::time::mono_ms()),
"a freshly created peer is due for its first operator log"
);
@@ -206,7 +206,7 @@ async fn mmp_should_log_marks_logged_once_per_interval() {
.unwrap()
.mmp()
.unwrap()
.should_log(crate::mmp::mono_ms()),
.should_log(crate::time::mono_ms()),
"after one tick the log is marked and not due again within the interval"
);
}
@@ -226,7 +226,7 @@ async fn session_full_mode_builds_sender_and_receiver_reports() {
node.check_session_mmp_reports().await;
let mmp = node.get_session(&addr).unwrap().mmp().unwrap();
let now = crate::mmp::mono_ms();
let now = crate::time::mono_ms();
assert!(
!mmp.sender.should_send_report(now),
"Full session consumes the sender interval"
@@ -254,7 +254,7 @@ async fn session_minimal_mode_still_sends_path_mtu() {
.path_mtu
.observe_incoming_mtu(1200);
let now_before = crate::mmp::mono_ms();
let now_before = crate::time::mono_ms();
assert!(
node.get_session(&addr)
.unwrap()
@@ -268,7 +268,7 @@ async fn session_minimal_mode_still_sends_path_mtu() {
node.check_session_mmp_reports().await;
let mmp = node.get_session(&addr).unwrap().mmp().unwrap();
let now = crate::mmp::mono_ms();
let now = crate::time::mono_ms();
assert!(
mmp.sender.should_send_report(now),
"Minimal mode suppresses the session SenderReport"
+1 -1
View File
@@ -1912,7 +1912,7 @@ async fn test_tun_outbound_path_mtu_generates_ptb() {
let entry = nodes[0].node.get_session_mut(&node1_addr).unwrap();
let mmp = entry.mmp_mut().unwrap();
mmp.path_mtu
.apply_notification(reduced_mtu, crate::mmp::mono_ms());
.apply_notification(reduced_mtu, crate::time::mono_ms());
assert_eq!(mmp.path_mtu.current_mtu(), reduced_mtu);
}
+3 -3
View File
@@ -328,7 +328,7 @@ impl Node {
// Monotonic ms for the flap-dampening / hold-down timers (distinct from
// the wall-clock `now_ms` above used for the peer's tree position). Read
// once and threaded into classify + the state mutators.
let mono_now_ms = crate::mmp::mono_ms();
let mono_now_ms = crate::time::mono_ms();
match Stp::classify_announce(&self.tree_state, *from, &peer_costs, &skip, mono_now_ms) {
TreeDecision::Switch {
@@ -582,7 +582,7 @@ impl Node {
// Monotonic ms for the flap-dampening / hold-down timers, read once and
// threaded into classify + the state mutators.
let mono_now_ms = crate::mmp::mono_ms();
let mono_now_ms = crate::time::mono_ms();
match Stp::classify_periodic(&self.tree_state, &peer_costs, &skip, mono_now_ms) {
TreeDecision::Switch {
@@ -735,7 +735,7 @@ impl Node {
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let mono_now_ms = crate::mmp::mono_ms();
let mono_now_ms = crate::time::mono_ms();
// Removal is not a pure classify: `handle_parent_lost` is a &mut mutator
// whose returned `changed` bool IS the decision. Drive it and map the