Skip to content

Commit

Permalink
generators: Expose specific generators
Browse files Browse the repository at this point in the history
Currently users of getrandom cannot choose which specific generator they
want to use if multiple are available on their system. As the preferred
generator may vary on a case-by-case basis, it is worth exposing an
interface that enables doing so.

Without affecting the preexisting functionality, this commit adds a new
"generators" module with a macro that can be used to generate function
wrappers with the same signature as getrandom() for each specific generator.

The generated functions can be used analogously to getrandom(), as follows:

getrandom(&mut buf).unwrap(); // <-- unchanged, default getrandom usage
generators::use_file(&mut buf).unwrap();
generators::linux_android_with_fallback(&mut buf).unwrap();

Due to the way conditional compilation of modules is currently handled
in the crate, this change required a rework of lib.rs in order to split
up selecting which modules to compile based on the target and selecting
which module to use as the preferred or default one.

In order to achieve that, a new module cfg_module.rs was added to
contain a macro cfg_if_module which takes a module name and a block of
code, then resolves to a cfg_if! containing the given block conditional
on the targets that the module can be compiled for. This way the targets
for each module can be configured once within the macro and then reused
across the crate as shown in the snippet below. This approach avoids
having to keep several sets of long target config attributes in sync in
different parts of the crate and makes the code more maintainable.

cfg_if_module!(linux_android_with_fallback, {
    mod linux_android_with_fallback;
});

This change does not intend to modify the default backend selection
logic. This split also makes it possible to avoid having to repeat the
same module declarations for modules that are dependencies of more than
one backend (especially mod util_libc).

As an example, an aarch64 Linux platform with the RNDR register available
could use the use_file, linux_android, linux_android_with_fallback, rndr
or rndr_with_fallback backends. All of them will be compiled and
accessible through "mod generators", but only one of them will be
selected and compiled as "mod imp" to be the crate default.
  • Loading branch information
mrkajetanp committed Aug 2, 2024
1 parent 5edb045 commit 1a7f87c
Show file tree
Hide file tree
Showing 5 changed files with 342 additions and 15 deletions.
File renamed without changes.
230 changes: 230 additions & 0 deletions src/cfg_module.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/// Compile a block of tokens if `module` is supported on the target platform
/// This is a convenience macro in order to avoid repeating lists of
/// supported targets in `cfg_if` blocks.
///
/// The target configs in this macro are supposed to describe platform
/// compatibility for each of the modules, regardless of the policy choice
/// on which module is preferred for a specific target.
///
/// Usage:
///
/// ```rust
/// cfg_if_module!(use_file, {
/// // any code that requires use_file to be supported
/// });
/// ```
macro_rules! cfg_if_module {
( $(util_libc, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(any(
target_os = "android", target_os = "linux", target_os = "solaris",
target_os = "netbsd", target_os = "haiku", target_os = "redox",
target_os = "nto", target_os = "aix", target_os = "vxworks",
target_os = "dragonfly", target_os = "freebsd", target_os = "hurd",
target_os = "illumos", target_os = "macos", target_os = "openbsd",
target_os = "vita", target_os = "emscripten"
))] {
$($tokens)*
}
}
)*};

( $(use_file, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(any(
target_os = "linux", target_os = "android", target_os = "macos",
target_os = "freebsd", target_os = "haiku", target_os = "redox",
target_os = "nto", target_os = "aix",
))] {
$($tokens)*
}
}
)*};

( $(getentropy, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(any(
target_os = "macos", target_os = "openbsd",
target_os = "vita", target_os = "emscripten",
))] {
$($tokens)*
}
}
)*};

( $(getrandom, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(any(
target_os = "dragonfly", target_os = "freebsd",
target_os = "hurd", target_os = "illumos",
// Check for target_arch = "arm" to only include the 3DS. Does not
// include the Nintendo Switch (which is target_arch = "aarch64").
all(target_os = "horizon", target_arch = "arm"),
))] {
$($tokens)*
}
}
)*};

( $(linux_android, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(any(
target_os = "linux", target_os = "android",
))] {
$($tokens)*
}
}
)*};

( $(linux_android_with_fallback, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(all(
not(feature = "linux_disable_fallback"),
any(
// Rust supports Android API level 19 (KitKat) [0] and the next upgrade targets
// level 21 (Lollipop) [1], while `getrandom(2)` was added only in
// level 23 (Marshmallow). Note that it applies only to the "old" `target_arch`es,
// RISC-V Android targets sufficiently new API level, same will apply for potential
// new Android `target_arch`es.
// [0]: https://blog.rust-lang.org/2023/01/09/android-ndk-update-r25.html
// [1]: https://github.com/rust-lang/rust/pull/120593
all(
target_os = "android",
any(target_arch = "aarch64", target_arch = "arm",
target_arch = "x86", target_arch = "x86_64",
)
),
// Only on these `target_arch`es Rust supports Linux kernel versions (3.2+)
// that precede the version (3.17) in which `getrandom(2)` was added:
// https://doc.rust-lang.org/stable/rustc/platform-support.html
all(
target_os = "linux",
any(
target_arch = "aarch64", target_arch = "arm", target_arch = "s390x",
target_arch = "powerpc", target_arch = "powerpc64",
target_arch = "x86", target_arch = "x86_64",
// Minimum supported Linux kernel version for MUSL targets
// is not specified explicitly (as of Rust 1.77) and they
// are used in practice to target pre-3.17 kernels.
target_env = "musl",
),
)
),
))] {
$($tokens)*
}
}
)*};

