use crate::error::{Error, Result};
use rand::{CryptoRng as RandCryptoRng, RngCore, SeedableRng};
use rand_chacha::ChaCha20Rng;
use std::sync::Mutex;
pub use rand::rngs::OsRng;
pub trait CryptoRng: RandCryptoRng + RngCore {}
impl<T: RandCryptoRng + RngCore> CryptoRng for T {}
pub fn random_bytes(len: usize) -> Vec<u8> {
let mut buf = vec![0u8; len];
OsRng.fill_bytes(&mut buf);
buf
}
pub fn random_array<const N: usize>() -> [u8; N] {
let mut buf = [0u8; N];
OsRng.fill_bytes(&mut buf);
buf
}
pub fn random_u64() -> u64 {
let mut buf = [0u8; 8];
OsRng.fill_bytes(&mut buf);
u64::from_le_bytes(buf)
}
pub fn random_u32() -> u32 {
let mut buf = [0u8; 4];
OsRng.fill_bytes(&mut buf);
u32::from_le_bytes(buf)
}
pub fn random_u128() -> u128 {
let mut buf = [0u8; 16];
OsRng.fill_bytes(&mut buf);
u128::from_le_bytes(buf)
}
pub fn random_range(max: u64) -> u64 {
if max == 0 {
return 0;
}
let threshold = u64::MAX - (u64::MAX % max);
loop {
let value = random_u64();
if value < threshold {
return value % max;
}
}
}
pub fn try_fill_bytes(dest: &mut [u8]) -> Result<()> {
getrandom::getrandom(dest).map_err(|_| Error::RngFailed)
}
pub struct DeterministicRng {
inner: ChaCha20Rng,
}
impl DeterministicRng {
pub fn from_seed(seed: [u8; 32]) -> Self {
Self {
inner: ChaCha20Rng::from_seed(seed),
}
}
pub fn from_u64(seed: u64) -> Self {
let mut full_seed = [0u8; 32];
full_seed[..8].copy_from_slice(&seed.to_le_bytes());
Self::from_seed(full_seed)
}
}
impl RngCore for DeterministicRng {
fn next_u32(&mut self) -> u32 {
self.inner.next_u32()
}
fn next_u64(&mut self) -> u64 {
self.inner.next_u64()
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.inner.fill_bytes(dest)
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> std::result::Result<(), rand::Error> {
self.inner.try_fill_bytes(dest)
}
}
pub struct ThreadLocalRng {
rng: Mutex<ChaCha20Rng>,
}
impl ThreadLocalRng {
pub fn new() -> Self {
Self {
rng: Mutex::new(ChaCha20Rng::from_entropy()),
}
}
pub fn fill_bytes(&self, dest: &mut [u8]) {
self.rng
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.fill_bytes(dest);
}
pub fn random_array<const N: usize>(&self) -> [u8; N] {
let mut buf = [0u8; N];
self.fill_bytes(&mut buf);
buf
}
pub fn reseed(&self) {
*self
.rng
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()) = ChaCha20Rng::from_entropy();
}
}
impl Default for ThreadLocalRng {
fn default() -> Self {
Self::new()
}
}
thread_local! {
static THREAD_RNG: ThreadLocalRng = ThreadLocalRng::new();
}
pub fn thread_random_bytes(len: usize) -> Vec<u8> {
let mut buf = vec![0u8; len];
THREAD_RNG.with(|rng| rng.fill_bytes(&mut buf));
buf
}
pub fn thread_random_array<const N: usize>() -> [u8; N] {
THREAD_RNG.with(|rng| rng.random_array())
}
pub fn mix_entropy(sources: &[&[u8]]) -> [u8; 32] {
use blake3::Hasher;
let mut hasher = Hasher::new();
let mut os_entropy = [0u8; 32];
OsRng.fill_bytes(&mut os_entropy);
hasher.update(&os_entropy);
for source in sources {
hasher.update(&(source.len() as u64).to_le_bytes());
hasher.update(source);
}
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
hasher.update(×tamp.to_le_bytes());
*hasher.finalize().as_bytes()
}
pub fn random_id(bytes: usize) -> String {
hex::encode(random_bytes(bytes))
}
pub fn random_alphanumeric(len: usize) -> String {
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
(0..len)
.map(|_| {
let idx = random_range(CHARSET.len() as u64) as usize;
CHARSET[idx] as char
})
.collect()
}
pub fn random_token(bytes: usize) -> String {
<base64ct::Base64UrlUnpadded as base64ct::Encoding>::encode_string(&random_bytes(bytes))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_random_bytes() {
let b1 = random_bytes(32);
let b2 = random_bytes(32);
assert_ne!(b1, b2);
assert_eq!(b1.len(), 32);
}
#[test]
fn test_random_array() {
let arr: [u8; 16] = random_array();
assert_eq!(arr.len(), 16);
}
#[test]
fn test_random_range() {
for _ in 0..1000 {
let value = random_range(100);
assert!(value < 100);
}
}
#[test]
fn test_deterministic_rng() {
let mut rng1 = DeterministicRng::from_u64(42);
let mut rng2 = DeterministicRng::from_u64(42);
let mut buf1 = [0u8; 32];
let mut buf2 = [0u8; 32];
rng1.fill_bytes(&mut buf1);
rng2.fill_bytes(&mut buf2);
assert_eq!(buf1, buf2);
}
#[test]
fn test_thread_local_rng() {
let b1 = thread_random_bytes(32);
let b2 = thread_random_bytes(32);
assert_ne!(b1, b2);
}
#[test]
fn test_mix_entropy() {
let mixed1 = mix_entropy(&[b"source1", b"source2"]);
let mixed2 = mix_entropy(&[b"source1", b"source2"]);
assert_ne!(mixed1, mixed2);
}
#[test]
fn test_random_id() {
let id = random_id(16);
assert_eq!(id.len(), 32); }
#[test]
fn test_random_alphanumeric() {
let s = random_alphanumeric(20);
assert_eq!(s.len(), 20);
assert!(s.chars().all(|c| c.is_alphanumeric()));
}
}