use super::generator::SeedRng;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PortWindow {
pub start_secs: u64,
pub duration_secs: u64,
pub port: u16,
}
pub struct PortSchedule {
rng: SeedRng,
port_range: (u16, u16),
}
impl PortSchedule {
pub fn new(seed: [u8; 32], port_range: (u16, u16)) -> Self {
Self {
rng: SeedRng::new(seed),
port_range,
}
}
pub fn generate(&mut self, count: usize) -> Vec<PortWindow> {
let (lo, hi) = self.port_range;
if count == 0 || hi <= lo {
return Vec::new();
}
let span = (hi - lo) as u64;
let mut out = Vec::with_capacity(count);
let mut t = 0u64;
for _ in 0..count {
let duration_secs = self.rng.range(30, 3600).max(1);
let port = lo + (self.rng.range(0, span) as u16);
out.push(PortWindow {
start_secs: t,
duration_secs,
port,
});
t = t.saturating_add(duration_secs);
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn windows_stay_in_range() {
let mut ps = PortSchedule::new([0x5Eu8; 32], (40_000, 40_100));
let w = ps.generate(5);
assert_eq!(w.len(), 5);
for win in &w {
assert!((40_000..40_100).contains(&win.port));
assert!(win.duration_secs >= 1);
}
}
#[test]
fn empty_on_bad_range() {
let mut ps = PortSchedule::new([1u8; 32], (100, 100));
assert!(ps.generate(3).is_empty());
}
}