Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Options API #353

Closed
wants to merge 3 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Draft: Options API
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 <[email protected]>
josephlr committed Mar 25, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 06b6850a28ef6b7487e8b0b75cfd11c0792042fe
6 changes: 4 additions & 2 deletions benches/buffer.rs
Original file line number Diff line number Diff line change
@@ -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);
}

25 changes: 25 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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;
@@ -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) })
}