use std::fs::metadata;
use std::io;
use std::io::Read;
use std::os::fd::IntoRawFd;
use std::os::unix::fs::FileTypeExt;
use std::sync::atomic::Ordering;
use crate::ported::utils::zwarn;
use crate::random_real::random_real;
use crate::zsh_h::{features, module};
use std::sync::{Mutex, OnceLock};
#[cfg(target_os = "macos")]
pub fn getrandom_buffer(buf: &mut [u8]) -> io::Result<()> {
unsafe {
libc::arc4random_buf(buf.as_mut_ptr() as *mut libc::c_void, buf.len());
}
Ok(())
}
thread_local! {
static RAND_BUFF: std::cell::RefCell<[u32; RAND_BUFF_SIZE]> = const {
std::cell::RefCell::new([0; RAND_BUFF_SIZE])
};
static BUF_CNT: std::cell::Cell<usize> = const {
std::cell::Cell::new(0)
};
}
#[cfg(target_os = "linux")]
pub fn getrandom_buffer(buf: &mut [u8]) -> io::Result<()> {
let mut filled = 0;
while filled < buf.len() {
let ret = unsafe {
libc::getrandom(
buf[filled..].as_mut_ptr() as *mut libc::c_void,
buf.len() - filled,
0,
)
};
if ret < 0 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(err);
}
filled += ret as usize;
}
Ok(())
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
pub fn getrandom_buffer(m: &mut [u8]) -> io::Result<()> {
let mut file = File::open("/dev/urandom")?;
file.read_exact(m)?;
Ok(())
}
pub fn get_bound_random_buffer(buffer: &mut [u32], max: u32) {
let mut bytes: Vec<u8> = vec![0u8; buffer.len() * 4];
let _ = getrandom_buffer(&mut bytes);
for (i, chunk) in bytes.chunks_exact(4).enumerate() {
buffer[i] = u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
}
if max == u32::MAX {
return; }
for i in 0..buffer.len() {
let mut multi_result: u64 = (buffer[i] as u64) * (max as u64); let mut leftover: u32 = multi_result as u32; if leftover < max {
let threshold: u32 = (max.wrapping_neg()) % max; while leftover < threshold {
let j: u32 = get_srandom(); multi_result = (j as u64) * (max as u64); leftover = multi_result as u32; }
}
buffer[i] = (multi_result >> 32) as u32; }
}
pub fn get_srandom() -> u32 {
let cnt = BUF_CNT.with(|c| c.get());
if cnt == 0 {
let mut bytes = [0u8; RAND_BUFF_SIZE * 4]; if getrandom_buffer(&mut bytes).is_ok() {
RAND_BUFF.with(|r| {
let mut buf = r.borrow_mut();
for (i, chunk) in bytes.chunks_exact(4).enumerate() {
buf[i] = u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
}
});
}
BUF_CNT.with(|c| c.set(RAND_BUFF_SIZE)); }
let new_cnt = BUF_CNT.with(|c| c.get()) - 1; BUF_CNT.with(|c| c.set(new_cnt));
RAND_BUFF.with(|r| r.borrow()[new_cnt]) }
pub fn math_zrand_int(
upper: Option<i64>,
lower: Option<i64>,
inclusive: bool,
) -> Result<i64, String> {
let lower = lower.unwrap_or(0);
let upper = upper.unwrap_or(u32::MAX as i64);
if lower < 0 || lower > u32::MAX as i64 {
return Err(format!(
"Lower bound ({}) out of range: 0-4294967295",
lower
));
}
if upper < lower {
return Err(format!(
"Upper bound ({}) must be greater than Lower Bound ({})",
upper, lower
));
}
if upper < 0 || upper > u32::MAX as i64 {
return Err(format!(
"Upper bound ({}) out of range: 0-4294967295",
upper
));
}
let incl = if inclusive { 1 } else { 0 };
let diff = (upper - lower + incl) as u32;
if diff == 0 {
return Ok(upper);
}
let r = bounded(diff);
Ok(r as i64 + lower)
}
pub fn math_zrand_float() -> f64 {
random_real()
}
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 {
match metadata("/dev/urandom") {
Ok(md) => {
if !md.file_type().is_char_device() {
zwarn("Error getting kernel random pool: not a char device");
return 1;
}
}
Err(e) => {
zwarn(&format!("Error getting kernel random pool: {}", e));
return 1;
}
}
0 }
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 {
*features = featuresarray(m, module_features());
0
}
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 {
handlefeatures(m, module_features(), enables)
}
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 {
match std::fs::OpenOptions::new().read(true).open("/dev/urandom") {
Ok(f) => {
let fd = f.into_raw_fd(); RANDFD.store(fd, Ordering::SeqCst);
0
}
Err(e) => {
zwarn(&format!("Could not access kernel random pool: {}", e));
1 }
}
}
pub fn cleanup_(m: *const module) -> i32 {
setfeatureenables(m, module_features(), None)
}
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 {
let fd = RANDFD.swap(-1, Ordering::SeqCst);
if fd >= 0 {
unsafe { libc::close(fd) }; }
0
}
const RAND_BUFF_SIZE: usize = 8;
pub static RANDFD: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(-1);
pub fn random_u32() -> u32 {
let mut buf = [0u8; 4];
let _ = getrandom_buffer(&mut buf);
u32::from_ne_bytes(buf)
}
pub fn random_u64() -> u64 {
let mut buf = [0u8; 8];
let _ = getrandom_buffer(&mut buf);
u64::from_ne_bytes(buf)
}
pub fn bounded(max: u32) -> u32 {
if max == 0 {
return 0;
}
if max == u32::MAX {
return random_u32();
}
let mut x = random_u32();
let mut m = (x as u64) * (max as u64);
let mut l = m as u32;
if l < max {
let threshold = (-(max as i64) as u64 % max as u64) as u32;
while l < threshold {
x = random_u32();
m = (x as u64) * (max as u64);
l = m as u32;
}
}
(m >> 32) as u32
}
static MODULE_FEATURES: OnceLock<Mutex<features>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<features>) -> Vec<String> {
vec![
"f:zrand_float".to_string(),
"f:zrand_int".to_string(),
"p:SRANDOM".to_string(),
]
}
fn handlefeatures(_m: *const module, _f: &Mutex<features>, enables: &mut Option<Vec<i32>>) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 3]);
}
0
}
fn setfeatureenables(_m: *const module, _f: &Mutex<features>, _e: Option<&[i32]>) -> i32 {
0
}
fn module_features() -> &'static Mutex<features> {
MODULE_FEATURES.get_or_init(|| {
Mutex::new(features {
bn_list: None,
bn_size: 0,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 2,
pd_list: None,
pd_size: 1,
n_abstract: 0,
})
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_random_state() {
let _g = crate::test_util::global_state_lock();
let r1 = get_srandom();
let r2 = get_srandom();
let r3 = get_srandom();
assert!(r1 != r2 || r2 != r3);
}
#[test]
fn test_get_random_u32() {
let _g = crate::test_util::global_state_lock();
let r1 = random_u32();
let r2 = random_u32();
let r3 = random_u32();
assert!(r1 != r2 || r2 != r3);
}
#[test]
fn test_get_random_u64() {
let _g = crate::test_util::global_state_lock();
let r1 = random_u64();
let r2 = random_u64();
assert_ne!(r1, r2);
}
#[test]
fn test_bounded_random() {
let _g = crate::test_util::global_state_lock();
for _ in 0..100 {
let r = bounded(10);
assert!(r < 10);
}
}
#[test]
fn test_bounded_random_one() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
let r = bounded(1);
assert_eq!(r, 0);
}
}
#[test]
fn test_zrand_int() {
let _g = crate::test_util::global_state_lock();
let r = math_zrand_int(Some(100), Some(50), false).unwrap();
assert!((50..100).contains(&r));
let r = math_zrand_int(Some(100), Some(50), true).unwrap();
assert!((50..=100).contains(&r));
}
#[test]
fn test_zrand_int_no_args() {
let _g = crate::test_util::global_state_lock();
let r = math_zrand_int(None, None, false).unwrap();
assert!(r >= 0);
}
#[test]
fn test_zrand_int_errors() {
let _g = crate::test_util::global_state_lock();
assert!(math_zrand_int(Some(50), Some(100), false).is_err());
assert!(math_zrand_int(Some(-1), None, false).is_err());
}
#[test]
fn test_zrand_float() {
let _g = crate::test_util::global_state_lock();
for _ in 0..100 {
let r = math_zrand_float();
assert!((0.0..1.0).contains(&r));
}
}
#[test]
fn test_random_real() {
let _g = crate::test_util::global_state_lock();
for _ in 0..100 {
let r = random_real();
assert!((0.0..1.0).contains(&r));
}
}
#[test]
fn test_shuffle() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let original = arr.clone();
let n = arr.len();
for i in (1..n).rev() {
let j = bounded((i + 1) as u32) as usize;
arr.swap(i, j);
}
arr.sort();
assert_eq!(arr, original.to_vec());
}
#[test]
fn test_fill_random_bytes() {
let _g = crate::test_util::global_state_lock();
let mut buf = [0u8; 32];
getrandom_buffer(&mut buf).unwrap();
assert!(!buf.iter().all(|&b| b == 0));
}
#[test]
fn math_zrand_int_inclusive_range_respects_bounds() {
let _g = crate::test_util::global_state_lock();
for _ in 0..50 {
let v = math_zrand_int(Some(10), Some(5), true).unwrap();
assert!(
(5..=10).contains(&v),
"value {v} out of inclusive range [5, 10]"
);
}
}
#[test]
fn math_zrand_int_exclusive_excludes_upper_bound() {
let _g = crate::test_util::global_state_lock();
for _ in 0..50 {
let v = math_zrand_int(Some(10), Some(5), false).unwrap();
assert!(
(5..10).contains(&v),
"value {v} out of exclusive range [5, 10)"
);
}
}
#[test]
fn math_zrand_float_in_unit_interval() {
let _g = crate::test_util::global_state_lock();
for _ in 0..50 {
let v = math_zrand_float();
assert!(
(0.0..1.0).contains(&v),
"value {v} out of unit interval [0.0, 1.0)"
);
}
}
#[test]
fn getrandom_buffer_two_calls_differ() {
let _g = crate::test_util::global_state_lock();
let mut a = [0u8; 32];
let mut b = [0u8; 32];
getrandom_buffer(&mut a).unwrap();
getrandom_buffer(&mut b).unwrap();
assert_ne!(a, b, "two random reads must differ (or RNG is broken)");
}
#[test]
fn random_corpus_get_srandom_varies() {
let _g = crate::test_util::global_state_lock();
let a = get_srandom();
let b = get_srandom();
let c = get_srandom();
assert!(
a != b || b != c || a != c,
"3 srandom calls all returned same value: {a} {b} {c}"
);
}
#[test]
fn random_corpus_math_zrand_float_unit_interval() {
let _g = crate::test_util::global_state_lock();
for _ in 0..50 {
let v = math_zrand_float();
assert!((0.0..1.0).contains(&v), "value {v} out of [0.0, 1.0)");
}
}
#[test]
fn random_corpus_bound_random_under_max() {
let _g = crate::test_util::global_state_lock();
let mut buf = [0u32; 50];
get_bound_random_buffer(&mut buf, 100);
for &v in &buf {
assert!(v < 100, "{v} should be < 100");
}
}
#[test]
fn random_corpus_bound_random_max_one_all_zero() {
let _g = crate::test_util::global_state_lock();
let mut buf = [0u32; 20];
get_bound_random_buffer(&mut buf, 1);
for &v in &buf {
assert_eq!(v, 0, "max=1 → all values 0, got {v}");
}
}
#[test]
fn random_corpus_getrandom_empty_buffer_no_panic() {
let _g = crate::test_util::global_state_lock();
let mut empty: [u8; 0] = [];
getrandom_buffer(&mut empty).unwrap();
}
#[test]
fn random_corpus_getrandom_single_byte() {
let _g = crate::test_util::global_state_lock();
let mut buf = [0u8; 1];
getrandom_buffer(&mut buf).unwrap();
}
#[test]
fn math_zrand_int_default_bounds_returns_ok() {
let _g = crate::test_util::global_state_lock();
let r = math_zrand_int(None, None, false).expect("default bounds → Ok");
assert!(r >= 0, "result must be ≥ 0");
assert!(r <= u32::MAX as i64, "result must fit in u32 range");
}
#[test]
fn math_zrand_int_negative_lower_returns_err() {
let _g = crate::test_util::global_state_lock();
let r = math_zrand_int(Some(100), Some(-1), false);
assert!(r.is_err(), "negative lower must error");
let msg = r.unwrap_err();
assert!(msg.contains("Lower bound"), "error mentions Lower bound");
}
#[test]
fn math_zrand_int_lower_above_u32_max_returns_err() {
let _g = crate::test_util::global_state_lock();
let r = math_zrand_int(Some(100), Some((u32::MAX as i64) + 1), false);
assert!(r.is_err(), "lower > u32::MAX must error");
}
#[test]
fn math_zrand_int_upper_below_lower_returns_err() {
let _g = crate::test_util::global_state_lock();
let r = math_zrand_int(Some(5), Some(10), false);
assert!(r.is_err());
let msg = r.unwrap_err();
assert!(
msg.contains("greater"),
"msg mentions 'greater', got: {}",
msg
);
}
#[test]
fn math_zrand_int_upper_equals_lower_returns_bound() {
let _g = crate::test_util::global_state_lock();
let r = math_zrand_int(Some(7), Some(7), false).expect("equal bounds OK");
assert_eq!(r, 7, "diff=0 → returns upper");
}
#[test]
fn math_zrand_int_inclusive_single_point_returns_lower() {
let _g = crate::test_util::global_state_lock();
let r = math_zrand_int(Some(42), Some(42), true).expect("OK");
assert_eq!(r, 42, "inclusive single-point → that point");
}
#[test]
fn math_zrand_int_result_in_range() {
let _g = crate::test_util::global_state_lock();
for _ in 0..50 {
let r = math_zrand_int(Some(10), Some(0), false).unwrap();
assert!(r >= 0 && r < 10, "exclusive [0,10): got {}", r);
}
for _ in 0..50 {
let r = math_zrand_int(Some(10), Some(0), true).unwrap();
assert!(r >= 0 && r <= 10, "inclusive [0,10]: got {}", r);
}
}
#[test]
fn math_zrand_float_in_zero_one_range() {
let _g = crate::test_util::global_state_lock();
for _ in 0..50 {
let r = math_zrand_float();
assert!(r >= 0.0 && r < 1.0, "must be in [0,1): got {}", r);
}
}
#[test]
fn math_zrand_float_produces_varied_output() {
let _g = crate::test_util::global_state_lock();
let first = math_zrand_float();
let any_different = (0..100).any(|_| math_zrand_float() != first);
assert!(
any_different,
"100 calls should produce ≥ 1 different value"
);
}
#[test]
fn get_srandom_produces_varied_values() {
let _g = crate::test_util::global_state_lock();
let first = get_srandom();
let any_different = (0..100).any(|_| get_srandom() != first);
assert!(
any_different,
"100 calls should produce ≥ 1 different value"
);
}
#[test]
fn getrandom_buffer_empty_buffer_returns_ok() {
let _g = crate::test_util::global_state_lock();
let mut buf: [u8; 0] = [];
assert!(getrandom_buffer(&mut buf).is_ok());
}
#[test]
fn getrandom_buffer_returns_io_result_type() {
let _g = crate::test_util::global_state_lock();
let mut buf = [0u8; 1];
let _: io::Result<()> = getrandom_buffer(&mut buf);
}
#[test]
fn getrandom_buffer_fills_with_nonzero_bytes() {
let _g = crate::test_util::global_state_lock();
let mut buf = [0u8; 256];
getrandom_buffer(&mut buf).unwrap();
assert!(
buf.iter().any(|&b| b != 0),
"256 random bytes should have ≥ 1 non-zero"
);
}
#[test]
fn get_bound_random_buffer_max_one_all_zero() {
let _g = crate::test_util::global_state_lock();
let mut buf = [0u32; 100];
get_bound_random_buffer(&mut buf, 1);
for &v in &buf {
assert_eq!(v, 0, "max=1 → all values must be 0");
}
}
#[test]
fn get_bound_random_buffer_respects_max_bound() {
let _g = crate::test_util::global_state_lock();
let mut buf = [0u32; 1000];
let max = 100u32;
get_bound_random_buffer(&mut buf, max);
for &v in &buf {
assert!(v < max, "value {} must be < max {}", v, max);
}
}
#[test]
fn get_srandom_returns_u32_type() {
let _g = crate::test_util::global_state_lock();
let _: u32 = get_srandom();
}
#[test]
fn math_zrand_float_strictly_in_half_open_unit() {
let _g = crate::test_util::global_state_lock();
for _ in 0..50 {
let v = math_zrand_float();
assert!(
v >= 0.0 && v < 1.0,
"math_zrand_float = {} must be in [0.0, 1.0)",
v
);
}
}
#[test]
fn random_full_lifecycle_returns_zero_for_all() {
let _g = crate::test_util::global_state_lock();
let null = std::ptr::null();
assert_eq!(setup_(null), 0);
let mut feats = Vec::new();
let _ = features_(null, &mut feats);
let mut enables: Option<Vec<i32>> = None;
let _ = enables_(null, &mut enables);
assert_eq!(boot_(null), 0);
assert_eq!(cleanup_(null), 0);
assert_eq!(finish_(null), 0);
}
#[test]
fn random_u32_produces_varied_values() {
let _g = crate::test_util::global_state_lock();
let first = random_u32();
let any_diff = (0..100).any(|_| random_u32() != first);
assert!(any_diff, "100 u32 randoms should differ from first");
}
#[test]
fn random_u64_produces_varied_values() {
let _g = crate::test_util::global_state_lock();
let first = random_u64();
let any_diff = (0..100).any(|_| random_u64() != first);
assert!(any_diff, "100 u64 randoms should differ from first");
}
#[test]
fn random_u32_returns_u32_type() {
let _g = crate::test_util::global_state_lock();
let _: u32 = random_u32();
}
#[test]
fn random_u64_returns_u64_type() {
let _g = crate::test_util::global_state_lock();
let _: u64 = random_u64();
}
#[test]
fn bounded_zero_max_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(bounded(0), 0, "max=0 always returns 0");
}
#[test]
fn bounded_one_max_always_zero() {
let _g = crate::test_util::global_state_lock();
for _ in 0..50 {
assert_eq!(bounded(1), 0, "max=1 → result must be 0");
}
}
#[test]
fn bounded_result_strictly_less_than_max() {
let _g = crate::test_util::global_state_lock();
for &max in &[2u32, 10, 100, 1000, 1_000_000] {
for _ in 0..20 {
let v = bounded(max);
assert!(v < max, "bounded({}) = {} must be < max", max, v);
}
}
}
#[test]
fn bounded_returns_u32_type() {
let _g = crate::test_util::global_state_lock();
let _: u32 = bounded(100);
}
#[test]
fn bounded_u32_max_returns_u32_range() {
let _g = crate::test_util::global_state_lock();
let _: u32 = bounded(u32::MAX);
}
#[test]
fn random_setup_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = setup_(std::ptr::null());
}
#[test]
fn random_features_canonical_three_entries() {
let _g = crate::test_util::global_state_lock();
let mut feats = Vec::new();
features_(std::ptr::null(), &mut feats);
assert_eq!(feats.len(), 3, "random advertises 3 features");
assert!(
feats.iter().any(|f| f == "f:zrand_float"),
"must contain f:zrand_float"
);
assert!(
feats.iter().any(|f| f == "f:zrand_int"),
"must contain f:zrand_int"
);
assert!(
feats.iter().any(|f| f == "p:SRANDOM"),
"must contain p:SRANDOM"
);
}
#[test]
fn random_cleanup_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(cleanup_(std::ptr::null()), 0);
}
}
#[test]
fn random_finish_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(finish_(std::ptr::null()), 0);
}
}
#[test]
fn random_boot_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(boot_(std::ptr::null()), 0);
}
}
#[test]
fn get_srandom_returns_u32_pin_alt() {
let _g = crate::test_util::global_state_lock();
let _: u32 = get_srandom();
}
#[test]
fn get_srandom_two_calls_differ() {
let _g = crate::test_util::global_state_lock();
let a = get_srandom();
let b = get_srandom();
assert_ne!(a, b, "two get_srandom() calls must differ");
}
#[test]
fn random_u32_returns_u32_pin_alt() {
let _g = crate::test_util::global_state_lock();
let _: u32 = random_u32();
}
#[test]
fn random_u64_returns_u64_pin_alt() {
let _g = crate::test_util::global_state_lock();
let _: u64 = random_u64();
}
#[test]
fn random_u64_eventually_exceeds_u32_max() {
let _g = crate::test_util::global_state_lock();
let any_large = (0..200).any(|_| random_u64() > (u32::MAX as u64));
assert!(
any_large,
"200 random_u64 values must include ≥ 1 above u32::MAX"
);
}
#[test]
fn math_zrand_float_returns_f64_type() {
let _g = crate::test_util::global_state_lock();
let _: f64 = math_zrand_float();
}
#[test]
fn math_zrand_float_always_finite() {
let _g = crate::test_util::global_state_lock();
for _ in 0..500 {
let v = math_zrand_float();
assert!(
v.is_finite(),
"math_zrand_float must always be finite, got {}",
v
);
}
}
#[test]
fn bounded_one_always_returns_zero() {
let _g = crate::test_util::global_state_lock();
for _ in 0..50 {
assert_eq!(
bounded(1),
0,
"bounded(1) must always return 0 (only valid value)"
);
}
}
#[test]
fn bounded_two_returns_zero_or_one() {
let _g = crate::test_util::global_state_lock();
for _ in 0..50 {
let v = bounded(2);
assert!(v < 2, "bounded(2) must be 0 or 1; got {}", v);
}
}
#[test]
fn get_bound_random_buffer_all_under_max() {
let _g = crate::test_util::global_state_lock();
let mut buf = vec![0u32; 100];
get_bound_random_buffer(&mut buf, 10);
for &v in &buf {
assert!(v < 10, "buffer value {} must be < max=10", v);
}
}
#[test]
fn random_each_lifecycle_hook_returns_zero_individually() {
let _g = crate::test_util::global_state_lock();
let null = std::ptr::null();
let mut v: Vec<String> = Vec::new();
let mut e: Option<Vec<i32>> = None;
assert_eq!(setup_(null), 0, "c:226 setup_");
assert_eq!(features_(null, &mut v), 0, "c:249 features_");
assert_eq!(enables_(null, &mut e), 0, "c:256 enables_");
assert_eq!(boot_(null), 0, "c:263 boot_");
assert_eq!(cleanup_(null), 0, "c:296 cleanup_");
assert_eq!(finish_(null), 0, "c:303 finish_");
}
}