( $(solaris, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(target_os = "solaris")] {
$($tokens)*
}
}
)*};

( $(netbsd, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(target_os = "netbsd")] {
$($tokens)*
}
}
)*};

( $(fuchsia, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(target_os = "fuchsia")] {
$($tokens)*
}
}
)*};

( $(apple_other, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(any(
target_os = "ios", target_os = "visionos", target_os = "watchos", target_os = "tvos"
))] {
$($tokens)*
}
}
)*};

( $(wasi, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] {
$($tokens)*
}
}
)*};

( $(hermit, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(target_os = "hermit")] {
$($tokens)*
}
}
)*};

( $(vxworks, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(target_os = "vxworks")] {
$($tokens)*
}
}
)*};

( $(solid, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(target_os = "solid_asp3")] {
$($tokens)*
}
}
)*};

( $(espidf, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(target_os = "espidf")] {
$($tokens)*
}
}
)*};

( $(windows7, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(all(windows, target_vendor = "win7"))] {
$($tokens)*
}
}
)*};

( $(windows, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(all(windows, not(target_vendor = "win7")))] {
$($tokens)*
}
}
)*};

( $(rdrand, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(any(
all(target_arch = "x86_64", target_env = "sgx"),
all(feature = "rdrand", any(target_arch = "x86_64", target_arch = "x86"))
))] {
$($tokens)*
}
}
)*};

( $(js, { $($tokens:tt)* })+ ) => {$(
cfg_if! {
if #[cfg(all(
feature = "js", target_os = "unknown",
any(target_arch = "wasm32", target_arch = "wasm64"),
))] {
$($tokens)*
}
}
)*};
}
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ impl Error {
pub const NODE_ES_MODULE: Error = internal_error(14);
/// Calling Windows ProcessPrng failed.
pub const WINDOWS_PROCESS_PRNG: Error = internal_error(15);
/// No hardware RNGs are available on this target.
pub const NO_HW: Error = internal_error(18);

/// Codes below this point represent OS Errors (i.e. positive i32 values).
/// Codes at or above this point, but below [`Error::CUSTOM_START`] are
Expand Down Expand Up @@ -175,6 +177,7 @@ fn internal_desc(error: Error) -> Option<&'static str> {
Error::NODE_RANDOM_FILL_SYNC => Some("Calling Node.js API crypto.randomFillSync failed"),
Error::NODE_ES_MODULE => Some("Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support"),
Error::WINDOWS_PROCESS_PRNG => Some("ProcessPrng: Windows system function failure"),
Error::NO_HW => Some("getrandom: No hardware RNGs are available on this target"),
_ => None,
}
}
Expand Down
67 changes: 67 additions & 0 deletions src/generators.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! Access functions for specific random number generators
use crate::getrandom;
use crate::util::slice_as_uninit_mut;
use crate::Error;

// Shared macro to generate access functions for each backend module
macro_rules! define_generator_fns {
($($module:ident),+) => {$(
cfg_if_module!($module, {
use crate::$module;
#[doc = concat!(" Access function for the ", stringify!($module), " generator.")]
pub fn $module(dest: &mut [u8]) -> Result<(), Error> {
let uninit_dest = unsafe { slice_as_uninit_mut(dest) };
if !uninit_dest.is_empty() {
$module::getrandom_inner(uninit_dest)?;
}
Ok(())
}
});
)+};
}

// Generate access functions for all backends supported by the target platform
define_generator_fns!(
use_file,
getentropy,
getrandom,
linux_android,
linux_android_with_fallback,
solaris,
netbsd,
fuchsia,
apple_other,
wasi,
hermit,
vxworks,
solid,
espidf,
windows7,
windows,
rdrand,
js
);

/// Fill `dest` with random bytes from a hardware random number generator
/// Returns an Error if no hardware RNGs are available
#[inline]
#[allow(unreachable_code)]
pub fn hardware(_dest: &mut [u8]) -> Result<(), Error> {
cfg_if_module!(rdrand, {
return rdrand(_dest);
});
Err(Error::NO_HW)
}

/// Fill `dest` with random bytes from a hardware random number generator
/// Falls back to getrandom() if no hardware RNGs are available
#[inline]
#[allow(unreachable_code)]
pub fn hardware_with_fallback(dest: &mut [u8]) -> Result<(), Error> {
if hardware(dest).is_ok() {
Ok(())
} else {
getrandom(dest)
}
}
Loading

0 comments on commit 1a7f87c

Please sign in to comment.