Skip to content

Commit a29180a

Browse files
committed
wasm32: Unify error codes an implementations
Right now, while our implementations for stdweb and wasm-bindgen use similar APIs, they do not use similar implementations. This change: - Switches to using the same error codes for both implementations - Uses error codes that detail exactly what API is missing - Cleans up the implemetnations to make them much more readable - Consitantly use thread_local in both implementations This makes issues easier to debug and the code easier to understand. Signed-off-by: Joe Richey <[email protected]>
1 parent 10df558 commit a29180a

File tree

3 files changed

+87
-117
lines changed

3 files changed

+87
-117
lines changed

custom/stdweb/src/lib.rs

Lines changed: 43 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -10,64 +10,52 @@
1010
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1111
compile_error!("This crate is only for the `wasm32-unknown-unknown` target");
1212

13-
use std::sync::Once;
13+
use std::thread_local;
1414

1515
use stdweb::js;
1616

1717
use getrandom::{register_custom_getrandom, Error};
1818

19-
#[derive(Clone, Copy, Debug)]
19+
#[derive(Clone, Copy, PartialEq)]
2020
enum RngSource {
2121
Browser,
2222
Node,
2323
}
2424

25+
thread_local!(
26+
static RNG_SOURCE: Result<RngSource, Error> = getrandom_init();
27+
);
28+
2529
register_custom_getrandom!(getrandom_inner);
2630

2731
fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
28-
static ONCE: Once = Once::new();
29-
static mut RNG_SOURCE: Result<RngSource, Error> = Ok(RngSource::Node);
30-
31-
// SAFETY: RNG_SOURCE is only written once, before being read.
32-
ONCE.call_once(|| unsafe {
33-
RNG_SOURCE = getrandom_init();
34-
});
35-
getrandom_fill(unsafe { RNG_SOURCE }?, dest)
32+
RNG_SOURCE.with(|&source| getrandom_fill(source?, dest))
3633
}
3734

