Repository: rust-random/getrandom (rust)

Issue:
Feature for using CPU-based RNG
For some custom x86 targets I am working on (https://github.com/intel/rust-hypervisor-firmware), it would be nice to have a feature that says "just use the on-CPU RNG". 

Having a feature for this seems better than adding more targets to the list of targets that use RDRAND. It would also be useful more conventional targets (i.e. Linux) that don't want to use a syscall to get random data (i.e. for performance).

Pull request description:
Add Cargo feature for CPU randomness
Closes #84 

## `cpu` Cargo feature
Many CPU architectures support instructions to get randomness:
- `x86`/`x86_64` have the well-known [RDRAND/RDSEED](https://en.wikipedia.org/wiki/RDRAND)
- ARMv8.5-RNG recently added [RNDR](https://developer.arm.com/docs/ddi0595/c/aarch64-system-registers/rndr) ([LLVM patch](https://reviews.llvm.org/D52481), [Linux patch](https://lwn.net/Articles/809929/))
- PowerPC ([version 3.0B of the ISA](https://openpowerfoundation.org/?resource_lib=power-isa-version-3-0), i.e. POWER9 and later) has DARN (Deliver A Random Number)  
- [S390 supports PRNO](https://fossies.org/linux/binutils/opcodes/s390-opc.txt) (perform PseudoRandom Number Operation) [Linux code](https://elixir.bootlin.com/linux/v5.5.4/source/arch/s390/include/asm/cpacf.h#L443)

This PR adds a `cpu` Cargo feature, which calls those instructions if there is not a supported implementation. Right now we only support `x86_64`. This `cpu` feature is on by default (as there's no real reason to disable it, other than wanting to make sure all buildable targets have a concrete implementations). This feature takes lower priority than `custom` implementations. This is necessary to have specific RNG implementations on unsupported platforms.

We can now drop explicit support for targets where we used RDRAND simply because we didn't know what else to use. This includes:
- `x86_64-unknown-hermit`
- `x86_64-unknown-l4re-uclibc`
- `x86_64-unknown-uefi`

As the `cpu` feature is on by default, these targets will keep working by default, but they can now be overridden by `custom` implementations.

We continue to _explicitly_ support all SGX targets. This means that `custom` implementations _cannot_, override the RDRAND implementation. Intel makes it very clear you have to use RDRAND to get randomness on SGX. The other targets don't have this mandate. For example, UEFI could get it's randomness form either RDRAND or [EFI_RNG_PROTOCOL](https://docs.microsoft.com/en-us/windows-hardware/drivers/bringup/efi-rng-protocol).

## Testing improvements

 I also took some time to fix the test structure. Now, there is a `test_common` module, which various test files import. This still allows for us to write tests once, but removes the need for hacky Cargo features. I then cleaned up some of the testing in `.travis.yml`. I also removed duplicate tests for minimum dependency versions.

We also now test the CPU-based RNG implementation on all x86 targets. This makes it possible to directly test on Linux, instead of needing a custom target.

Patch:
diff --git a/.travis.yml b/.travis.yml
index 70bed8b..916dcb3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,11 +1,16 @@
 language: rust
-sudo: false
+os: linux
 
-matrix:
+# Targets that we just build (rather than run and test)
+env:
+  global:
+    - STD_TARGETS="x86_64-sun-solaris x86_64-unknown-cloudabi x86_64-unknown-freebsd x86_64-fuchsia x86_64-unknown-netbsd x86_64-unknown-redox x86_64-fortanix-unknown-sgx"
+    - NO_STD_TARGETS="x86_64-unknown-uefi x86_64-unknown-hermit x86_64-unknown-l4re-uclibc x86_64-uwp-windows-gnu x86_64-wrs-vxworks"
+
+jobs:
   include:
     - name: "Linux, 1.32.0"
       rust: 1.32.0
-      os: linux
 
     - name: "OSX, 1.32.0"
       rust: 1.32.0
@@ -68,41 +73,40 @@ matrix:
         # This is actually useful as it finds stuff such as rust-random/rand#669
         - cargo web test --target wasm32-unknown-emscripten --no-run
         - cargo web test --target asmjs-unknown-emscripten --no-run
-        # wasi tests
         - cargo test --target wasm32-wasi
-        # stdweb tests (Node, Chrome)
-        - cd custom/stdweb
-        - cargo web test --nodejs --target=wasm32-unknown-unknown
-        - cargo web test --target=wasm32-unknown-unknown
-        # wasm-bindgen tests (Node, Firefox, Chrome)
-        - cd ../wasm-bindgen
-        - cargo test --target wasm32-unknown-unknown
-        - GECKODRIVER=$HOME/geckodriver cargo test --target wasm32-unknown-unknown --features=test-in-browser
-        - CHROMEDRIVER=$HOME/chromedriver cargo test --target wasm32-unknown-unknown --features=test-in-browser
+        # stdweb (wasm32-unknown-unknown) tests (Node, Chrome)
+        - cargo web test --package stdweb-getrandom
+        - cargo web test --package stdweb-getrandom --nodejs
+        # wasm-bindgen (wasm32-unknown-unknown) tests (Node, Firefox, Chrome)
+        - cargo test --package wasm-bindgen-getrandom
+          --target wasm32-unknown-unknown --test node
+        - GECKODRIVER=$HOME/geckodriver
+          cargo test --package wasm-bindgen-getrandom
+          --target wasm32-unknown-unknown --test web
+        - CHROMEDRIVER=$HOME/chromedriver
+          cargo test --package wasm-bindgen-getrandom
+          --target wasm32-unknown-unknown --test web
 
     - &nightly_and_docs
       name: "Linux, nightly, docs"
       rust: nightly
-      os: linux
       install:
         - rustup target add wasm32-unknown-unknown
         - cargo --list | egrep "^\s*deadlinks$" -q || cargo install cargo-deadlinks
         - cargo deadlinks -V
       script:
-        # Check that our tests pass in the default configuration
-        - cargo test
-        - cargo test --benches
         # Check that setting various features does not break the build
         - cargo build --features=std
         - cargo build --features=custom
         # remove cached documentation, otherwise files from previous PRs can get included
         - rm -rf target/doc
         - cargo doc --no-deps --features=std,custom
-        - cargo doc --no-deps --manifest-path=custom/wasm-bindgen/Cargo.toml --target=wasm32-unknown-unknown
+        - cargo doc --no-deps --package wasm-bindgen-getrandom --target=wasm32-unknown-unknown
         - cargo deadlinks --dir target/doc
-        # also test minimum dependency versions are usable
+        # Check that our tests pass in the default/minimal configuration
+        - cargo test --tests --benches
         - cargo generate-lockfile -Z minimal-versions
-        - cargo test --features=std,custom
+        - cargo test --tests --benches
 
     - <<: *nightly_and_docs
       name: "OSX, nightly, docs"
@@ -111,49 +115,23 @@ matrix:
     - name: "cross-platform build only"
       rust: nightly
       install:
-        - rustup target add x86_64-sun-solaris
-        - rustup target add x86_64-unknown-cloudabi
-        - rustup target add x86_64-unknown-freebsd
-        - rustup target add x86_64-fuchsia
-        - rustup target add x86_64-unknown-netbsd
-        - rustup target add x86_64-unknown-redox
-        - rustup target add x86_64-fortanix-unknown-sgx
+        - echo $STD_TARGETS | xargs -n1 rustup target add
         # For no_std targets
         - rustup component add rust-src
-        - cargo install cargo-xbuild || true
+        - cargo --list | egrep "^\s*xbuild$" -q || cargo install cargo-xbuild
       script:
-        - cargo build --target=x86_64-sun-solaris
-        - cargo build --target=x86_64-unknown-cloudabi
-        - cargo build --target=x86_64-unknown-freebsd
-        - cargo build --target=x86_64-fuchsia
-        - cargo build --target=x86_64-unknown-netbsd
-        - cargo build --target=x86_64-unknown-redox
-        - cargo build --target=x86_64-fortanix-unknown-sgx
-        - cargo xbuild --target=x86_64-unknown-uefi
-        - cargo xbuild --target=x86_64-unknown-hermit
-        - cargo xbuild --target=x86_64-unknown-l4re-uclibc
-        - cargo xbuild --target=x86_64-uwp-windows-gnu
-        - cargo xbuild --target=x86_64-wrs-vxworks
+        # We test that getrandom builds for all targets
+        - echo $STD_TARGETS | xargs -t -n1 cargo build --target
+        - echo $NO_STD_TARGETS | xargs -t -n1 cargo xbuild --features=cpu --target
         # also test minimum dependency versions are usable
         - cargo generate-lockfile -Z minimal-versions
-        - cargo build --target=x86_64-sun-solaris
-        - cargo build --target=x86_64-unknown-cloudabi
-        - cargo build --target=x86_64-unknown-freebsd
-        - cargo build --target=x86_64-fuchsia
-        - cargo build --target=x86_64-unknown-netbsd
-        - cargo build --target=x86_64-unknown-redox
-        - cargo build --target=x86_64-fortanix-unknown-sgx
-        - cargo xbuild --target=x86_64-unknown-uefi
-        - cargo xbuild --target=x86_64-unknown-hermit
-        - cargo xbuild --target=x86_64-unknown-l4re-uclibc
-        - cargo xbuild --target=x86_64-uwp-windows-gnu
-        - cargo xbuild --target=x86_64-wrs-vxworks
+        - echo $STD_TARGETS | xargs -t -n1 cargo build --target
+        - echo $NO_STD_TARGETS | xargs -t -n1 cargo xbuild --features=cpu --target
 
     # Trust cross-built/emulated targets. We must repeat all non-default values.
     - name: "Linux (MIPS, big-endian)"
       env: TARGET=mips-unknown-linux-gnu
       rust: stable
-      sudo: required
       dist: trusty
       services: docker
       install:
@@ -165,7 +143,6 @@ matrix:
     - name: "Android (ARMv7)"
       env: TARGET=armv7-linux-androideabi
       rust: stable
-      sudo: required
       dist: trusty
       services: docker
       install:
diff --git a/Cargo.toml b/Cargo.toml
index b576b84..0488cc1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,6 +35,8 @@ wasi = "0.9"
 
 [features]
 std = []
+# Feature to enable fallback CPU-based implementation
+cpu = []
 # Feature to enable custom RNG implementations
 custom = []
 # Unstable feature to support being a libstd dependency
diff --git a/benches/mod.rs b/benches/mod.rs
index 07953f1..a93e720 100644
--- a/benches/mod.rs
+++ b/benches/mod.rs
@@ -1,5 +1,4 @@
 #![feature(test)]
-extern crate getrandom;
 extern crate test;
 
 #[bench]
diff --git a/custom/stdweb/Cargo.toml b/custom/stdweb/Cargo.toml
index 2b94124..67e44b9 100644
--- a/custom/stdweb/Cargo.toml
+++ b/custom/stdweb/Cargo.toml
@@ -13,14 +13,5 @@ categories = ["wasm"]
 getrandom = { path = "../..", version = "0.2", features = ["custom"] }
 stdweb = "0.4.18"
 
-# Test-only features allowing us to reuse most of the code in common.rs
-[features]
-default = ["test-stdweb"]
-test-stdweb = []
-
-[[test]]
-name = "common"
-path = "../../tests/common.rs"
-
 [package.metadata.docs.rs]
-default-target = "wasm32-unknown-unknown"
\ No newline at end of file
+default-target = "wasm32-unknown-unknown"
diff --git a/custom/wasm-bindgen/Cargo.toml b/custom/wasm-bindgen/Cargo.toml
index 93202fa..2a66592 100644
--- a/custom/wasm-bindgen/Cargo.toml
+++ b/custom/wasm-bindgen/Cargo.toml
@@ -11,20 +11,10 @@ categories = ["wasm"]
 
 [dependencies]
 getrandom = { path = "../..", version = "0.2", features = ["custom"] }
-wasm-bindgen = "0.2.29"
+wasm-bindgen = "0.2.46"
 
 [dev-dependencies]
-wasm-bindgen-test = "0.2"
-
-# Test-only features allowing us to reuse most of the code in common.rs
-[features]
-default = ["test-bindgen"]
-test-bindgen = []
-test-in-browser = ["test-bindgen"]
-
-[[test]]
-name = "common"
-path = "../../tests/common.rs"
+wasm-bindgen-test = "0.2.46"
 
 [package.metadata.docs.rs]
-default-target = "wasm32-unknown-unknown"
\ No newline at end of file
+default-target = "wasm32-unknown-unknown"
diff --git a/src/custom.rs b/src/custom.rs
index 11abf0c..71a1533 100644
--- a/src/custom.rs
+++ b/src/custom.rs
@@ -23,7 +23,7 @@ macro_rules! register_custom_getrandom {
         // We use an extern "C" function to get the guarantees of a stable ABI.
         #[no_mangle]
         extern "C" fn __getrandom_custom(dest: *mut u8, len: usize) -> u32 {
-            let slice = unsafe { ::std::slice::from_raw_parts_mut(dest, len) };
+            let slice = unsafe { ::core::slice::from_raw_parts_mut(dest, len) };
             match $path(slice) {
                 Ok(()) => 0,
                 Err(e) => e.code().get(),
diff --git a/src/lib.rs b/src/lib.rs
index cb62223..98dcff6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -25,8 +25,7 @@
 //! | Redox            | [`rand:`][12]
 //! | CloudABI         | [`cloudabi_sys_random_get`][13]
 //! | Haiku            | `/dev/random` (identical to `/dev/urandom`)
-//! | L4RE, SGX, UEFI  | [RDRAND][18]
-//! | Hermit           | [RDRAND][18] as [`sys_rand`][22] is currently broken.
+//! | SGX              | [RDRAND][18]
 //! | VxWorks          | `randABytes` after checking entropy pool initialization with `randSecure`
 //! | Web browsers     | [`Crypto.getRandomValues`][14] (see [Support for WebAssembly and asm.js][16])
 //! | Node.js          | [`crypto.randomBytes`][15] (see [Support for WebAssembly and asm.js][16])
@@ -118,7 +117,6 @@
 //! [19]: https://www.unix.com/man-page/mojave/2/getentropy/
 //! [20]: https://www.unix.com/man-page/mojave/4/random/
 //! [21]: https://www.freebsd.org/cgi/man.cgi?query=getrandom&manpath=FreeBSD+12.0-stable
-//! [22]: https://github.com/hermitcore/libhermit-rs/blob/09c38b0371cee6f56a541400ba453e319e43db53/src/syscalls/random.rs#L21
 
 #![doc(
     html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png",
@@ -183,15 +181,12 @@ cfg_if! {
         #[path = "windows_uwp.rs"] mod imp;
     } else if #[cfg(windows)] {
         #[path = "windows.rs"] mod imp;
-    } else if #[cfg(all(target_arch = "x86_64", any(
-                  target_os = "hermit",
-                  target_os = "l4re",
-                  target_os = "uefi",
-                  target_env = "sgx",
-              )))] {
+    } else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] {
         #[path = "rdrand.rs"] mod imp;
     } else if #[cfg(feature = "custom")] {
         use custom as imp;
+    } else if #[cfg(all(feature = "cpu", target_arch = "x86_64"))] {
+        #[path = "rdrand.rs"] mod imp;
     } else {
         compile_error!("\
             target is not supported, for more information see: \
@@ -219,3 +214,8 @@ pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
     }
     imp::getrandom_inner(dest)
 }
+
+#[cfg(test)]
+mod test_common;
+#[cfg(test)]
+mod test_cpu;


Test patch:
diff --git a/custom/stdweb/tests/test.rs b/custom/stdweb/tests/test.rs
new file mode 100644
index 0000000..744da52
--- /dev/null
+++ b/custom/stdweb/tests/test.rs
@@ -0,0 +1,7 @@
+// Explicitly use the Custom RNG crate to link it in.
+use stdweb_getrandom as _;
+
+use getrandom::getrandom;
+use test;
+#[path = "../../../src/test_common.rs"]
+mod test_common;
diff --git a/custom/wasm-bindgen/tests/node.rs b/custom/wasm-bindgen/tests/node.rs
new file mode 100644
index 0000000..0861401
--- /dev/null
+++ b/custom/wasm-bindgen/tests/node.rs
@@ -0,0 +1,7 @@
+// Explicitly use the Custom RNG crate to link it in.
+use wasm_bindgen_getrandom as _;
+
+use getrandom::getrandom;
+use wasm_bindgen_test::wasm_bindgen_test as test;
+#[path = "../../../src/test_common.rs"]
+mod test_common;
diff --git a/custom/wasm-bindgen/tests/web.rs b/custom/wasm-bindgen/tests/web.rs
new file mode 100644
index 0000000..f09ddca
--- /dev/null
+++ b/custom/wasm-bindgen/tests/web.rs
@@ -0,0 +1,9 @@
+// Explicitly use the Custom RNG crate to link it in.
+use wasm_bindgen_getrandom as _;
+
+wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
+
+use getrandom::getrandom;
+use wasm_bindgen_test::wasm_bindgen_test as test;
+#[path = "../../../src/test_common.rs"]
+mod test_common;
diff --git a/tests/common.rs b/src/test_common.rs
similarity index 64%
rename from tests/common.rs
rename to src/test_common.rs
index a19af2d..bbcf0e0 100644
--- a/tests/common.rs
+++ b/src/test_common.rs
@@ -1,25 +1,14 @@
-// Explicitly use the Custom RNG crates to link them in.
-#[cfg(feature = "test-stdweb")]
-use stdweb_getrandom as _;
-#[cfg(feature = "test-bindgen")]
-use wasm_bindgen_getrandom as _;
+// Both getrandom and test can be renamed by the parent module.
+use super::getrandom;
+#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
+use super::test;
 
-#[cfg(feature = "test-bindgen")]
-use wasm_bindgen_test::*;
-
-use getrandom::getrandom;
-
-#[cfg(feature = "test-in-browser")]
-wasm_bindgen_test_configure!(run_in_browser);
-
-#[cfg_attr(feature = "test-bindgen", wasm_bindgen_test)]
 #[test]
 fn test_zero() {
     // Test that APIs are happy with zero-length requests
     getrandom(&mut [0u8; 0]).unwrap();
 }
 
-#[cfg_attr(feature = "test-bindgen", wasm_bindgen_test)]
 #[test]
 fn test_diff() {
     let mut v1 = [0u8; 1000];
@@ -37,18 +26,18 @@ fn test_diff() {
     assert!(n_diff_bits >= v1.len() as u32);
 }
 
-#[cfg_attr(feature = "test-bindgen", wasm_bindgen_test)]
 #[test]
 fn test_huge() {
     let mut huge = [0u8; 100_000];
     getrandom(&mut huge).unwrap();
 }
 
-#[cfg(any(unix, windows, target_os = "redox", target_os = "fuchsia"))]
+// On WASM, the thread API always fails/panics
+#[cfg(not(target_arch = "wasm32"))]
 #[test]
 fn test_multithreading() {
-    use std::sync::mpsc::channel;
-    use std::thread;
+    extern crate std;
+    use std::{sync::mpsc::channel, thread, vec};
 
     let mut txs = vec![];
     for _ in 0..20 {
diff --git a/src/test_cpu.rs b/src/test_cpu.rs
new file mode 100644
index 0000000..efcb1c8
--- /dev/null
+++ b/src/test_cpu.rs
@@ -0,0 +1,8 @@
+// We only test the CPU-based RNG source on supported architectures.
+#![cfg(target_arch = "x86_64")]
+
+#[path = "rdrand.rs"]
+mod rdrand;
+use rdrand::getrandom_inner as getrandom;
+#[path = "test_common.rs"]
+mod test_common;