-
Notifications
You must be signed in to change notification settings - Fork 190
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
generators: Expose specific generators
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
1 parent
5edb045
commit 1a7f87c
Showing
5 changed files
with
342 additions
and
15 deletions.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)* | ||
} | ||
} | ||
)*}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.