3835
fn getrandom_init() -> Result<RngSource, Error> {
39-
let result = js! {
40-
try {
41-
if (
42-
typeof self === "object" &&
43-
typeof self.crypto === "object" &&
44-
typeof self.crypto.getRandomValues === "function"
45-
) {
46-
return { success: true, ty: 1 };
47-
}
48-
49-
if (typeof require("crypto").randomBytes === "function") {
50-
return { success: true, ty: 2 };
51-
}
52-
53-
return { success: false, error: new Error("not supported") };
54-
} catch(err) {
55-
return { success: false, error: err };
56-
}
57-
};
58-
59-
if js! { return @{ result.as_ref() }.success } == true {
60-
let ty = js! { return @{ result }.ty };
61-
62-
if ty == 1 {
36+
if js! { return typeof self === "object"; } == true {
37+
// We are in a Browser or WebWorker
38+
let supported = js! { return typeof self.crypto === "object"; };
39+
if supported == true {
6340
Ok(RngSource::Browser)
64-
} else if ty == 2 {
65-
Ok(RngSource::Node)
6641
} else {
67-
unreachable!()
42+
Err(Error::WEB_CRYPTO)
6843
}
6944
} else {
70-
Err(Error::STDWEB_NO_RNG)
45+
// We are in Node.js
46+
let supported = js! {
47+
try {
48+
require("crypto");
49+
return true;
50+
} catch(err) {
51+
return false;
52+
}
53+
};
54+
if supported == true {
55+
Ok(RngSource::Node)
56+
} else {
57+
Err(Error::NODE_CRYPTO)
58+
}
7159
}
7260
}
7361

@@ -76,32 +64,28 @@ fn getrandom_fill(source: RngSource, dest: &mut [u8]) -> Result<(), Error> {
7664
let len = chunk.len() as u32;
7765
let ptr = chunk.as_mut_ptr() as i32;
7866

79-
let result = match source {
80-
RngSource::Browser => js! {
81-
try {
82-
let array = new Uint8Array(@{ len });
83-
self.crypto.getRandomValues(array);
84-
HEAPU8.set(array, @{ ptr });
67+
let success = js! {
68+
try {
69+
let array = new Uint8Array(@{ len });
8570

86-
return { success: true };
87-
} catch(err) {
88-
return { success: false, error: err };
71+
if @{ source == RngSource::Browser } {
72+
self.crypto.getRandomValues(array);
73+
} else {
74+
require("crypto").randomFillSync(array);
8975
}
90-
},
91-
RngSource::Node => js! {
92-
try {
93-
let bytes = require("crypto").randomBytes(@{ len });
94-
HEAPU8.set(new Uint8Array(bytes), @{ ptr });
9576

96-
return { success: true };
97-
} catch(err) {
98-
return { success: false, error: err };
99-
}
100-
},
77+
HEAPU8.set(array, @{ ptr });
78+
return true;
79+
} catch(err) {
80+
return false;
81+
}
10182
};
10283

103-
if js! { return @{ result.as_ref() }.success } != true {
104-
return Err(Error::STDWEB_RNG_FAILED);
84+
if success != true {
85+
return match source {
86+
RngSource::Browser => Err(Error::WEB_GET_RANDOM_VALUES),
87+
RngSource::Node => Err(Error::NODE_RANDOM_FILL_SYNC),
88+
};
10589
}
10690
}
10791
Ok(())

custom/wasm-bindgen/src/lib.rs

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,12 @@
99
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1010
compile_error!("This crate is only for the `wasm32-unknown-unknown` target");
1111

12-
use core::cell::RefCell;
1312
use std::thread_local;
1413

1514
use wasm_bindgen::prelude::*;
1615

1716
use getrandom::{register_custom_getrandom, Error};
1817

19-
#[derive(Clone, Debug)]
2018
enum RngSource {
2119
Node(NodeCrypto),
2220
Browser(BrowserCrypto),
@@ -25,20 +23,21 @@ enum RngSource {
2523
// JsValues are always per-thread, so we initialize RngSource for each thread.
2624
// See: https://github.com/rustwasm/wasm-bindgen/pull/955
2725
thread_local!(
28-
static RNG_SOURCE: RefCell<Option<RngSource>> = RefCell::new(None);
26+
static RNG_SOURCE: Result<RngSource, Error> = getrandom_init();
2927
);
3028

3129
register_custom_getrandom!(getrandom_inner);
3230

3331
fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
34-
RNG_SOURCE.with(|f| {
35-
let mut source = f.borrow_mut();
36-
if source.is_none() {
37-
*source = Some(getrandom_init()?);
38-
}
39-
40-
match source.as_ref().unwrap() {
41-
RngSource::Node(n) => n.random_fill_sync(dest),
32+
RNG_SOURCE.with(|result| {
33+
let source = result.as_ref().map_err(|&e| e)?;
34+
35+
match source {
36+
RngSource::Node(n) => {
37+
if n.random_fill_sync(dest).is_err() {
38+
return Err(Error::NODE_RANDOM_FILL_SYNC);
39+
}
40+
}
4241
RngSource::Browser(n) => {
4342
// see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
4443
//
@@ -47,7 +46,9 @@ fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
4746
// > A QuotaExceededError DOMException is thrown if the
4847
// > requested length is greater than 65536 bytes.
4948
for chunk in dest.chunks_mut(65536) {
50-
n.get_random_values(chunk)
49+
if n.get_random_values(chunk).is_err() {
50+
return Err(Error::WEB_GET_RANDOM_VALUES);
51+
}
5152
}
5253
}
5354
};
@@ -63,20 +64,15 @@ fn getrandom_init() -> Result<RngSource, Error> {
6364
// assume we're in an older web browser and the OS RNG isn't available.
6465

6566
let crypto: BrowserCrypto = match (self_.crypto(), self_.ms_crypto()) {
66-
(crypto, _) if !crypto.is_undefined() => crypto.into(),
67-
(_, crypto) if !crypto.is_undefined() => crypto.into(),
68-
_ => return Err(Error::BINDGEN_CRYPTO_UNDEF),
67+
(crypto, _) if !crypto.is_undefined() => crypto,
68+
(_, crypto) if !crypto.is_undefined() => crypto,
69+
_ => return Err(Error::WEB_CRYPTO),
6970
};
70-
71-
// Test if `crypto.getRandomValues` is undefined as well
72-
if crypto.get_random_values_fn().is_undefined() {
73-
return Err(Error::BINDGEN_GRV_UNDEF);
74-
}
75-
7671
return Ok(RngSource::Browser(crypto));
7772
}
7873

79-
return Ok(RngSource::Node(MODULE.require("crypto")));
74+
let crypto = MODULE.require("crypto").map_err(|_| Error::NODE_CRYPTO)?;
75+
Ok(RngSource::Node(crypto))
8076
}
8177

8278
#[wasm_bindgen]
@@ -86,33 +82,23 @@ extern "C" {
8682
fn get_self() -> Result<Self_, JsValue>;
8783

8884
type Self_;
89-
#[wasm_bindgen(method, getter, js_name = "msCrypto", structural)]
90-
fn ms_crypto(me: &Self_) -> JsValue;
91-
#[wasm_bindgen(method, getter, structural)]
92-
fn crypto(me: &Self_) -> JsValue;
85+
#[wasm_bindgen(method, getter, js_name = "msCrypto")]
86+
fn ms_crypto(me: &Self_) -> BrowserCrypto;
87+
#[wasm_bindgen(method, getter)]
88+
fn crypto(me: &Self_) -> BrowserCrypto;
9389

94-
#[derive(Clone, Debug)]
9590
type BrowserCrypto;
96-
97-
// TODO: these `structural` annotations here ideally wouldn't be here to
98-
// avoid a JS shim, but for now with feature detection they're
99-
// unavoidable.
100-
#[wasm_bindgen(method, js_name = getRandomValues, structural, getter)]
101-
fn get_random_values_fn(me: &BrowserCrypto) -> JsValue;
102-
#[wasm_bindgen(method, js_name = getRandomValues, structural)]
103-
fn get_random_values(me: &BrowserCrypto, buf: &mut [u8]);
104-
105-
#[derive(Clone, Debug)]
106-
type NodeCrypto;
107-
108-
#[wasm_bindgen(method, js_name = randomFillSync, structural)]
109-
fn random_fill_sync(me: &NodeCrypto, buf: &mut [u8]);
110-
111-
type NodeModule;
91+
#[wasm_bindgen(method, js_name = getRandomValues, catch)]
92+
fn get_random_values(me: &BrowserCrypto, buf: &mut [u8]) -> Result<(), JsValue>;
11293

11394
#[wasm_bindgen(js_name = module)]
11495
static MODULE: NodeModule;
11596

116-
#[wasm_bindgen(method)]
117-
fn require(this: &NodeModule, s: &str) -> NodeCrypto;
97+
type NodeModule;
98+
#[wasm_bindgen(method, catch)]
99+
fn require(this: &NodeModule, s: &str) -> Result<NodeCrypto, JsValue>;
100+
101+
type NodeCrypto;
102+
#[wasm_bindgen(method, js_name = randomFillSync, catch)]
103+
fn random_fill_sync(crypto: &NodeCrypto, buf: &mut [u8]) -> Result<(), JsValue>;
118104
}

src/error.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,16 @@ impl Error {
3737
pub const FAILED_RDRAND: Error = internal_error(5);
3838
/// RDRAND instruction unsupported on this target.
3939
pub const NO_RDRAND: Error = internal_error(6);
40-
/// Using `wasm-bindgen`, browser does not support `self.crypto`.
41-
pub const BINDGEN_CRYPTO_UNDEF: Error = internal_error(7);
42-
/// Using `wasm-bindgen`, browser does not support `crypto.getRandomValues`.
43-
pub const BINDGEN_GRV_UNDEF: Error = internal_error(8);
44-
/// Using `stdweb`, no cryptographic RNG is available.
45-
pub const STDWEB_NO_RNG: Error = internal_error(9);
46-
/// Using `stdweb`, invoking a cryptographic RNG failed.
47-
pub const STDWEB_RNG_FAILED: Error = internal_error(10);
40+
/// The browser does not have support for `self.crypto`.
41+
pub const WEB_CRYPTO: Error = internal_error(7);
42+
/// The browser does not have support for `crypto.getRandomValues`.
43+
pub const WEB_GET_RANDOM_VALUES: Error = internal_error(8);
4844
/// On VxWorks, call to `randSecure` failed (random number generator is not yet initialized).
4945
pub const VXWORKS_RAND_SECURE: Error = internal_error(11);
46+
/// NodeJS does not have support for the `crypto` module.
47+
pub const NODE_CRYPTO: Error = internal_error(12);
48+
/// NodeJS does not have support for `crypto.randomFillSync`.
49+
pub const NODE_RANDOM_FILL_SYNC: Error = internal_error(13);
5050

5151
/// Codes below this point represent OS Errors (i.e. positive i32 values).
5252
/// Codes at or above this point, but below [`Error::CUSTOM_START`] are
@@ -152,15 +152,15 @@ fn internal_desc(error: Error) -> Option<&'static str> {
152152
match error {
153153
Error::UNSUPPORTED => Some("getrandom: this target is not supported"),
154154
Error::ERRNO_NOT_POSITIVE => Some("errno: did not return a positive value"),
155-
Error::IOS_SEC_RANDOM => Some("SecRandomCopyBytes: iOS Secuirty framework failure"),
155+
Error::IOS_SEC_RANDOM => Some("SecRandomCopyBytes: iOS Security framework failure"),
156156
Error::WINDOWS_RTL_GEN_RANDOM => Some("RtlGenRandom: Windows system function failure"),
157157
Error::FAILED_RDRAND => Some("RDRAND: failed multiple times: CPU issue likely"),
158158
Error::NO_RDRAND => Some("RDRAND: instruction not supported"),
159-
Error::BINDGEN_CRYPTO_UNDEF => Some("wasm-bindgen: self.crypto is undefined"),
160-
Error::BINDGEN_GRV_UNDEF => Some("wasm-bindgen: crypto.getRandomValues is undefined"),
161-
Error::STDWEB_NO_RNG => Some("stdweb: no randomness source available"),
162-
Error::STDWEB_RNG_FAILED => Some("stdweb: failed to get randomness"),
159+
Error::WEB_CRYPTO => Some("Web API self.crypto is unavailable"),
160+
Error::WEB_GET_RANDOM_VALUES => Some("Web API crypto.getRandomValues is unavailable"),
163161
Error::VXWORKS_RAND_SECURE => Some("randSecure: VxWorks RNG module is not initialized"),
162+
Error::NODE_CRYPTO => Some("Node.js crypto module is unavailable"),
163+
Error::NODE_RANDOM_FILL_SYNC => Some("Node.js API crypto.randomFillSync is unavailable"),
164164
_ => None,
165165
}
166166
}

0 commit comments

Comments
 (0)