diff --git a/src/node/session/mod.rs b/src/node/session/mod.rs index 59c6db4..f7be440 100644 --- a/src/node/session/mod.rs +++ b/src/node/session/mod.rs @@ -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. diff --git a/src/proto/fmp/tests/core.rs b/src/proto/fmp/tests/core.rs index be9a3ab..03e8a7b 100644 --- a/src/proto/fmp/tests/core.rs +++ b/src/proto/fmp/tests/core.rs @@ -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]