Skip to content

Commit

Permalink
Use getrandom for seeding the RNG on WASM targets (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
notgull authored Jun 5, 2023
1 parent 46527e0 commit eb78d89
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 20 deletions.
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ exclude = ["/.*"]
[features]
default = ["std"]
alloc = []
std = ["alloc", "instant"]
std = ["alloc"]
js = ["std", "getrandom"]

[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies]
instant = { version = "0.1", optional = true }
[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies]
getrandom = { version = "0.2", features = ["js"], optional = true }

[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dev-dependencies]
instant = { version = "0.1", features = ["wasm-bindgen"] }
[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies]
wasm-bindgen-test = "0.3"
getrandom = { version = "0.2", features = ["js"] }

Expand Down
56 changes: 41 additions & 15 deletions src/global_rng.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ use crate::Rng;
use std::cell::Cell;
use std::ops::RangeBounds;

#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
use instant::Instant;
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
use std::time::Instant;
// Chosen by fair roll of the dice.
const DEFAULT_RNG_SEED: u64 = 0xef6f79ed30ba75a;

impl Default for Rng {
/// Initialize the `Rng` from the system's random number generator.
Expand All @@ -29,17 +27,7 @@ impl Rng {
}

thread_local! {
static RNG: Cell<Rng> = Cell::new(Rng({
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::thread;

let mut hasher = DefaultHasher::new();
Instant::now().hash(&mut hasher);
thread::current().id().hash(&mut hasher);
let hash = hasher.finish();
(hash << 1) | 1
}));
static RNG: Cell<Rng> = Cell::new(Rng(random_seed().unwrap_or(DEFAULT_RNG_SEED)));
}

/// Run an operation with the current thread-local generator.
Expand Down Expand Up @@ -190,3 +178,41 @@ pub fn f64() -> f64 {
pub fn choose_multiple<T: Iterator>(source: T, amount: usize) -> Vec<T::Item> {
with_rng(|rng| rng.choose_multiple(source, amount))
}

#[cfg(not(all(
any(target_arch = "wasm32", target_arch = "wasm64"),
target_os = "unknown"
)))]
fn random_seed() -> Option<u64> {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::thread;
use std::time::Instant;

let mut hasher = DefaultHasher::new();
Instant::now().hash(&mut hasher);
thread::current().id().hash(&mut hasher);
let hash = hasher.finish();
Some((hash << 1) | 1)
}

#[cfg(all(
any(target_arch = "wasm32", target_arch = "wasm64"),
target_os = "unknown",
feature = "js"
))]
fn random_seed() -> Option<u64> {
// TODO(notgull): Failures should be logged somewhere.
let mut seed = [0u8; 8];
getrandom::getrandom(&mut seed).ok()?;
Some(u64::from_ne_bytes(seed))
}

#[cfg(all(
any(target_arch = "wasm32", target_arch = "wasm64"),
target_os = "unknown",
not(feature = "js")
))]
fn random_seed() -> Option<u64> {
None
}
15 changes: 15 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@
//! - `std` (enabled by default): Enables the `std` library. This is required for the global
//! generator and global entropy. Without this feature, [`Rng`] can only be instantiated using
//! the [`with_seed`](Rng::with_seed) method.
//! - `js`: Assumes that WebAssembly targets are being run in a JavaScript environment. See the
//! [WebAssembly Notes](#webassembly-notes) section for more information.
//!
//! # WebAssembly Notes
//!
//! For non-WASI WASM targets, there is additional sublety to consider when utilizing the global RNG.
//! By default, `std` targets will use entropy sources in the standard library to seed the global RNG.
//! However, these sources are not available by default on WASM targets outside of WASI.
//!
//! If the `js` feature is enabled, this crate will assume that it is running in a JavaScript
//! environment. At this point, the [`getrandom`] crate will be used in order to access the available
//! entropy sources and seed the global RNG. If the `js` feature is not enabled, the global RNG will
//! use a predefined seed.
//!
//! [`getrandom`]: https://crates.io/crates/getrandom

#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
Expand Down

0 comments on commit eb78d89

Please sign in to comment.