use std::error::Error;
use std::fmt;
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
compile_error!("lgwks_std::random supports linux, macOS, and windows only.");
#[derive(Debug)]
pub struct EntropyError {
backend: &'static str,
cause: String,
}
impl EntropyError {
#[must_use]
pub fn backend(&self) -> &'static str {
self.backend
}
}
impl fmt::Display for EntropyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"could not read OS entropy from {}: {}",
self.backend, self.cause
)
}
}
impl Error for EntropyError {}
pub fn fill_bytes(buf: &mut [u8]) -> Result<(), EntropyError> {
if buf.is_empty() {
return Ok(());
}
getrandom::fill(buf).map_err(|cause| EntropyError {
backend: "getrandom",
cause: cause.to_string(),
})
}
pub fn bytes<const N: usize>() -> Result<[u8; N], EntropyError> {
let mut out = [0u8; N];
fill_bytes(&mut out)?;
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fills_the_whole_buffer() -> Result<(), EntropyError> {
let mut buf = [0xAAu8; 64];
fill_bytes(&mut buf)?;
assert!(
buf.iter().any(|&byte_value| byte_value != 0xAA),
"buffer looks unwritten"
);
Ok(())
}
#[test]
fn successive_draws_differ() -> Result<(), EntropyError> {
let first = bytes::<32>()?;
let second = bytes::<32>()?;
assert_ne!(first, second);
Ok(())
}
#[test]
fn empty_buffer_is_a_no_op() {
assert!(fill_bytes(&mut []).is_ok());
}
}