FSP wire format revision and session-layer MMP implementation

FSP wire format revision (TASK-2026-0007):

Introduce the FIPS Session Protocol (FSP) wire format with a 4-byte
common prefix [ver_phase:1][flags:1][payload_len:2 LE] replacing the
old 1-byte msg_type dispatch. All session messages share this prefix
with phase-based dispatch (Established, Setup, Ack, Unencrypted).

- New session_wire.rs: FSP constants, header types, parse/build helpers
- SessionMessageType enum: DataPacket (0x10), SenderReport (0x11),
  ReceiverReport (0x12), PathMtuNotification (0x13)
- FspFlags (CP/K/U) and FspInnerFlags (SP) for flag management
- SessionSenderReport, SessionReceiverReport, PathMtuNotification
  message structs with encode/decode
- FSP send pipeline: 12-byte header as AAD, 6-byte inner header
  (timestamp + msg_type + inner_flags), encrypt_with_aad()
- FSP receive pipeline: parse header, extract cleartext coords (CP),
  AEAD decrypt with AAD, strip inner header, msg_type dispatch
- Forwarding: transit nodes parse cleartext coords without decryption
- Removed DataPacket struct and associated types
- SessionEntry: session_start_ms, mark_established(), session_timestamp()
- FIPS_OVERHEAD: 144 → 150 bytes (+6 for FSP inner header)
- Design docs updated for new wire format

Session-layer MMP implementation (TASK-2026-0008):

Implement complete session-layer MMP reusing the link-layer algorithm
modules (SenderState, ReceiverState, MmpMetrics, SpinBitState) with
independent configuration and higher report interval clamps.

- SessionMmpConfig: separate config section (node.session_mmp.*)
- MmpSessionState: session-specific wrapper with PathMtuState tracking
- Session-layer constants (500ms-10s report intervals, 1s cold start)
- Parameterized interval methods (new_with_cold_start,
  update_report_interval_with_bounds) on SenderState/ReceiverState
- Bidirectional From conversions between link/session report types
- SessionEntry: mmp and is_initiator fields, initialized on Established
- send_session_msg() for reports/notifications
- Per-message RX recording with spin bit state tracking
- Handlers for SenderReport, ReceiverReport, PathMtuNotification
- path_mtu threaded from SessionDatagram envelope through to handlers
- check_session_mmp_reports() tick handler with collect-then-send pattern
- Periodic and teardown operator logging for session metrics
- PathMtuState: destination observes incoming MTU on all session messages,
  source seeded from outbound transport MTU, decrease-immediate /
  increase-requires-3-consecutive rules

Link-layer MMP fix:

- Stop feeding spin bit RTT samples into SRTT estimator; inter-frame
  timing in the mesh is irregular, inflating spin-bit RTT by variable
  processing delays; timestamp-echo provides accurate RTT

29 files changed, 602 tests pass, 0 clippy warnings.
This commit is contained in:
Johnathan Corgan
2026-02-19 03:15:05 +00:00
parent d8cb4d407e
commit 04d9fd625d
29 changed files with 2675 additions and 733 deletions
+20 -13
View File
@@ -5,7 +5,8 @@
//! multi-hop forwarding through live node topologies.
use super::*;
use crate::protocol::{DataPacket, SessionAck, SessionDatagram, SessionSetup};
use crate::node::session_wire::{build_fsp_header, FSP_FLAG_CP};
use crate::protocol::{SessionAck, SessionDatagram, SessionSetup, encode_coords};
use crate::tree::TreeCoordinate;
use spanning_tree::{
cleanup_nodes, process_available_packets, run_tree_test, verify_tree_convergence,
@@ -184,7 +185,7 @@ async fn test_coord_cache_warming_session_ack() {
}
#[tokio::test]
async fn test_coord_cache_warming_data_packet_with_coords() {
async fn test_coord_cache_warming_encrypted_msg_with_coords() {
let mut node = make_node();
let from = make_node_addr(0xAA);
let src_addr = make_node_addr(0x01);
@@ -194,9 +195,13 @@ async fn test_coord_cache_warming_data_packet_with_coords() {
let src_coords = TreeCoordinate::from_addrs(vec![src_addr, root_addr]).unwrap();
let dest_coords = TreeCoordinate::from_addrs(vec![dest_addr, root_addr]).unwrap();
let data = DataPacket::new(0, vec![1, 2, 3, 4])
.with_coords(src_coords.clone(), dest_coords.clone());
let data_payload = data.encode();
// Build FSP encrypted message with CP flag: header(12) + coords + fake_ciphertext
let header = build_fsp_header(0, FSP_FLAG_CP, 20);
let mut data_payload = Vec::new();
data_payload.extend_from_slice(&header);
encode_coords(&src_coords, &mut data_payload);
encode_coords(&dest_coords, &mut data_payload);
data_payload.extend_from_slice(&[0xCC; 36]); // fake ciphertext (20 payload + 16 tag)
let dg = SessionDatagram::new(src_addr, dest_addr, data_payload);
let encoded = dg.encode();
@@ -213,24 +218,26 @@ async fn test_coord_cache_warming_data_packet_with_coords() {
assert!(
node.coord_cache().get(&src_addr, now_ms).is_some(),
"src coords not cached from DataPacket"
"src coords not cached from encrypted message"
);
assert!(
node.coord_cache().get(&dest_addr, now_ms).is_some(),
"dest coords not cached from DataPacket"
"dest coords not cached from encrypted message"
);
}
#[tokio::test]
async fn test_coord_cache_warming_opaque_data_packet() {
async fn test_coord_cache_warming_encrypted_msg_no_coords() {
let mut node = make_node();
let from = make_node_addr(0xAA);
let src_addr = make_node_addr(0x01);
let dest_addr = make_node_addr(0x02);
// DataPacket without COORDS_PRESENT — no coords to cache
let data = DataPacket::new(0, vec![1, 2, 3, 4]);
let data_payload = data.encode();
// Build FSP encrypted message without CP flag: header(12) + fake_ciphertext
let header = build_fsp_header(0, 0, 20);
let mut data_payload = Vec::new();
data_payload.extend_from_slice(&header);
data_payload.extend_from_slice(&[0xCC; 36]); // fake ciphertext (20 payload + 16 tag)
let dg = SessionDatagram::new(src_addr, dest_addr, data_payload);
let encoded = dg.encode();
@@ -244,11 +251,11 @@ async fn test_coord_cache_warming_opaque_data_packet() {
assert!(
node.coord_cache().get(&src_addr, now_ms).is_none(),
"Should not cache coords from opaque DataPacket"
"Should not cache coords from message without CP flag"
);
assert!(
node.coord_cache().get(&dest_addr, now_ms).is_none(),
"Should not cache coords from opaque DataPacket"
"Should not cache coords from message without CP flag"
);
}