mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Pin the rekey counter arm's boundary against the real trigger
The send-counter arm of the rekey trigger had no test that could fail for the reason it existed. The integration suites never reached it: they inject after_messages = 65536 against a ping-driven workload, so every rekey they observe is time-triggered. The unit test that looked like the guard reproduced the trigger's OR predicate as a local closure and asserted against its own copy, so deleting the arm from the real predicate left it green. That is the coverage hole that let the data-plane drop across a message-count-triggered rekey survive: the defect is precisely a rekey of a young session, which only the counter arm can produce. Replaces the existing at-threshold check with a boundary test driving poll_rekey, holding the time arm off so the counter is provably what decides. The silence-below half is the part that is new, and it earns its place by measurement: widening the bound to `p.counter >= 1` reds exactly one test in the library suite, this one. Deletion, narrowing to `>` and inversion are caught here too, though the time arm's negative tests already catch a bound removed altogether. Removes the closure-based test rather than repairing it. Its one assertion over real code, that a fresh session entry's jitter lies within the symmetric bound, is already covered over 100 samples elsewhere. Note this leaves the deployed maintenance line untested on the same arm: its trigger is inline in an async method that reads the clock directly, with no pure function to drive, so the same test cannot be written there without the sans-IO seam this line already has.
This commit is contained in:
+8
-30
@@ -1315,36 +1315,14 @@ mod overlapping_epoch_tests {
|
||||
);
|
||||
}
|
||||
|
||||
// Rekey-trigger threshold: elapsed time (with symmetric jitter applied)
|
||||
// OR the send counter crossing its configured bound.
|
||||
#[test]
|
||||
fn rekey_trigger_threshold_arithmetic() {
|
||||
let after_secs = 100u64;
|
||||
let after_messages = 1_000u64;
|
||||
|
||||
// Jitter is always within [-REKEY_JITTER_SECS, +REKEY_JITTER_SECS].
|
||||
let (_s, recv) = xk_pair(1, 2);
|
||||
let entry = entry_with_current(recv);
|
||||
let jitter = entry.rekey_jitter_secs();
|
||||
assert!(
|
||||
jitter.abs() <= REKEY_JITTER_SECS,
|
||||
"jitter within configured bound"
|
||||
);
|
||||
|
||||
// Effective time threshold applies the symmetric jitter.
|
||||
let effective_after = after_secs.saturating_add_signed(jitter);
|
||||
|
||||
// Reproduce the policy's OR predicate directly.
|
||||
let triggers =
|
||||
|elapsed: u64, counter: u64| elapsed >= effective_after || counter >= after_messages;
|
||||
|
||||
// Time arm: fires at/after the effective threshold, not below it.
|
||||
assert!(!triggers(effective_after - 1, 0), "below time threshold");
|
||||
assert!(triggers(effective_after, 0), "at time threshold");
|
||||
// Counter arm: fires independently of elapsed time.
|
||||
assert!(!triggers(0, after_messages - 1), "below counter threshold");
|
||||
assert!(triggers(0, after_messages), "at counter threshold");
|
||||
}
|
||||
// The rekey trigger's own threshold arithmetic is tested against the real
|
||||
// predicate in src/proto/fmp/tests/core.rs, which drives poll_rekey. A test
|
||||
// here previously reproduced that OR predicate as a local closure and
|
||||
// asserted against its own copy, so it could not fail for the reason it
|
||||
// existed: deleting the counter arm from the trigger left it green. Its one
|
||||
// assertion over real code, that a fresh entry's jitter lies within the
|
||||
// symmetric bound, is covered over 100 samples by
|
||||
// test_session_entry_rekey_jitter_in_range in src/node/tests/session.rs.
|
||||
|
||||
// Dampening boundary: within `dampening_ms` of the peer's rekey msg1, local
|
||||
// initiation is suppressed; at/after the window it is not.
|
||||
|
||||
@@ -158,13 +158,37 @@ fn rekey_expired_drain_and_trigger_both_fire() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The send-counter arm of the rekey trigger, pinned at its boundary through
|
||||
/// the real `poll_rekey` rather than a reimplementation of its predicate.
|
||||
///
|
||||
/// Firing at the threshold catches the arm being deleted, its comparison
|
||||
/// narrowed to `>`, or its comparison inverted. The silence-below half is what
|
||||
/// this test adds over that: it catches the arm being *widened* below its
|
||||
/// configured bound. Measured rather than assumed — replacing the bound with
|
||||
/// `p.counter >= 1` reds exactly one test in the whole library suite, this one,
|
||||
/// while a bound removed altogether is already caught by the time arm's own
|
||||
/// negative tests.
|
||||
///
|
||||
/// The time arm is held off in both halves — `elapsed_secs` stays 0 against a
|
||||
/// 100 s threshold with no jitter — so the counter is provably what decides.
|
||||
#[test]
|
||||
fn rekey_triggers_on_counter() {
|
||||
fn rekey_counter_arm_fires_at_threshold_and_is_silent_below() {
|
||||
let fmp = Fmp::new();
|
||||
let mut p = peer_snapshot(0x13);
|
||||
p.counter = 1_000; // == after_messages
|
||||
let actions = fmp.poll_rekey(vec![p], &cfg());
|
||||
assert!(matches!(actions[0], ConnAction::InitiateRekey { .. }));
|
||||
|
||||
let mut below = peer_snapshot(0x13);
|
||||
below.counter = 999; // one below after_messages
|
||||
assert!(
|
||||
fmp.poll_rekey(vec![below], &cfg()).is_empty(),
|
||||
"a send counter one below the threshold must not trigger a rekey"
|
||||
);
|
||||
|
||||
let mut at = peer_snapshot(0x13);
|
||||
at.counter = 1_000; // == after_messages
|
||||
let actions = fmp.poll_rekey(vec![at], &cfg());
|
||||
assert!(
|
||||
matches!(actions[0], ConnAction::InitiateRekey { .. }),
|
||||
"a send counter at the threshold must trigger a rekey"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user