use std::time::Duration;
#[derive(Debug, Clone)]
pub(crate) struct StealthConfig {
pub delay: Duration,
pub jitter: Duration,
}
impl StealthConfig {
pub(crate) const fn none() -> Self {
Self { delay: Duration::ZERO, jitter: Duration::ZERO }
}
pub(crate) const fn new(delay_ms: u64, jitter_ms: u64) -> Self {
Self { delay: Duration::from_millis(delay_ms), jitter: Duration::from_millis(jitter_ms) }
}
pub(crate) fn next_delay(&self) -> Duration {
if self.delay.is_zero() && self.jitter.is_zero() {
return Duration::ZERO;
}
let jitter_ms = u64::try_from(self.jitter.as_millis()).unwrap_or(u64::MAX);
let jitter = if jitter_ms == 0 { Duration::ZERO } else { Duration::from_millis(rand::random_range(0..=jitter_ms)) };
self.delay + jitter
}
pub(crate) async fn wait(&self) {
let d = self.next_delay();
if !d.is_zero() {
tokio::time::sleep(d).await;
}
}
}
#[cfg(test)]
mod tests {
#![expect(clippy::pedantic, reason = "unit test -- lints are suppressed per project policy")]
use super::*;
#[test]
fn stealth_config_none_has_zero_delay() {
let sc = StealthConfig::none();
assert!(sc.delay.is_zero());
assert!(sc.jitter.is_zero());
assert!(sc.next_delay().is_zero());
}
#[test]
fn stealth_config_new_stores_values() {
let sc = StealthConfig::new(100, 50);
assert_eq!(sc.delay, Duration::from_millis(100));
assert_eq!(sc.jitter, Duration::from_millis(50));
}
#[tokio::test]
async fn wait_completes_without_panic() {
let sc = StealthConfig::none();
sc.wait().await;
}
#[test]
fn stealth_config_zero_jitter_returns_base_delay() {
let sc = StealthConfig::new(50, 0);
let delay = sc.next_delay();
assert_eq!(delay, Duration::from_millis(50), "zero jitter must return exact base delay");
}
#[test]
fn sub_millisecond_jitter_does_not_panic() {
let sc = StealthConfig { delay: Duration::from_millis(10), jitter: Duration::from_micros(500) };
let d = sc.next_delay();
assert_eq!(d, Duration::from_millis(10));
}
#[test]
fn sub_millisecond_jitter_with_zero_delay_returns_zero() {
let sc = StealthConfig { delay: Duration::ZERO, jitter: Duration::from_micros(900) };
assert!(sc.next_delay().is_zero());
}
}