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 std::sync::{Mutex, OnceLock};
use crate::ported::utils::zwarn;
use crate::random_real::random_real;
use crate::zsh_h::{features, module};
#[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();
}
}