Skip to main content

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}