use core::ops::{Range, RangeInclusive};
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)
}
#[inline]
fn bounded_u64(n: u64) -> io::Result<u64> {
debug_assert!(n != 0, "bounded_u64 requires n > 0");
let mut x = random_u64()?;
let mut m: u128 = (x as u128).wrapping_mul(n as u128);
let mut l: u64 = m as u64;
if l < n {
let t: u64 = n.wrapping_neg() % n;
while l < t {
x = random_u64()?;
m = (x as u128).wrapping_mul(n as u128);
l = m as u64;
}
}
Ok((m >> 64) as u64)
}
#[inline]
fn empty_range_error(msg: &'static str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, msg)
}
pub fn random_range_u64(range: Range<u64>) -> io::Result<u64> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_u64: empty range"));
}
let span = end - start;
Ok(start + bounded_u64(span)?)
}
pub fn random_range_inclusive_u64(range: RangeInclusive<u64>) -> io::Result<u64> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error("random_range_inclusive_u64: empty range"));
}
if start == 0 && end == u64::MAX {
return random_u64();
}
let span = end - start + 1;
Ok(start + bounded_u64(span)?)
}
pub fn random_range_u32(range: Range<u32>) -> io::Result<u32> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_u32: empty range"));
}
let span = (end - start) as u64;
Ok((start as u64 + bounded_u64(span)?) as u32)
}
pub fn random_range_inclusive_u32(range: RangeInclusive<u32>) -> io::Result<u32> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error("random_range_inclusive_u32: empty range"));
}
let span = (end as u64) - (start as u64) + 1;
Ok((start as u64 + bounded_u64(span)?) as u32)
}
pub fn random_range_i64(range: Range<i64>) -> io::Result<i64> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_i64: empty range"));
}
let span = (end as i128 - start as i128) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i128) + (offset as i128)) as i64)
}
pub fn random_range_inclusive_i64(range: RangeInclusive<i64>) -> io::Result<i64> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error("random_range_inclusive_i64: empty range"));
}
if start == i64::MIN && end == i64::MAX {
return random_u64().map(|u| u as i64);
}
let span = ((end as i128) - (start as i128) + 1) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i128) + (offset as i128)) as i64)
}
pub fn random_range_i32(range: Range<i32>) -> io::Result<i32> {
let Range { start, end } = range;
if start >= end {
return Err(empty_range_error("random_range_i32: empty range"));
}
let span = (end as i64 - start as i64) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i64) + (offset as i64)) as i32)
}
pub fn random_range_inclusive_i32(range: RangeInclusive<i32>) -> io::Result<i32> {
let (start, end) = range.into_inner();
if start > end {
return Err(empty_range_error("random_range_inclusive_i32: empty range"));
}
let span = ((end as i64) - (start as i64) + 1) as u64;
let offset = bounded_u64(span)?;
Ok(((start as i64) + (offset as i64)) as i32)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[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_varies_within_small_sample() {
let mut seen = HashSet::with_capacity(16);
for _ in 0..16 {
seen.insert(random_u64().unwrap());
}
assert!(seen.len() > 1, "u64 output appears constant in sample");
}
#[test]
fn random_u32_varies_within_small_sample() {
let mut seen = HashSet::with_capacity(32);
for _ in 0..32 {
seen.insert(random_u32().unwrap());
}
assert!(seen.len() > 1, "u32 output appears constant in sample");
}
#[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");
}
#[test]
fn random_range_u64_bounds() {
for _ in 0..1000 {
let n = random_range_u64(100..200).unwrap();
assert!((100..200).contains(&n));
}
}
#[test]
fn random_range_u64_single_value_window() {
for _ in 0..100 {
assert_eq!(random_range_u64(7..8).unwrap(), 7);
}
}
#[test]
fn random_range_inclusive_u64_die_roll_visits_all_faces() {
let mut faces = [0u32; 6];
for _ in 0..1000 {
let d = random_range_inclusive_u64(1..=6).unwrap();
assert!((1..=6).contains(&d));
faces[(d - 1) as usize] += 1;
}
for (i, &c) in faces.iter().enumerate() {
assert!(c > 0, "face {} never appeared in 1000 rolls", i + 1);
}
}
#[test]
fn random_range_inclusive_u64_single_value() {
for _ in 0..100 {
assert_eq!(random_range_inclusive_u64(42..=42).unwrap(), 42);
}
}
#[test]
fn random_range_inclusive_u64_full_width() {
let a = random_range_inclusive_u64(0..=u64::MAX).unwrap();
let b = random_range_inclusive_u64(0..=u64::MAX).unwrap();
assert_ne!(a, b);
}
#[test]
fn random_range_u32_bounds() {
for _ in 0..1000 {
let n = random_range_u32(0..256).unwrap();
assert!(n < 256);
}
}
#[test]
fn random_range_inclusive_u32_full_width() {
for _ in 0..100 {
let _ = random_range_inclusive_u32(0..=u32::MAX).unwrap();
}
}
#[test]
fn random_range_i64_negative() {
for _ in 0..1000 {
let n = random_range_i64(-100..-50).unwrap();
assert!((-100..-50).contains(&n));
}
}
#[test]
fn random_range_i64_mixed_sign() {
let mut saw_neg = false;
let mut saw_pos = false;
for _ in 0..1000 {
let n = random_range_i64(-100..100).unwrap();
assert!((-100..100).contains(&n));
if n < 0 {
saw_neg = true;
}
if n >= 0 {
saw_pos = true;
}
}
assert!(saw_neg && saw_pos);
}
#[test]
fn random_range_inclusive_i64_full_width() {
let _ = random_range_inclusive_i64(i64::MIN..=i64::MAX).unwrap();
}
#[test]
fn random_range_i32_bounds() {
for _ in 0..1000 {
let n = random_range_i32(-1000..1000).unwrap();
assert!((-1000..1000).contains(&n));
}
}
#[test]
fn random_range_inclusive_i32_full_width() {
for _ in 0..100 {
let _ = random_range_inclusive_i32(i32::MIN..=i32::MAX).unwrap();
}
}
#[test]
fn random_range_u64_empty_returns_invalid_input() {
let err = random_range_u64(10..10).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn random_range_u64_reverse_returns_invalid_input() {
let err = random_range_u64(10..5).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn random_range_inclusive_u64_reverse_returns_invalid_input() {
let err = random_range_inclusive_u64(10..=5).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn random_range_i64_reverse_returns_invalid_input() {
let err = random_range_i64(5..-5).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
fn random_range_uniformity_chi_squared() {
let mut counts = [0u32; 50];
for _ in 0..50_000 {
let v = random_range_u32(0..50).unwrap();
counts[v as usize] += 1;
}
let expected = 50_000.0 / 50.0;
let chi: f64 = counts
.iter()
.map(|&c| {
let diff = c as f64 - expected;
diff * diff / expected
})
.sum();
assert!(
chi < 200.0,
"tier3 chi-squared {chi} too high — bounded-range output is biased"
);
}
}