dig_pex/timer.rs
1//! Timing math (SPEC §6) — interval negotiation, the sender's effective interval + additive jitter,
2//! and the receiver's anti-flood minimum inter-arrival floor.
3//!
4//! All engine timestamps are **Unix epoch milliseconds** (`now_ms`); the protocol's interval
5//! constants are seconds, converted here. Keeping the timing pure + separately testable is what makes
6//! the receiver's enforcement (SPEC §6.4) exact: a sender's jitter is **additive only**, so a
7//! conformant sender never sends earlier than its effective interval, and the receiver's floor can be
8//! a hard discard.
9
10use rand::Rng;
11
12use crate::caps::{PEX_ARRIVAL_GRACE, PEX_MAX_INTERVAL, PEX_MIN_INTERVAL};
13
14/// Clamp a declared interval (seconds) into the legal `[PEX_MIN_INTERVAL, PEX_MAX_INTERVAL]` range
15/// (SPEC §6.2). Both a sender's own declaration and a remote's declaration are clamped for use.
16#[must_use]
17pub fn clamp_interval(secs: u32) -> u32 {
18 secs.clamp(PEX_MIN_INTERVAL, PEX_MAX_INTERVAL)
19}
20
21/// A sender's **effective interval** (seconds) — the minimum spacing it must honor for its own data
22/// messages (SPEC §6.2): `max(own declared, remote declared once known, PEX_MIN_INTERVAL)`. The
23/// remote's declaration is a floor ("don't tell me more often than this") once its handshake arrives.
24#[must_use]
25pub fn effective_interval_secs(own_declared: u32, remote_declared: Option<u32>) -> u32 {
26 let own = clamp_interval(own_declared);
27 let remote = remote_declared.map_or(0, clamp_interval);
28 own.max(remote).max(PEX_MIN_INTERVAL)
29}
30
31/// The receiver's minimum inter-arrival floor in **milliseconds** for data messages, given the
32/// sender's handshake-declared `interval` (SPEC §6.4): `max(declared, PEX_MIN_INTERVAL) −
33/// PEX_ARRIVAL_GRACE`, in ms. A data message arriving less than this after the previous one is a
34/// rate violation.
35#[must_use]
36pub fn arrival_floor_ms(remote_declared: u32) -> u64 {
37 let declared = clamp_interval(remote_declared).max(PEX_MIN_INTERVAL);
38 u64::from(declared - PEX_ARRIVAL_GRACE) * 1000
39}
40
41/// Additive send jitter in **milliseconds**: a uniformly random `0..=10%` of the effective interval
42/// (SPEC §6.3), to decorrelate network-wide ticks. Additive only — a sender MUST NOT send *earlier*
43/// than its effective interval.
44#[must_use]
45pub fn jitter_ms(effective_secs: u32) -> u64 {
46 // 10% of `effective_secs` seconds, in ms = effective_secs * 1000 * 0.10 = effective_secs * 100.
47 let span = u64::from(effective_secs) * 100;
48 if span == 0 {
49 0
50 } else {
51 rand::thread_rng().gen_range(0..=span)
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn clamps_into_range() {
61 assert_eq!(clamp_interval(0), PEX_MIN_INTERVAL);
62 assert_eq!(clamp_interval(29), 30);
63 assert_eq!(clamp_interval(60), 60);
64 assert_eq!(clamp_interval(4000), PEX_MAX_INTERVAL);
65 }
66
67 #[test]
68 fn effective_is_max_of_own_remote_and_floor() {
69 // Own only, before the remote handshake: max(own, 30).
70 assert_eq!(effective_interval_secs(60, None), 60);
71 assert_eq!(effective_interval_secs(10, None), 30); // clamped up to the floor
72 // Remote declares a larger interval → it becomes the floor.
73 assert_eq!(effective_interval_secs(60, Some(120)), 120);
74 // Remote declares a smaller one → own still governs.
75 assert_eq!(effective_interval_secs(120, Some(45)), 120);
76 }
77
78 #[test]
79 fn arrival_floor_is_declared_minus_grace() {
80 // 60 s declared → (60 − 5) * 1000 ms.
81 assert_eq!(arrival_floor_ms(60), 55_000);
82 // 30 s (the floor) → (30 − 5) * 1000.
83 assert_eq!(arrival_floor_ms(30), 25_000);
84 // A sub-floor declaration is clamped up to 30 first.
85 assert_eq!(arrival_floor_ms(1), 25_000);
86 }
87
88 #[test]
89 fn jitter_is_additive_and_bounded() {
90 for _ in 0..1000 {
91 let j = jitter_ms(60);
92 assert!(
93 j <= 6000,
94 "jitter must be <= 10% of 60 s = 6000 ms, got {j}"
95 );
96 }
97 }
98}