blob: fc96976566a24e090c17e5b3856c60ff2b3a4e91 [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Rust implementation of functionality parallel to libchrome's base/rand_util.h.
use std::io;
use std::thread::sleep;
use std::time::Duration;
use libc::{c_uint, c_void};
use sys_util::handle_eintr_errno;
/// How long to wait before calling getrandom again if it does not return
/// enough bytes.
const POLL_INTERVAL: Duration = Duration::from_millis(50);
/// Represents whether or not the random bytes are pulled from the source of
/// /dev/random or /dev/urandom.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Source {
// This is the default and uses the same source as /dev/urandom.
Pseudorandom,
// This uses the same source as /dev/random and may be.
Random,
}
impl Default for Source {
fn default() -> Self {
Source::Pseudorandom
}
}
impl Source {
fn to_getrandom_flags(&self) -> c_uint {
match self {
Source::Random => libc::GRND_RANDOM,
Source::Pseudorandom => 0,
}
}
}
/// Fills `output` completely with random bytes from the specified `source`.
pub fn rand_bytes(mut output: &mut [u8], source: Source) -> Result<(), io::Error> {
if output.is_empty() {
return Ok(());
}
loop {
// Safe because output is mutable and the writes are limited by output.len().
let bytes = handle_eintr_errno!(unsafe {
libc::getrandom(
output.as_mut_ptr() as *mut c_void,
output.len(),
source.to_getrandom_flags(),
)
});
if bytes < 0 {
return Err(io::Error::last_os_error());
}
if bytes as usize == output.len() {
return Ok(());
}
// Wait for more entropy and try again for the remaining bytes.
sleep(POLL_INTERVAL);
output = &mut output[bytes as usize..];
}
}
/// Allocates a vector of length `len` filled with random bytes from the
/// specified `source`.
pub fn rand_vec(len: usize, source: Source) -> Result<Vec<u8>, io::Error> {
let mut rand = Vec::with_capacity(len);
if len == 0 {
return Ok(rand);
}
// Safe because rand will either be initialized by getrandom or dropped.
unsafe { rand.set_len(len) };
rand_bytes(rand.as_mut_slice(), source)?;
Ok(rand)
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_SIZE: usize = 64;
#[test]
fn randbytes_success() {
let mut rand = vec![0u8; TEST_SIZE];
rand_bytes(&mut rand, Source::Pseudorandom).unwrap();
assert_ne!(&rand, &[0u8; TEST_SIZE]);
}
#[test]
fn randvec_success() {
let rand = rand_vec(TEST_SIZE, Source::Pseudorandom).unwrap();
assert_eq!(rand.len(), TEST_SIZE);
assert_ne!(&rand, &[0u8; TEST_SIZE]);
}
#[test]
fn sourcerandom_success() {
let rand = rand_vec(TEST_SIZE, Source::Random).unwrap();
assert_ne!(&rand, &[0u8; TEST_SIZE]);
}
}