Skip to content

Commit 06b6850

Browse files
committed
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]>
1 parent 7f37234 commit 06b6850

File tree

3 files changed

+108
-18
lines changed

3 files changed

+108
-18
lines changed

benches/buffer.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ extern crate test;
33

44
use std::mem::MaybeUninit;
55

6+
use getrandom::Options;
7+
68
// Call getrandom on a zero-initialized stack buffer
79
#[inline(always)]
810
fn bench_getrandom<const N: usize>() {
911
let mut buf = [0u8; N];
10-
getrandom::getrandom(&mut buf).unwrap();
12+
Options::DEFAULT.fill(&mut buf).unwrap();
1113
test::black_box(&buf as &[u8]);
1214
}
1315

1416
// Call getrandom_uninit on an uninitialized stack buffer
1517
#[inline(always)]
1618
fn bench_getrandom_uninit<const N: usize>() {
1719
let mut uninit = [MaybeUninit::uninit(); N];
18-
let buf: &[u8] = getrandom::getrandom_uninit(&mut uninit).unwrap();
20+
let buf: &[u8] = Options::DEFAULT.fill_uninit(&mut uninit).unwrap();
1921
test::black_box(buf);
2022
}
2123

build.rs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use std::env;
2+
use std::process::Command;
3+
use std::str;
4+
5+
fn main() {
6+
let minor_ver = rustc_minor().expect("failed to get rustc version");
7+
8+
if minor_ver >= 40 {
9+
println!("cargo:rustc-cfg=getrandom_non_exhaustive");
10+
}
11+
}
12+
13+
// Based on libc's implementation:
14+
// https://github.com/rust-lang/libc/blob/74e81a50c2528b01507e9d03f594949c0f91c817/build.rs#L168-L205
15+
fn rustc_minor() -> Option<u32> {
16+
let rustc = env::var_os("RUSTC")?;
17+
let output = Command::new(rustc).arg("--version").output().ok()?.stdout;
18+
let version = str::from_utf8(&output).ok()?;
19+
let mut pieces = version.split('.');
20+
if pieces.next()? != "rustc 1" {
21+
return None;
22+
}
23+
let minor = pieces.next()?;
24+
minor.parse().ok()
25+
}

src/lib.rs

+79-16
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@
193193
#[macro_use]
194194
extern crate cfg_if;
195195

196-
use crate::util::slice_as_uninit_mut;
196+
use crate::util::{slice_as_uninit_mut, slice_assume_init_mut};
197+
use core::mem::MaybeUninit;
197198

198199
mod error;
199200
mod util;
@@ -290,22 +291,84 @@ cfg_if! {
290291
/// Fill `dest` with random bytes from the system's preferred random number
291292
/// source.
292293
///
293-
/// This function returns an error on any failure, including partial reads. We
294-
/// make no guarantees regarding the contents of `dest` on error. If `dest` is
295-
/// empty, `getrandom` immediately returns success, making no calls to the
296-
/// underlying operating system.
297-
///
298-
/// Blocking is possible, at least during early boot; see module documentation.
299-
///
300-
/// In general, `getrandom` will be fast enough for interactive usage, though
301-
/// significantly slower than a user-space CSPRNG; for the latter consider
302-
/// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html).
294+
/// Convinence alias for `Options::DEFAULT.fill(dest)`. For more info, see
295+
/// [`Options::DEFAULT`] and [`Options::fill`].
303296
#[inline]
304297
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
305-
if dest.is_empty() {
306-
return Ok(());
298+
Options::DEFAULT.fill(dest)
299+
}
300+
301+
/// Options for specifying how random bytes should be generated.
302+
///
303+
/// Currently, [`Options::DEFAULT`] is the only allowed option, but we may add
304+
/// additional options in the future (hense why this enum is `non_exhaustive`).
305+
#[derive(Clone, Copy, Debug)]
306+
#[cfg_attr(getrandom_non_exhaustive, non_exhaustive)]
307+
pub enum Options {
308+
/// Use the system's preferred random number source.
309+
///
310+
/// This implementation is garunteed to produce cryptographically random
311+
/// bytes on success. However, it may block in order to do so,
312+
/// [especially during early boot](https://docs.rs/getrandom#early-boot).
313+
///
314+
/// In general, this sources will be fast enough for
315+
/// interactive usage, though significantly slower than a user-space CSPRNG.
316+
/// For a user-space CSPRNG seeded from this source, consider
317+
/// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html).
318+
DEFAULT,
319+
}
320+
321+
impl Options {
322+
/// Fill `dest` with random bytes.
323+
///
324+
/// This function returns an error on any failure, including partial reads.
325+
/// We make no guarantees regarding the contents of `dest` on error. If
326+
/// `dest` is empty, immediately return success, making no calls to the
327+
/// underlying system RNG source.
328+
#[inline]
329+
pub fn fill(self, dest: &mut [u8]) -> Result<(), Error> {
330+
if dest.is_empty() {
331+
return Ok(());
332+
}
333+
// SAFETY: The &mut [MaybeUninit<u8>] reference doesn't escape, and
334+
// `getrandom_inner` will never de-initialize any part of `dest`.
335+
imp::getrandom_inner(unsafe { slice_as_uninit_mut(dest) })
336+
}
337+
338+
/// Initialize `dest` with random bytes.
339+
///
340+
/// On success, this function is guaranteed to return a slice which points
341+
/// to the same memory as `dest` and has the same length. In this case, it
342+
/// is safe to assume that `dest` is initialized.
343+
///
344+
/// On either success or failure, no part of `dest` will ever be
345+
/// de-initialized at any point.
346+
///
347+
/// # Examples
348+
///
349+
/// ```
350+
/// # use core::mem::MaybeUninit;
351+
/// # fn uninit_example() -> Result<(), getrandom::Error> {
352+
/// use getrandom::Options;
353+
///
354+
/// let mut buf = [MaybeUninit::<u8>::uninit(); 1024];
355+
/// let buf: &mut [u8] = Options::DEFAULT.fill_uninit(&mut buf)?;
356+
/// # Ok(()) }
357+
/// # uninit_example().unwrap();
358+
/// ```
359+
#[inline]
360+
pub fn fill_uninit(self, dest: &mut [MaybeUninit<u8>]) -> Result<&mut [u8], Error> {
361+
if !dest.is_empty() {
362+
imp::getrandom_inner(dest)?;
363+
}
364+
// SAFETY: `dest` has been fully initialized by `imp::getrandom_inner`
365+
Ok(unsafe { slice_assume_init_mut(dest) })
366+
}
367+
}
368+
369+
// TODO(MSRV 1.62): Use #[derive(Default)]
370+
impl Default for Options {
371+
fn default() -> Self {
372+
Self::DEFAULT
307373
}
308-
// SAFETY: The &mut [MaybeUninit<u8>] reference doesn't escape, and
309-
// `getrandom_inner` will never de-initialize any part of `dest`.
310-
imp::getrandom_inner(unsafe { slice_as_uninit_mut(dest) })
311374
}

0 commit comments

Comments
 (0)