use std::io;
mod sys {
use std::io;
pub fn fill(buf: &mut [u8]) -> io::Result<()> {
platform::fill(buf)
}
#[cfg(target_os = "linux")]
mod platform {
use std::io;
use std::sync::atomic::{AtomicU8, Ordering};
extern "C" {
fn getrandom(buf: *mut u8, buflen: usize, flags: u32) -> isize;
fn __errno_location() -> *mut i32;
}
const EINTR: i32 = 4;
const ENOSYS: i32 = 38;
static STATE: AtomicU8 = AtomicU8::new(0);
fn errno() -> i32 {
unsafe { *__errno_location() }
}
pub fn fill(buf: &mut [u8]) -> io::Result<()> {
if STATE.load(Ordering::Relaxed) == 2 {
return urandom_fallback(buf);
}
let mut pos = 0;
while pos < buf.len() {
let r = unsafe { getrandom(buf.as_mut_ptr().add(pos), buf.len() - pos, 0) };
if r > 0 {
pos += r as usize;
continue;
}
let e = errno();
if e == EINTR {
continue;
}
if e == ENOSYS {
STATE.store(2, Ordering::Relaxed);
return urandom_fallback(&mut buf[pos..]);
}
return Err(io::Error::from_raw_os_error(e));
}
STATE.store(1, Ordering::Relaxed);
Ok(())
}
fn urandom_fallback(buf: &mut [u8]) -> io::Result<()> {
use std::fs::File;
use std::io::Read;
let mut f = File::open("/dev/urandom")?;
f.read_exact(buf)
}
}
#[cfg(target_os = "macos")]
mod platform {
use std::io;
extern "C" {
fn getentropy(buf: *mut u8, buflen: usize) -> i32;
fn __error() -> *mut i32;
}
const EINTR: i32 = 4;
const MAX_PER_CALL: usize = 256;
fn errno() -> i32 {
unsafe { *__error() }
}
pub fn fill(buf: &mut [u8]) -> io::Result<()> {
for chunk in buf.chunks_mut(MAX_PER_CALL) {
loop {
let r = unsafe { getentropy(chunk.as_mut_ptr(), chunk.len()) };
if r == 0 {
break;
}
let e = errno();
if e == EINTR {
continue;
}
return Err(io::Error::from_raw_os_error(e));
}
}
Ok(())
}
}
#[cfg(target_os = "windows")]
mod platform {
use std::io;
#[link(name = "bcrypt")]
extern "system" {
fn BCryptGenRandom(
hAlgorithm: *mut core::ffi::c_void,
pbBuffer: *mut u8,
cbBuffer: u32,
dwFlags: u32,
) -> i32;
}
const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x0000_0002;
const STATUS_SUCCESS: i32 = 0;
pub fn fill(buf: &mut [u8]) -> io::Result<()> {
for chunk in buf.chunks_mut(u32::MAX as usize) {
let status = unsafe {
BCryptGenRandom(
core::ptr::null_mut(),
chunk.as_mut_ptr(),
chunk.len() as u32,
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
)
};
if status != STATUS_SUCCESS {
return Err(io::Error::other(format!(
"BCryptGenRandom failed: NTSTATUS 0x{:08X}",
status as u32
)));
}
}
Ok(())
}
}
#[cfg(all(unix, not(any(target_os = "linux", target_os = "macos"))))]
mod platform {
use std::fs::File;
use std::io::{self, Read};
pub fn fill(buf: &mut [u8]) -> io::Result<()> {
let mut f = File::open("/dev/urandom")?;
f.read_exact(buf)
}
}
#[cfg(not(any(unix, target_os = "windows")))]
mod platform {
use std::io;
pub fn fill(_buf: &mut [u8]) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"mod-rand tier3 has no entropy source on this platform",
))
}
}
}
pub fn fill_bytes(buf: &mut [u8]) -> io::Result<()> {
if buf.is_empty() {
return Ok(());
}
sys::fill(buf)
}
pub fn random_u64() -> io::Result<u64> {
let mut buf = [0u8; 8];
fill_bytes(&mut buf)?;
Ok(u64::from_le_bytes(buf))
}
pub fn random_u32() -> io::Result<u32> {
let mut buf = [0u8; 4];
fill_bytes(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
pub fn random_bytes(len: usize) -> io::Result<Vec<u8>> {
let mut v = vec![0u8; len];
fill_bytes(&mut v)?;
Ok(v)
}
pub fn random_hex(bytes: usize) -> io::Result<String> {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut buf = vec![0u8; bytes];
fill_bytes(&mut buf)?;
let mut out = String::with_capacity(bytes * 2);
for b in buf {
out.push(HEX[(b >> 4) as usize] as char);
out.push(HEX[(b & 0xF) as usize] as char);
}
Ok(out)
}
pub fn random_base32(chars: usize) -> io::Result<String> {
const ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
let byte_count = (chars * 5).div_ceil(8);
let mut buf = vec![0u8; byte_count.max(1)];
fill_bytes(&mut buf)?;
let mut out = String::with_capacity(chars);
let mut acc: u64 = 0;
let mut bits: u32 = 0;
let mut idx = 0;
while out.len() < chars {
if bits < 5 {
acc |= (buf[idx] as u64) << bits;
bits += 8;
idx += 1;
if idx == buf.len() && bits < 5 && out.len() < chars {
let mut extra = [0u8; 8];
fill_bytes(&mut extra)?;
for &b in &extra {
acc |= (b as u64) << bits;
bits += 8;
if bits >= 5 + 56 {
break;
}
}
}
}
out.push(ALPHABET[(acc & 0x1F) as usize] as char);
acc >>= 5;
bits -= 5;
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fill_bytes_produces_output() {
let mut buf = [0u8; 32];
fill_bytes(&mut buf).unwrap();
assert!(buf.iter().any(|&b| b != 0));
}
#[test]
fn empty_buffer_succeeds_without_syscall() {
let mut buf: [u8; 0] = [];
fill_bytes(&mut buf).unwrap();
}
#[test]
fn random_u64_nonzero_majority() {
let n = random_u64().unwrap();
let m = random_u64().unwrap();
assert_ne!(n, m, "two u64 draws should differ");
}
#[test]
fn random_u32_two_draws_differ() {
let a = random_u32().unwrap();
let b = random_u32().unwrap();
assert_ne!(a, b);
}
#[test]
fn random_hex_correct_length_and_alphabet() {
let h = random_hex(16).unwrap();
assert_eq!(h.len(), 32);
assert!(h.chars().all(|c| c.is_ascii_hexdigit()));
assert!(h.chars().all(|c| !c.is_ascii_uppercase()));
}
#[test]
fn random_hex_zero_length() {
let h = random_hex(0).unwrap();
assert_eq!(h, "");
}
#[test]
fn random_base32_correct_length_and_alphabet() {
const ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
for len in [1, 5, 8, 16, 24, 32, 64] {
let s = random_base32(len).unwrap();
assert_eq!(s.len(), len, "length {len}");
assert!(
s.bytes().all(|b| ALPHABET.contains(&b)),
"alphabet violation in {s}"
);
}
}
#[test]
fn random_bytes_is_correct_length() {
let b = random_bytes(48).unwrap();
assert_eq!(b.len(), 48);
}
#[test]
fn large_buffer_fill_succeeds() {
let mut buf = vec![0u8; 4096];
fill_bytes(&mut buf).unwrap();
assert!(buf.iter().any(|&b| b != 0));
}
#[test]
fn stress_many_small_calls() {
for _ in 0..1000 {
let mut buf = [0u8; 32];
fill_bytes(&mut buf).unwrap();
}
}
#[test]
fn byte_frequency_chi_squared() {
let mut buf = vec![0u8; 1 << 20];
fill_bytes(&mut buf).unwrap();
let mut counts = [0u32; 256];
for &b in &buf {
counts[b as usize] += 1;
}
let n = buf.len() as f64;
let expected = n / 256.0;
let chi: f64 = counts
.iter()
.map(|&c| {
let diff = c as f64 - expected;
diff * diff / expected
})
.sum();
assert!(chi < 500.0, "byte-frequency chi-squared {chi} too high");
}
}