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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/tests.yml
Expand Up @@ -8,6 +8,9 @@ on:
schedule:
- cron: "0 12 * * 1"

permissions:
contents: read

env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-Dwarnings"
Expand Down
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()
}
116 changes: 74 additions & 42 deletions src/lib.rs
Expand Up @@ -291,52 +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> {
// SAFETY: The `&mut MaybeUninit<_>` reference doesn't escape, and
// `getrandom_uninit` guarantees it will never de-initialize any part of
// `dest`.
getrandom_uninit(unsafe { slice_as_uninit_mut(dest) })?;
Ok(())
Options::DEFAULT.fill(dest)
}

/// Version of the `getrandom` function which fills `dest` with random bytes
/// returns a mutable reference to those bytes.
///
/// On successful completion this function is guaranteed to return a slice
/// which points to the same memory as `dest` and has the same length.
/// In other words, it's safe to assume that `dest` is initialized after
/// this function has returned `Ok`.
///
/// No part of `dest` will ever be de-initialized at any point, regardless
/// of what is returned.
/// Options for specifying how random bytes should be generated.
///
/// # Examples
///
/// ```ignore
/// # // We ignore this test since `uninit_array` is unstable.
/// #![feature(maybe_uninit_uninit_array)]
/// # fn main() -> Result<(), getrandom::Error> {
/// let mut buf = core::mem::MaybeUninit::uninit_array::<1024>();
/// let buf: &mut [u8] = getrandom::getrandom_uninit(&mut buf)?;
/// # Ok(()) }
/// ```
#[inline]
pub fn getrandom_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<&mut [u8], Error> {
if !dest.is_empty() {
imp::getrandom_inner(dest)?;
/// 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: `dest` has been fully initialized by `imp::getrandom_inner`
// since it returned `Ok`.
Ok(unsafe { slice_assume_init_mut(dest) })
}