From 140385528f6838aa352a2ab8649ae2b2306ceeb7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 5 Jul 2018 22:12:57 -0700 Subject: [PATCH] Add support for wasm-bindgen This commit adds support to implement the `rand` crate on the wasm32-unknown-unknown target with the `wasm-bindgen`-based runtime. This supports being run both in node.js as well as browsers by delegating appropriately. Closes #478 --- .travis.yml | 6 +++ Cargo.toml | 1 + README.md | 6 ++- src/deprecated.rs | 12 +++-- src/lib.rs | 126 ++++++++++++++++++++++++++++++++++---------- src/rngs/entropy.rs | 11 ++-- src/rngs/mod.rs | 6 ++- src/rngs/os.rs | 100 +++++++++++++++++++++++++++++++++-- 8 files changed, 226 insertions(+), 42 deletions(-) diff --git a/.travis.yml b/.travis.yml index 293796fd00f..20e982cc3cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,6 +57,12 @@ matrix: script: - cargo web test --target wasm32-unknown-unknown --nodejs --features=stdweb + - rust: nightly + install: + - rustup target add wasm32-unknown-unknown + script: + - cargo build --target wasm32-unknown-unknown --features wasm-bindgen + - rust: nightly install: - rustup target add thumbv6m-none-eabi diff --git a/Cargo.toml b/Cargo.toml index 21cf85131c2..887620cce04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ fuchsia-zircon = { version = "0.3.2", optional = true } [target.wasm32-unknown-unknown.dependencies] # use with `--target wasm32-unknown-unknown --features=stdweb` stdweb = { version = "0.4", optional = true } +wasm-bindgen = { version = "0.2", optional = true } [dev-dependencies] # This is for testing serde, unfortunately we can't specify feature-gated dev diff --git a/README.md b/README.md index b02ed1ff019..8158a62a5c7 100644 --- a/README.md +++ b/README.md @@ -117,8 +117,12 @@ optional features are available: - `log` enables some logging via the `log` crate. - `nightly` enables all unstable features (`i128_support`). - `serde1` enables serialization for some types, via Serde version 1. -- `stdweb` enables support for `OsRng` on `wasm-unknown-unknown` via `stdweb` +- `stdweb` enables support for `OsRng` on `wasm32-unknown-unknown` via `stdweb` combined with `cargo-web`. +- `wasm-bindgen` enables support for `OsRng` on `wasm32-unknown-unknown` via + [`wasm-bindgen`] + +[`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen `no_std` mode is activated by setting `default-features = false`; this removes functionality depending on `std`: diff --git a/src/deprecated.rs b/src/deprecated.rs index 0b6a71d502f..5ad73527fea 100644 --- a/src/deprecated.rs +++ b/src/deprecated.rs @@ -263,7 +263,8 @@ impl CryptoRng for StdRng {} target_os = "redox", target_os = "fuchsia", windows, - all(target_arch = "wasm32", feature = "stdweb") + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), )))] #[derive(Clone, Debug)] #[deprecated(since="0.6.0", note="import with rand::rngs::OsRng instead")] @@ -283,7 +284,8 @@ pub struct OsRng(rngs::OsRng); target_os = "redox", target_os = "fuchsia", windows, - all(target_arch = "wasm32", feature = "stdweb") + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), )))] #[cfg(feature="std")] impl RngCore for OsRng { @@ -322,7 +324,8 @@ impl RngCore for OsRng { target_os = "redox", target_os = "fuchsia", windows, - all(target_arch = "wasm32", feature = "stdweb") + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), )))] #[cfg(feature="std")] impl OsRng { @@ -345,7 +348,8 @@ impl OsRng { target_os = "redox", target_os = "fuchsia", windows, - all(target_arch = "wasm32", feature = "stdweb") + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), )))] #[cfg(feature="std")] impl CryptoRng for OsRng {} diff --git a/src/lib.rs b/src/lib.rs index 522d07020e2..4d169ac0e16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,7 +84,7 @@ //! //! The [`distributions` module] provides implementations //! of some other distributions, including Normal, Log-Normal and Exponential. -//! +//! //! It is worth noting that the functionality already mentioned is implemented //! with distributions: [`gen`] samples values using the [`Standard`] //! distribution, while [`gen_range`] uses [`Uniform`]. @@ -109,7 +109,7 @@ //! //! // thread_rng is often the most convenient source of randomness: //! let mut rng = thread_rng(); -//! +//! //! if rng.gen() { // random bool //! let x: f64 = rng.gen(); // random number in range [0, 1) //! println!("x is: {}", x); @@ -230,6 +230,9 @@ #![cfg_attr(all(feature="i128_support", feature="nightly"), feature(i128_type, i128))] #![cfg_attr(all(feature="simd_support", feature="nightly"), feature(stdsimd))] #![cfg_attr(feature = "stdweb", recursion_limit="128")] +#![cfg_attr(feature = "wasm-bindgen", feature(proc_macro))] +#![cfg_attr(feature = "wasm-bindgen", feature(wasm_import_module))] +#![cfg_attr(feature = "wasm-bindgen", feature(wasm_custom_section))] #[cfg(feature="std")] extern crate std as core; #[cfg(all(feature = "alloc", not(feature="std")))] extern crate alloc; @@ -242,6 +245,9 @@ #[macro_use] extern crate stdweb; +#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))] +extern crate wasm_bindgen; + extern crate rand_core; #[cfg(feature = "log")] #[macro_use] extern crate log; @@ -297,7 +303,8 @@ pub mod seq; target_os = "redox", target_os = "fuchsia", windows, - all(target_arch = "wasm32", feature = "stdweb") + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), )))] #[doc(hidden)] pub use deprecated::OsRng; @@ -329,7 +336,8 @@ pub mod jitter { target_os = "redox", target_os = "fuchsia", windows, - all(target_arch = "wasm32", feature = "stdweb") + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), )))] #[doc(hidden)] pub mod os { @@ -364,20 +372,20 @@ use distributions::uniform::{SampleUniform, UniformSampler, SampleBorrow}; /// An automatically-implemented extension trait on [`RngCore`] providing high-level /// generic methods for sampling values and other convenience methods. -/// +/// /// This is the primary trait to use when generating random values. -/// +/// /// # Generic usage -/// +/// /// The basic pattern is `fn foo(rng: &mut R)`. Some /// things are worth noting here: -/// +/// /// - Since `Rng: RngCore` and every `RngCore` implements `Rng`, it makes no /// difference whether we use `R: Rng` or `R: RngCore`. /// - The `+ ?Sized` un-bounding allows functions to be called directly on /// type-erased references; i.e. `foo(r)` where `r: &mut RngCore`. Without /// this it would be necessary to write `foo(&mut r)`. -/// +/// /// An alternative pattern is possible: `fn foo(rng: R)`. This has some /// trade-offs. It allows the argument to be consumed directly without a `&mut` /// (which is how `from_rng(thread_rng())` works); also it still works directly @@ -386,20 +394,20 @@ use distributions::uniform::{SampleUniform, UniformSampler, SampleBorrow}; /// hence many uses of `rng` require an extra reference, either explicitly /// (`distr.sample(&mut rng)`) or implicitly (`rng.gen()`); one may hope the /// optimiser can remove redundant references later. -/// +/// /// Example: -/// +/// /// ``` /// # use rand::thread_rng; /// use rand::Rng; -/// +/// /// fn foo(rng: &mut R) -> f32 { /// rng.gen() /// } /// /// # let v = foo(&mut thread_rng()); /// ``` -/// +/// /// [`RngCore`]: trait.RngCore.html pub trait Rng: RngCore { /// Return a random value supporting the [`Standard`] distribution. @@ -624,7 +632,7 @@ pub trait Rng: RngCore { /// Return a random element from `values`. /// /// Deprecated: use [`SliceRandom::choose`] instead. - /// + /// /// [`SliceRandom::choose`]: seq/trait.SliceRandom.html#method.choose #[deprecated(since="0.6.0", note="use SliceRandom::choose instead")] fn choose<'a, T>(&mut self, values: &'a [T]) -> Option<&'a T> { @@ -635,7 +643,7 @@ pub trait Rng: RngCore { /// Return a mutable pointer to a random element from `values`. /// /// Deprecated: use [`SliceRandom::choose_mut`] instead. - /// + /// /// [`SliceRandom::choose_mut`]: seq/trait.SliceRandom.html#method.choose_mut #[deprecated(since="0.6.0", note="use SliceRandom::choose_mut instead")] fn choose_mut<'a, T>(&mut self, values: &'a mut [T]) -> Option<&'a mut T> { @@ -646,7 +654,7 @@ pub trait Rng: RngCore { /// Shuffle a mutable slice in place. /// /// Deprecated: use [`SliceRandom::shuffle`] instead. - /// + /// /// [`SliceRandom::shuffle`]: seq/trait.SliceRandom.html#method.shuffle #[deprecated(since="0.6.0", note="use SliceRandom::shuffle instead")] fn shuffle(&mut self, values: &mut [T]) { @@ -658,15 +666,15 @@ pub trait Rng: RngCore { impl Rng for R {} /// Trait for casting types to byte slices -/// +/// /// This is used by the [`fill`] and [`try_fill`] methods. -/// +/// /// [`fill`]: trait.Rng.html#method.fill /// [`try_fill`]: trait.Rng.html#method.try_fill pub trait AsByteSliceMut { /// Return a mutable reference to self as a byte slice fn as_byte_slice_mut(&mut self) -> &mut [u8]; - + /// Call `to_le` on each element (i.e. byte-swap on Big Endian platforms). fn to_le(&mut self); } @@ -675,7 +683,7 @@ impl AsByteSliceMut for [u8] { fn as_byte_slice_mut(&mut self) -> &mut [u8] { self } - + fn to_le(&mut self) {} } @@ -698,7 +706,7 @@ macro_rules! impl_as_byte_slice { } } } - + fn to_le(&mut self) { for x in self { *x = x.to_le(); @@ -724,7 +732,7 @@ macro_rules! impl_as_byte_slice_arrays { ($n:expr,) => {}; ($n:expr, $N:ident, $($NN:ident,)*) => { impl_as_byte_slice_arrays!($n - 1, $($NN,)*); - + impl AsByteSliceMut for [T; $n] where [T]: AsByteSliceMut { fn as_byte_slice_mut(&mut self) -> &mut [u8] { self[..].as_byte_slice_mut() @@ -743,7 +751,7 @@ macro_rules! impl_as_byte_slice_arrays { fn as_byte_slice_mut(&mut self) -> &mut [u8] { self[..].as_byte_slice_mut() } - + fn to_le(&mut self) { self[..].to_le() } @@ -874,6 +882,68 @@ pub fn random() -> T where Standard: Distribution { thread_rng().gen() } +// Due to rustwasm/wasm-bindgen#201 this can't be defined in the inner os +// modules, so hack around it for now and place it at the root. +#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))] +#[doc(hidden)] +#[allow(missing_debug_implementations)] +pub mod __wbg_shims { + + // `extern { type Foo; }` isn't supported on 1.22 syntactically, so use a + // macro to work around that. + macro_rules! rust_122_compat { + ($($t:tt)*) => ($($t)*) + } + + rust_122_compat! { + extern crate wasm_bindgen; + + pub use wasm_bindgen::prelude::*; + + #[wasm_bindgen] + extern { + pub type This; + pub static this: This; + + #[wasm_bindgen(method, getter, structural, js_name = self)] + pub fn self_(me: &This) -> JsValue; + #[wasm_bindgen(method, getter, structural)] + pub fn crypto(me: &This) -> JsValue; + + pub type BrowserCrypto; + + // TODO: these `structural` annotations here ideally wouldn't be here to + // avoid a JS shim, but for now with feature detection they're + // unavoidable. + #[wasm_bindgen(method, js_name = getRandomValues, structural, getter)] + pub fn get_random_values_fn(me: &BrowserCrypto) -> JsValue; + #[wasm_bindgen(method, js_name = getRandomValues, structural)] + pub fn get_random_values(me: &BrowserCrypto, buf: &mut [u8]); + + #[wasm_bindgen(js_name = require)] + pub fn node_require(s: &str) -> NodeCrypto; + + pub type NodeCrypto; + + #[wasm_bindgen(method, js_name = randomFillSync, structural)] + pub fn random_fill_sync(me: &NodeCrypto, buf: &mut [u8]); + } + + // TODO: replace with derive once rustwasm/wasm-bindgen#400 is merged + impl Clone for BrowserCrypto { + fn clone(&self) -> BrowserCrypto { + BrowserCrypto { obj: self.obj.clone() } + } + } + + impl Clone for NodeCrypto { + fn clone(&self) -> NodeCrypto { + NodeCrypto { obj: self.obj.clone() } + } + } + } +} + #[cfg(test)] mod test { use rngs::mock::StepRng; @@ -936,25 +1006,25 @@ mod test { } } } - + #[test] fn test_fill() { let x = 9041086907909331047; // a random u64 let mut rng = StepRng::new(x, 0); - + // Convert to byte sequence and back to u64; byte-swap twice if BE. let mut array = [0u64; 2]; rng.fill(&mut array[..]); assert_eq!(array, [x, x]); assert_eq!(rng.next_u64(), x); - + // Convert to bytes then u32 in LE order let mut array = [0u32; 2]; rng.fill(&mut array[..]); assert_eq!(array, [x as u32, (x >> 32) as u32]); assert_eq!(rng.next_u32(), x as u32); } - + #[test] fn test_fill_empty() { let mut array = [0u32; 0]; @@ -1029,7 +1099,7 @@ mod test { assert_eq!(r.gen_range(0, 1), 0); let _c: u8 = Standard.sample(&mut r); } - + #[test] #[cfg(feature="std")] fn test_random() { diff --git a/src/rngs/entropy.rs b/src/rngs/entropy.rs index b8f4be799b6..0f85596da62 100644 --- a/src/rngs/entropy.rs +++ b/src/rngs/entropy.rs @@ -24,7 +24,7 @@ use rngs; /// /// If no secure source of entropy is available `EntropyRng` will panic on use; /// i.e. it should never output predictable data. -/// +/// /// This is either a little slow ([`OsRng`] requires a system call) or extremely /// slow ([`JitterRng`] must use significant CPU time to generate sufficient /// jitter); for better performance it is common to seed a local PRNG from @@ -207,7 +207,8 @@ impl EntropySource for NoSource { target_os = "redox", target_os = "fuchsia", windows, - all(target_arch = "wasm32", feature = "stdweb") + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), )))] #[derive(Clone, Debug)] pub struct Os(rngs::OsRng); @@ -226,7 +227,8 @@ pub struct Os(rngs::OsRng); target_os = "redox", target_os = "fuchsia", windows, - all(target_arch = "wasm32", feature = "stdweb") + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), )))] impl EntropySource for Os { fn new_and_fill(dest: &mut [u8]) -> Result { @@ -254,7 +256,8 @@ impl EntropySource for Os { target_os = "redox", target_os = "fuchsia", windows, - all(target_arch = "wasm32", feature = "stdweb") + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), ))))] type Os = NoSource; diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index 3e183387018..f16ee545c37 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -197,7 +197,8 @@ pub use self::std::StdRng; target_os = "redox", target_os = "fuchsia", windows, - all(target_arch = "wasm32", feature = "stdweb") + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), )))] mod os; @@ -215,6 +216,7 @@ mod os; target_os = "redox", target_os = "fuchsia", windows, - all(target_arch = "wasm32", feature = "stdweb") + all(target_arch = "wasm32", feature = "stdweb"), + all(target_arch = "wasm32", feature = "wasm-bindgen"), )))] pub use self::os::OsRng; diff --git a/src/rngs/os.rs b/src/rngs/os.rs index 4a8955c2563..176d4193096 100644 --- a/src/rngs/os.rs +++ b/src/rngs/os.rs @@ -60,8 +60,8 @@ use rand_core::{CryptoRng, RngCore, Error, impls}; /// doesn't support [`Crypto.getRandomValues`][12]. /// /// The bare Wasm target `wasm32-unknown-unknown` tries to call the javascript -/// methods directly, using `stdweb` in combination with `cargo-web`. -/// `wasm-bindgen` is not yet supported. +/// methods directly, using either `stdweb` in combination with `cargo-web` or +/// `wasm-bindgen` depending on what features are activated for this crate. /// /// ## Early boot /// @@ -963,7 +963,7 @@ mod imp { #[cfg(windows)] mod imp { extern crate winapi; - + use {Error, ErrorKind}; use super::OsRngImpl; @@ -1102,6 +1102,100 @@ mod imp { } } +#[cfg(all(target_arch = "wasm32", + not(target_os = "emscripten"), + not(feature = "stdweb"), + feature = "wasm-bindgen"))] +mod imp { + use core::fmt; + + use __wbg_shims::*; + + use {Error, ErrorKind}; + use super::OsRngImpl; + + #[derive(Clone)] + pub enum OsRng { + Node(NodeCrypto), + Browser(BrowserCrypto), + } + + impl OsRngImpl for OsRng { + fn new() -> Result { + // First up we need to detect if we're running in node.js or a + // browser. Both environments have `this` defined as an object for + // our own scope, and we use that to look up other values. + // + // Here we test if `this.self` is defined. If so we're in a browser + // (either main window or web worker) and if not we're in node. If + // it turns out we're in node.js then we require the `crypto` + // package and use that. The API we're using was added in Node 6.x + // so it should be safe to assume tha it works. + if this.self_().is_undefined() { + return Ok(OsRng::Node(node_require("crypto"))) + } + + // If `self` is defined then we're in a browser somehow (main window + // or web worker). Here we want to try to use + // `crypto.getRandomValues`, but if `crypto` isn't defined we assume + // we're in an older web browser and the OS RNG isn't available. + let crypto = this.crypto(); + if crypto.is_undefined() { + let msg = "self.crypto is undefined"; + return Err(Error::new(ErrorKind::Unavailable, msg)) + } + + // Test if `crypto.getRandomValues` is undefined as well + let crypto: BrowserCrypto = crypto.into(); + if crypto.get_random_values_fn().is_undefined() { + let msg = "crypto.getRandomValues is undefined"; + return Err(Error::new(ErrorKind::Unavailable, msg)) + } + + // Ok! `self.crypto.getRandomValues` is a defined value, so let's + // assume we can do browser crypto. + Ok(OsRng::Browser(crypto)) + } + + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> { + match *self { + OsRng::Node(ref n) => n.random_fill_sync(dest), + OsRng::Browser(ref n) => n.get_random_values(dest), + } + Ok(()) + } + + fn max_chunk_size(&self) -> usize { + match *self { + OsRng::Node(_) => usize::max_value(), + OsRng::Browser(_) => { + // see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues + // + // where it says: + // + // > A QuotaExceededError DOMException is thrown if the + // > requested length is greater than 65536 bytes. + 65536 + } + } + } + + fn method_str(&self) -> &'static str { + match *self { + OsRng::Node(_) => "crypto.randomFillSync", + OsRng::Browser(_) => "crypto.getRandomValues", + } + } + } + + // TODO: replace with derive once rustwasm/wasm-bindgen#399 is merged + impl fmt::Debug for OsRng { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("OsRng").finish() + } + } +} + #[cfg(test)] mod test {