use sha2::{Digest, Sha256};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NonceNamespace(u32);
impl NonceNamespace {
pub fn for_run(seed: &str, run: u32) -> Self {
let mut hasher = Sha256::new();
hasher.update(b"thruput-testkit nonce namespace v1");
hasher.update(seed.as_bytes());
hasher.update(run.to_be_bytes());
let digest = hasher.finalize();
Self(u32::from_be_bytes([
digest[0], digest[1], digest[2], digest[3],
]))
}
pub fn make_nonce(self, sequence: u32) -> u64 {
(u64::from(self.0) << 32) | u64::from(sequence)
}
}
#[cfg(test)]
mod tests {
use super::NonceNamespace;
#[test]
fn nonce_packs_namespace_into_high_bits() {
let namespace = NonceNamespace::for_run("seed-v1", 0);
let high = namespace.make_nonce(0);
assert_eq!(
high & 0xffff_ffff,
0,
"sequence 0 leaves the low bits clear"
);
assert_eq!(namespace.make_nonce(0x90ab_cdef), high | 0x90ab_cdef);
}
#[test]
fn derivation_is_deterministic_and_seed_specific() {
assert_eq!(
NonceNamespace::for_run("seed-a", 0),
NonceNamespace::for_run("seed-a", 0)
);
assert_ne!(
NonceNamespace::for_run("seed-a", 0),
NonceNamespace::for_run("seed-b", 0)
);
}
#[test]
fn each_run_gets_a_distinct_namespace() {
assert_ne!(
NonceNamespace::for_run("seed", 0),
NonceNamespace::for_run("seed", 1)
);
assert_ne!(
NonceNamespace::for_run("seed", 1),
NonceNamespace::for_run("seed", 2)
);
}
}