use core::sync::atomic::{AtomicU64, Ordering};
pub struct Random {
state: [u32; 16],
out: [u32; 16],
used: usize,
}
#[inline]
fn qround(x: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
x[a] = x[a].wrapping_add(x[b]);
x[d] = (x[d] ^ x[a]).rotate_left(16);
x[c] = x[c].wrapping_add(x[d]);
x[b] = (x[b] ^ x[c]).rotate_left(12);
x[a] = x[a].wrapping_add(x[b]);
x[d] = (x[d] ^ x[a]).rotate_left(8);
x[c] = x[c].wrapping_add(x[d]);
x[b] = (x[b] ^ x[c]).rotate_left(7);
}
impl Random {
pub const fn new() -> Random {
Random {
state: [0; 16],
out: [0; 16],
used: 16,
}
}
pub fn seed_from(&mut self, key: [u32; 8], stream: u64) {
self.state[0] = 0x6170_7865;
self.state[1] = 0x3320_646e;
self.state[2] = 0x7962_2d32;
self.state[3] = 0x6b20_6574;
self.state[4..12].copy_from_slice(&key);
self.state[12] = 0; self.state[13] = 0;
self.state[14] = stream as u32;
self.state[15] = (stream >> 32) as u32;
self.used = 16;
}
pub fn reseed(&mut self) {
let mut key = [0u32; 8];
if !os_entropy(&mut key) {
static COUNTER: AtomicU64 = AtomicU64::new(0x9E37_79B9_7F4A_7C15);
let stack = &key as *const _ as usize as u64;
let mut acc = crate::prim::clock_now()
^ stack.rotate_left(17)
^ (crate::prim::thread_id() as u64).rotate_left(33)
^ COUNTER.fetch_add(0x9E37_79B9_7F4A_7C15, Ordering::Relaxed);
for k in key.iter_mut() {
acc = acc.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = acc;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
*k = ((z ^ (z >> 31)) & 0xFFFF_FFFF) as u32;
}
}
let stream = crate::prim::thread_id() as u64;
self.seed_from(key, stream);
}
fn refill(&mut self) {
let mut x = self.state;
for _ in 0..4 {
qround(&mut x, 0, 4, 8, 12);
qround(&mut x, 1, 5, 9, 13);
qround(&mut x, 2, 6, 10, 14);
qround(&mut x, 3, 7, 11, 15);
qround(&mut x, 0, 5, 10, 15);
qround(&mut x, 1, 6, 11, 12);
qround(&mut x, 2, 7, 8, 13);
qround(&mut x, 3, 4, 9, 14);
}
for (o, (xi, si)) in self.out.iter_mut().zip(x.iter().zip(self.state.iter())) {
*o = xi.wrapping_add(*si);
}
let (lo, carry) = self.state[12].overflowing_add(1);
self.state[12] = lo;
if carry {
self.state[13] = self.state[13].wrapping_add(1);
}
self.used = 0;
}
#[inline]
pub fn next_u32(&mut self) -> u32 {
if self.used >= 16 {
self.refill();
}
let v = self.out[self.used];
self.used += 1;
v
}
#[inline]
pub fn next_usize(&mut self) -> usize {
let lo = self.next_u32() as usize;
#[cfg(target_pointer_width = "64")]
{
let hi = self.next_u32() as usize;
(hi << 32) | lo
}
#[cfg(not(target_pointer_width = "64"))]
{
lo
}
}
#[inline]
pub fn below(&mut self, n: usize) -> usize {
if n <= 1 { 0 } else { self.next_usize() % n }
}
}
impl Default for Random {
fn default() -> Self {
Self::new()
}
}
#[cfg(all(windows, not(miri)))]
fn os_entropy(key: &mut [u32; 8]) -> bool {
use windows_sys::Win32::Security::Cryptography::{
BCRYPT_USE_SYSTEM_PREFERRED_RNG, BCryptGenRandom,
};
let status = unsafe {
BCryptGenRandom(
core::ptr::null_mut(),
key.as_mut_ptr().cast::<u8>(),
(key.len() * 4) as u32,
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
)
};
status == 0
}
#[cfg(all(unix, not(miri)))]
fn os_entropy(key: &mut [u32; 8]) -> bool {
use std::io::Read;
let Ok(mut f) = std::fs::File::open("/dev/urandom") else {
return false;
};
let buf =
unsafe { core::slice::from_raw_parts_mut(key.as_mut_ptr().cast::<u8>(), key.len() * 4) };
f.read_exact(buf).is_ok()
}
#[cfg(miri)]
fn os_entropy(_key: &mut [u32; 8]) -> bool {
false }
#[cfg(all(target_arch = "wasm32", not(miri)))]
fn os_entropy(_key: &mut [u32; 8]) -> bool {
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stream_is_deterministic_and_varied() {
let mut a = Random::new();
let mut b = Random::new();
a.seed_from([1, 2, 3, 4, 5, 6, 7, 8], 42);
b.seed_from([1, 2, 3, 4, 5, 6, 7, 8], 42);
let xs: Vec<u32> = (0..64).map(|_| a.next_u32()).collect();
let ys: Vec<u32> = (0..64).map(|_| b.next_u32()).collect();
assert_eq!(xs, ys, "same seed must give the same stream");
assert!(xs.windows(2).any(|w| w[0] != w[1]), "stream is constant");
let mut c = Random::new();
c.seed_from([1, 2, 3, 4, 5, 6, 7, 8], 43);
let zs: Vec<u32> = (0..64).map(|_| c.next_u32()).collect();
assert_ne!(xs, zs);
}
#[test]
fn reseed_produces_distinct_streams() {
let mut a = Random::new();
let mut b = Random::new();
a.reseed();
b.reseed();
let xs: Vec<u32> = (0..8).map(|_| a.next_u32()).collect();
let ys: Vec<u32> = (0..8).map(|_| b.next_u32()).collect();
assert_ne!(xs, ys, "two reseeds collided");
}
}