use crate::Error;
pub(crate) fn os_random(buf: &mut [u8]) -> Result<(), Error> {
imp::fill(buf)
}
#[cfg(unix)]
fn fill_urandom(buf: &mut [u8]) -> Result<(), Error> {
use std::io::Read;
std::fs::File::open("/dev/urandom")
.and_then(|mut f| f.read_exact(buf))
.map_err(|_| Error::OsRandom)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
mod imp {
use super::fill_urandom;
use crate::Error;
#[cfg(all(target_arch = "x86_64", not(target_abi = "x32")))]
const SYS_GETRANDOM: core::ffi::c_long = 318;
#[cfg(target_arch = "x86")]
const SYS_GETRANDOM: core::ffi::c_long = 355;
#[cfg(any(
target_arch = "aarch64",
target_arch = "riscv64",
target_arch = "riscv32",
target_arch = "loongarch64"
))]
const SYS_GETRANDOM: core::ffi::c_long = 278;
#[cfg(target_arch = "arm")]
const SYS_GETRANDOM: core::ffi::c_long = 384;
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
const SYS_GETRANDOM: core::ffi::c_long = 359;
#[cfg(target_arch = "s390x")]
const SYS_GETRANDOM: core::ffi::c_long = 349;
#[cfg(any(
all(target_arch = "x86_64", not(target_abi = "x32")),
target_arch = "x86",
target_arch = "aarch64",
target_arch = "riscv64",
target_arch = "riscv32",
target_arch = "loongarch64",
target_arch = "arm",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "s390x"
))]
pub fn fill(buf: &mut [u8]) -> Result<(), Error> {
unsafe extern "C" {
fn syscall(num: core::ffi::c_long, ...) -> core::ffi::c_long;
}
const EINTR: i32 = 4;
let mut done = 0;
while done < buf.len() {
let rc = unsafe {
syscall(
SYS_GETRANDOM,
buf[done..].as_mut_ptr(),
buf.len() - done,
0 as core::ffi::c_uint,
)
};
if rc > 0 {
done += rc as usize;
continue;
}
if rc == 0 {
return Err(Error::OsRandom);
}
if std::io::Error::last_os_error().raw_os_error().unwrap_or(0) == EINTR {
continue;
}
return fill_urandom(buf);
}
Ok(())
}
#[cfg(not(any(
all(target_arch = "x86_64", not(target_abi = "x32")),
target_arch = "x86",
target_arch = "aarch64",
target_arch = "riscv64",
target_arch = "riscv32",
target_arch = "loongarch64",
target_arch = "arm",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "s390x"
)))]
pub fn fill(buf: &mut [u8]) -> Result<(), Error> {
fill_urandom(buf)
}
}
#[cfg(any(target_os = "macos", target_os = "openbsd"))]
mod imp {
use super::fill_urandom;
use crate::Error;
pub fn fill(buf: &mut [u8]) -> Result<(), Error> {
unsafe extern "C" {
fn getentropy(buf: *mut u8, len: usize) -> i32;
}
let mut done = 0;
while done < buf.len() {
let chunk = (buf.len() - done).min(256);
let rc = unsafe { getentropy(buf[done..].as_mut_ptr(), chunk) };
if rc != 0 {
return fill_urandom(buf);
}
done += chunk;
}
Ok(())
}
}
#[cfg(any(
target_os = "ios",
target_os = "tvos",
target_os = "watchos",
target_os = "visionos"
))]
mod imp {
use crate::Error;
pub fn fill(buf: &mut [u8]) -> Result<(), Error> {
unsafe extern "C" {
fn CCRandomGenerateBytes(bytes: *mut core::ffi::c_void, count: usize) -> i32;
}
const SUCCESS: i32 = 0;
let rc = unsafe { CCRandomGenerateBytes(buf.as_mut_ptr().cast(), buf.len()) };
if rc == SUCCESS {
Ok(())
} else {
Err(Error::OsRandom)
}
}
}
#[cfg(windows)]
mod imp {
use crate::Error;
pub fn fill(buf: &mut [u8]) -> Result<(), Error> {
#[cfg_attr(
target_arch = "x86",
link(
name = "bcryptprimitives",
kind = "raw-dylib",
import_name_type = "undecorated"
)
)]
#[cfg_attr(
not(target_arch = "x86"),
link(name = "bcryptprimitives", kind = "raw-dylib")
)]
unsafe extern "system" {
fn ProcessPrng(pbdata: *mut u8, cbdata: usize) -> i32;
}
let rc = unsafe { ProcessPrng(buf.as_mut_ptr(), buf.len()) };
if rc != 0 {
Ok(())
} else {
Err(Error::OsRandom)
}
}
}
#[cfg(all(target_os = "wasi", target_env = "p1"))]
mod imp {
use crate::Error;
pub fn fill(buf: &mut [u8]) -> Result<(), Error> {
#[link(wasm_import_module = "wasi_snapshot_preview1")]
unsafe extern "C" {
fn random_get(buf: *mut u8, len: usize) -> u16;
}
let rc = unsafe { random_get(buf.as_mut_ptr(), buf.len()) };
if rc == 0 {
Ok(())
} else {
Err(Error::OsRandom)
}
}
}
#[cfg(all(
unix,
not(any(
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "openbsd",
target_os = "ios",
target_os = "tvos",
target_os = "watchos",
target_os = "visionos"
))
))]
mod imp {
use super::fill_urandom;
use crate::Error;
pub fn fill(buf: &mut [u8]) -> Result<(), Error> {
fill_urandom(buf)
}
}
#[cfg(not(any(unix, windows, all(target_os = "wasi", target_env = "p1"))))]
mod imp {
use crate::Error;
pub fn fill(_buf: &mut [u8]) -> Result<(), Error> {
Err(Error::OsRandom)
}
}
#[cfg(test)]
mod tests {
use super::os_random;
use alloc::vec;
#[test]
fn fills_every_length_across_the_getentropy_chunk_boundary() {
for len in 0usize..=300 {
let mut buf = vec![0xAAu8; len];
os_random(&mut buf).expect("the OS entropy source works");
if len >= 64 {
assert!(
buf.iter().any(|&b| b != 0xAA),
"len {len}: buffer was never written"
);
assert!(buf.iter().any(|&b| b != 0), "len {len}: buffer is all zero");
}
}
}
#[test]
fn successive_calls_differ() {
let mut a = [0u8; 32];
let mut b = [0u8; 32];
os_random(&mut a).expect("the OS entropy source works");
os_random(&mut b).expect("the OS entropy source works");
assert_ne!(a, b, "two 32-byte draws collided");
}
#[test]
fn zero_length_is_ok() {
os_random(&mut []).expect("a zero-length fill is trivially satisfiable");
}
}