Skip to content

Commit

Permalink
Draft: Options API
Browse files Browse the repository at this point in the history
Right now we only have a single option `Option::DEFAULT`.

However, in the future, we could add options to:
  - guarantee the function doesn't block
  - use a insecure but quicker system source (for seeding hashmaps).
  - not use a fallback mechanism
  - never use the custom RNG source
  - always prefere the custom RNG source if it exists

Not all of these are things we should _necessarily_ do, but this gives
us the flexibility to add such options in the future.

Signed-off-by: Joe Richey <joerichey@google.com>
  • Loading branch information
josephlr committed Mar 25, 2023
1 parent 7f37234 commit 06b6850
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 18 deletions.
6 changes: 4 additions & 2 deletions benches/buffer.rs
Expand Up @@ -3,19 +3,21 @@ extern crate test;

use std::mem::MaybeUninit;

use getrandom::Options;

// Call getrandom on a zero-initialized stack buffer
#[inline(always)]
fn bench_getrandom<const N: usize>() {
let mut buf = [0u8; N];
getrandom::getrandom(&mut buf).unwrap();
Options::DEFAULT.fill(&mut buf).unwrap();
test::black_box(&buf as &[u8]);
}

// Call getrandom_uninit on an uninitialized stack buffer
#[inline(always)]
fn bench_getrandom_uninit<const N: usize>() {
let mut uninit = [MaybeUninit::uninit(); N];
let buf: &[u8] = getrandom::getrandom_uninit(&mut uninit).unwrap();
let buf: &[u8] = Options::DEFAULT.fill_uninit(&mut uninit).unwrap();
test::black_box(buf);
}

Expand Down
25 changes: 25 additions & 0 deletions build.rs
@@ -0,0 +1,25 @@
use std::env;
use std::process::Command;
use std::str;

fn main() {
let minor_ver = rustc_minor().expect("failed to get rustc version");

if minor_ver >= 40 {
println!("cargo:rustc-cfg=getrandom_non_exhaustive");
}
}

// Based on libc's implementation:
// https://github.com/rust-lang/libc/blob/74e81a50c2528b01507e9d03f594949c0f91c817/build.rs#L168-L205
fn rustc_minor() -> Option<u32> {
let rustc = env::var_os("RUSTC")?;
let output = Command::new(rustc).arg("--version").output().ok()?.stdout;
let version = str::from_utf8(&output).ok()?;
let mut pieces = version.split('.');
if pieces.next()? != "rustc 1" {
return None;
}
let minor = pieces.next()?;
minor.parse().ok()
}
95 changes: 79 additions & 16 deletions src/lib.rs
Expand Up @@ -193,7 +193,8 @@
#[macro_use]
extern crate cfg_if;

use crate::util::slice_as_uninit_mut;
use crate::util::{slice_as_uninit_mut, slice_assume_init_mut};
use core::mem::MaybeUninit;

mod error;
mod util;
Expand Down Expand Up @@ -290,22 +291,84 @@ cfg_if! {
/// Fill `dest` with random bytes from the system's preferred random number
/// source.
///
/// This function returns an error on any failure, including partial reads. We
/// make no guarantees regarding the contents of `dest` on error. If `dest` is
/// empty, `getrandom` immediately returns success, making no calls to the
/// underlying operating system.
///
/// Blocking is possible, at least during early boot; see module documentation.
///
/// In general, `getrandom` will be fast enough for interactive usage, though
/// significantly slower than a user-space CSPRNG; for the latter consider
/// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html).
/// Convinence alias for `Options::DEFAULT.fill(dest)`. For more info, see
/// [`Options::DEFAULT`] and [`Options::fill`].
#[inline]
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
if dest.is_empty() {
return Ok(());
Options::DEFAULT.fill(dest)
}

/// Options for specifying how random bytes should be generated.
///
/// Currently, [`Options::DEFAULT`] is the only allowed option, but we may add
/// additional options in the future (hense why this enum is `non_exhaustive`).
#[derive(Clone, Copy, Debug)]
#[cfg_attr(getrandom_non_exhaustive, non_exhaustive)]
pub enum Options {
/// Use the system's preferred random number source.
///
/// This implementation is garunteed to produce cryptographically random
/// bytes on success. However, it may block in order to do so,
/// [especially during early boot](https://docs.rs/getrandom#early-boot).
///
/// In general, this sources will be fast enough for
/// interactive usage, though significantly slower than a user-space CSPRNG.
/// For a user-space CSPRNG seeded from this source, consider
/// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html).
DEFAULT,
}

impl Options {
/// Fill `dest` with random bytes.
///
/// This function returns an error on any failure, including partial reads.
/// We make no guarantees regarding the contents of `dest` on error. If
/// `dest` is empty, immediately return success, making no calls to the
/// underlying system RNG source.
#[inline]
pub fn fill(self, dest: &mut [u8]) -> Result<(), Error> {
if dest.is_empty() {
return Ok(());
}
// SAFETY: The &mut [MaybeUninit<u8>] reference doesn't escape, and
// `getrandom_inner` will never de-initialize any part of `dest`.
imp::getrandom_inner(unsafe { slice_as_uninit_mut(dest) })
}

/// Initialize `dest` with random bytes.
///
/// On success, this function is guaranteed to return a slice which points
/// to the same memory as `dest` and has the same length. In this case, it
/// is safe to assume that `dest` is initialized.
///
/// On either success or failure, no part of `dest` will ever be
/// de-initialized at any point.
///
/// # Examples
///
/// ```
/// # use core::mem::MaybeUninit;
/// # fn uninit_example() -> Result<(), getrandom::Error> {
/// use getrandom::Options;
///
/// let mut buf = [MaybeUninit::<u8>::uninit(); 1024];
/// let buf: &mut [u8] = Options::DEFAULT.fill_uninit(&mut buf)?;
/// # Ok(()) }
/// # uninit_example().unwrap();
/// ```
#[inline]
pub fn fill_uninit(self, dest: &mut [MaybeUninit<u8>]) -> Result<&mut [u8], Error> {
if !dest.is_empty() {
imp::getrandom_inner(dest)?;
}
// SAFETY: `dest` has been fully initialized by `imp::getrandom_inner`
Ok(unsafe { slice_assume_init_mut(dest) })
}
}

// TODO(MSRV 1.62): Use #[derive(Default)]
impl Default for Options {
fn default() -> Self {
Self::DEFAULT
}
// SAFETY: The &mut [MaybeUninit<u8>] reference doesn't escape, and
// `getrandom_inner` will never de-initialize any part of `dest`.
imp::getrandom_inner(unsafe { slice_as_uninit_mut(dest) })
}

0 comments on commit 06b6850

Please sign in to comment.