diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a8717d7f..0c9f3f15 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,6 +8,9 @@ on: schedule: - cron: "0 12 * * 1" +permissions: + contents: read + env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Dwarnings" diff --git a/benches/buffer.rs b/benches/buffer.rs index b32be433..1e3e78e6 100644 --- a/benches/buffer.rs +++ b/benches/buffer.rs @@ -3,11 +3,13 @@ extern crate test; use std::mem::MaybeUninit; +use getrandom::Options; + // Call getrandom on a zero-initialized stack buffer #[inline(always)] fn bench_getrandom() { let mut buf = [0u8; N]; - getrandom::getrandom(&mut buf).unwrap(); + Options::DEFAULT.fill(&mut buf).unwrap(); test::black_box(&buf as &[u8]); } @@ -15,7 +17,7 @@ fn bench_getrandom() { #[inline(always)] fn bench_getrandom_uninit() { 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); } diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..cd43fcae --- /dev/null +++ b/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 { + 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() +} diff --git a/src/lib.rs b/src/lib.rs index 26490e69..08cb0e16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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]) -> 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] 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::::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]) -> 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) }) }