Skip to content

Commit 587794a

Browse files
committed
add deterministic_testing opt-in backend
1 parent eadb879 commit 587794a

File tree

5 files changed

+145
-4
lines changed

5 files changed

+145
-4
lines changed

.cargo/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ runner = 'node'
1414

1515
[resolver]
1616
incompatible-rust-versions = "allow"
17+
[build]
18+
rustflags = ['--cfg', 'getrandom_backend="deterministic_testing"']

Cargo.lock

Lines changed: 77 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ rustdoc-args = ["--cfg", "getrandom_backend=\"extern_impl\""]
2020
# use std to retrieve OS error descriptions
2121
std = []
2222

23+
# Feature to enable deterministic testing -- has an MSRV of 1.85
24+
#
25+
# WARNING: This should only be enabled in dev-dependencies as it is for deterministic testing only.
26+
unsafe_deterministic_testing = ["dep:rand_core", "dep:chacha20"]
27+
2328
# Optional backend: wasm_js
2429
#
2530
# This flag enables the wasm_js backend and uses it by default on wasm32 where
@@ -44,6 +49,11 @@ rand_core = { version = "0.10.0", optional = true }
4449
[target.'cfg(all(any(target_os = "linux", target_os = "android"), not(any(all(target_os = "linux", target_env = ""), getrandom_backend = "custom", getrandom_backend = "linux_raw", getrandom_backend = "rdrand", getrandom_backend = "rndr"))))'.dependencies]
4550
libc = { version = "0.2.154", default-features = false }
4651

52+
# deterministic_testing
53+
[target.'cfg(getrandom_backend = "deterministic_testing")'.dependencies]
54+
chacha20 = { version = "0.10.0", features = ["rng"], optional = true }
55+
56+
4757
# apple-other
4858
[target.'cfg(any(target_os = "ios", target_os = "visionos", target_os = "watchos", target_os = "tvos"))'.dependencies]
4959
libc = { version = "0.2.154", default-features = false }
@@ -96,7 +106,7 @@ wasm-bindgen-test = "0.3"
96106
[lints.rust.unexpected_cfgs]
97107
level = "warn"
98108
check-cfg = [
99-
'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "linux_raw", "windows_legacy", "unsupported", "extern_impl"))',
109+
'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "linux_raw", "windows_legacy", "unsupported", "extern_impl", "deterministic_testing"))',
100110
'cfg(getrandom_msan)',
101111
'cfg(getrandom_test_linux_fallback)',
102112
'cfg(getrandom_test_linux_without_fallback)',

src/backends.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
//! regardless of what value it returns.
99
1010
cfg_if! {
11-
if #[cfg(getrandom_backend = "custom")] {
11+
if #[cfg(all(getrandom_backend = "deterministic_testing", feature = "unsafe_deterministic_testing"))] {
12+
mod deterministic;
13+
pub use deterministic::*;
14+
} else if #[cfg(getrandom_backend = "custom")] {
1215
mod custom;
1316
pub use custom::*;
1417
} else if #[cfg(getrandom_backend = "linux_getrandom")] {

src/backends/deterministic.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//! Deterministic testing backend — seeded ChaCha12 RNG, single-thread only.
2+
3+
// This module is only compiled under `cfg(test)`, so `std` is always linked
4+
// even though the crate is `#![no_std]`.
5+
extern crate std;
6+
7+
pub use crate::util::{inner_u32, inner_u64};
8+
9+
use crate::Error;
10+
11+
use chacha20::ChaCha12Rng;
12+
use core::mem::MaybeUninit;
13+
use rand_core::{Rng, SeedableRng};
14+
use std::sync::{Mutex, OnceLock};
15+
16+
/// The RNG, initialised exactly once on first use.
17+
static RNG: OnceLock<Mutex<ChaCha12Rng>> = OnceLock::new();
18+
19+
#[inline]
20+
pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
21+
let rng = RNG.get_or_init(|| Mutex::new(ChaCha12Rng::from_seed([42u8; 32])));
22+
23+
let mut guard = rng.lock().unwrap();
24+
25+
// SAFETY: `fill_bytes` fully overwrites every byte of the slice, so
26+
// treating uninitialized `MaybeUninit<u8>` as `u8` for the purpose of
27+
// writing (never reading) is sound.
28+
let dest_init = unsafe { dest.assume_init_mut() };
29+
guard.fill_bytes(dest_init);
30+
31+
Ok(())
32+
}
33+
34+
#[cfg(test)]
35+
mod tests {
36+
use super::*;
37+
38+
#[test]
39+
fn test_deterministic() {
40+
let mut buf = [0u8; 32];
41+
crate::fill(&mut buf).unwrap();
42+
assert_eq!(
43+
[
44+
0x1b, 0x8c, 0x20, 0xcd, 0xe2, 0xdb, 0xb4, 0x3c, 0xd3, 0xc7, 0x9, 0xb2, 0x90, 0xac,
45+
0x50, 0xdc, 0xd2, 0xbe, 0x2a, 0x87, 0xa3, 0xa2, 0x45, 0x44, 0xb5, 0xa5, 0x10, 0x9b,
46+
0xc7, 0x6e, 0xa7, 0xfb,
47+
],
48+
buf
49+
);
50+
}
51+
}

0 commit comments

Comments
 (0)