stochastic-routing-extended 1.0.2

SRX (Stochastic Routing eXtended) — a next-generation VPN protocol with stochastic routing, DPI evasion, post-quantum cryptography, and multi-transport channel splitting
Documentation
//! Jitter modeling: randomized timing between packets.

use crate::seed::SeedRng;
use std::time::Duration;

/// Generates pseudo-random inter-packet delays to defeat timing analysis.
pub struct JitterModel {
    _rng: SeedRng,
    /// Base delay between packets.
    base_delay: Duration,
    /// Maximum jitter added on top of base delay.
    max_jitter: Duration,
}

impl JitterModel {
    pub fn new(rng: SeedRng, base_delay: Duration, max_jitter: Duration) -> Self {
        Self {
            _rng: rng,
            base_delay,
            max_jitter,
        }
    }

    /// Compute the next inter-packet delay.
    pub fn next_delay(&mut self) -> Duration {
        let jitter_micros = self._rng.range(0, self.max_jitter.as_micros() as u64);
        self.base_delay + Duration::from_micros(jitter_micros)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn make_rng(seed_byte: u8) -> SeedRng {
        SeedRng::new([seed_byte; 32])
    }

    #[test]
    fn delay_within_expected_range() {
        let base = Duration::from_millis(10);
        let max_jitter = Duration::from_millis(5);
        let mut model = JitterModel::new(make_rng(42), base, max_jitter);

        for _ in 0..100 {
            let delay = model.next_delay();
            assert!(
                delay >= base && delay <= base + max_jitter,
                "delay {:?} out of range [{:?}, {:?}]",
                delay,
                base,
                base + max_jitter
            );
        }
    }

    #[test]
    fn deterministic_from_same_seed() {
        let base = Duration::from_millis(10);
        let max_jitter = Duration::from_millis(5);

        let mut model_a = JitterModel::new(make_rng(42), base, max_jitter);
        let mut model_b = JitterModel::new(make_rng(42), base, max_jitter);

        for _ in 0..20 {
            assert_eq!(model_a.next_delay(), model_b.next_delay());
        }
    }

    #[test]
    fn different_seeds_produce_different_sequences() {
        let base = Duration::from_millis(10);
        let max_jitter = Duration::from_millis(5);

        let mut model_a = JitterModel::new(make_rng(1), base, max_jitter);
        let mut model_b = JitterModel::new(make_rng(2), base, max_jitter);

        // Collect sequences
        let seq_a: Vec<Duration> = (0..20).map(|_| model_a.next_delay()).collect();
        let seq_b: Vec<Duration> = (0..20).map(|_| model_b.next_delay()).collect();

        assert_ne!(seq_a, seq_b, "sequences from different seeds should differ");
    }

    #[test]
    fn zero_jitter_returns_constant_base_delay() {
        let base = Duration::from_millis(10);
        let max_jitter = Duration::ZERO;
        let mut model = JitterModel::new(make_rng(42), base, max_jitter);

        for _ in 0..20 {
            assert_eq!(model.next_delay(), base);
        }
    }

    #[test]
    fn multiple_calls_return_varying_delays() {
        let base = Duration::from_millis(10);
        let max_jitter = Duration::from_millis(50);
        let mut model = JitterModel::new(make_rng(42), base, max_jitter);

        let delays: Vec<Duration> = (0..20).map(|_| model.next_delay()).collect();

        // With non-zero jitter and a varying RNG, we expect at least some variation
        let unique_count = delays
            .iter()
            .collect::<std::collections::HashSet<_>>()
            .len();
        assert!(
            unique_count > 1,
            "expected varying delays with non-zero jitter, got {} unique values out of {}",
            unique_count,
            delays.len()
        );
    }
}