use sha2::{Digest, Sha256};
use crate::seed::SeedRng;
use crate::transport::TransportKind;
pub struct ChannelHopper {
rng: SeedRng,
all_transports: Vec<TransportKind>,
}
impl ChannelHopper {
pub fn new(rng: SeedRng, transports: Vec<TransportKind>) -> Self {
Self {
rng,
all_transports: transports,
}
}
pub fn active_transports(&mut self, time_slot: u64) -> Vec<TransportKind> {
if self.all_transports.is_empty() {
return Vec::new();
}
let mut hasher = Sha256::new();
hasher.update(self.rng.seed_bytes());
hasher.update(time_slot.to_be_bytes());
let digest = hasher.finalize();
let mut out = Vec::new();
for (i, &t) in self.all_transports.iter().enumerate() {
let byte = digest[(i + 4) % 32];
if (byte >> (i % 8)) & 1 != 0 {
out.push(t);
}
}
if out.is_empty() {
let idx = (digest[0] as usize) % self.all_transports.len();
out.push(self.all_transports[idx]);
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn peers_agree_on_slot() {
let kinds = vec![TransportKind::Tcp, TransportKind::Udp];
let mut a = ChannelHopper::new(SeedRng::new([3u8; 32]), kinds.clone());
let mut b = ChannelHopper::new(SeedRng::new([3u8; 32]), kinds);
assert_eq!(a.active_transports(7), b.active_transports(7));
}
#[test]
fn non_empty_when_configured() {
let kinds = vec![TransportKind::Quic];
let mut h = ChannelHopper::new(SeedRng::new([9u8; 32]), kinds);
assert_eq!(h.active_transports(0), vec![TransportKind::Quic]);
}
}