|
7 | 7 | // except according to those terms.
|
8 | 8 |
|
9 | 9 | //! Implementation for WASM via wasm-bindgen
|
10 |
| -extern crate std; |
| 10 | +use js_sys::global; |
| 11 | +use wasm_bindgen::{prelude::*, JsCast}; |
| 12 | +use web_sys::{window, Crypto, WorkerGlobalScope}; |
11 | 13 |
|
12 |
| -use core::cell::RefCell; |
13 |
| -use core::mem; |
14 |
| -use std::thread_local; |
15 |
| - |
16 |
| -use wasm_bindgen::prelude::*; |
17 |
| - |
18 |
| -use crate::error::{BINDGEN_CRYPTO_UNDEF, BINDGEN_GRV_UNDEF}; |
19 |
| -use crate::Error; |
20 |
| - |
21 |
| -#[derive(Clone, Debug)] |
22 |
| -enum RngSource { |
23 |
| - Node(NodeCrypto), |
24 |
| - Browser(BrowserCrypto), |
25 |
| -} |
26 |
| - |
27 |
| -// JsValues are always per-thread, so we initialize RngSource for each thread. |
28 |
| -// See: https://github.com/rustwasm/wasm-bindgen/pull/955 |
29 |
| -thread_local!( |
30 |
| - static RNG_SOURCE: RefCell<Option<RngSource>> = RefCell::new(None); |
31 |
| -); |
| 14 | +use crate::util::LazyBool; |
| 15 | +use crate::{error, Error}; |
32 | 16 |
|
33 | 17 | pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
|
34 |
| - assert_eq!(mem::size_of::<usize>(), 4); |
35 |
| - |
36 |
| - RNG_SOURCE.with(|f| { |
37 |
| - let mut source = f.borrow_mut(); |
38 |
| - if source.is_none() { |
39 |
| - *source = Some(getrandom_init()?); |
| 18 | + static IS_NODE: LazyBool = LazyBool::new(); |
| 19 | + if IS_NODE.unsync_init(|| node_crypto().is_some()) { |
| 20 | + if node_crypto().unwrap().random_fill_sync(dest).is_err() { |
| 21 | + return Err(error::BINDGEN_NODE_FAILED); |
40 | 22 | }
|
41 |
| - |
42 |
| - match source.as_ref().unwrap() { |
43 |
| - RngSource::Node(n) => n.random_fill_sync(dest), |
44 |
| - RngSource::Browser(n) => { |
45 |
| - // see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues |
46 |
| - // |
47 |
| - // where it says: |
48 |
| - // |
49 |
| - // > A QuotaExceededError DOMException is thrown if the |
50 |
| - // > requested length is greater than 65536 bytes. |
51 |
| - for chunk in dest.chunks_mut(65536) { |
52 |
| - n.get_random_values(chunk) |
53 |
| - } |
| 23 | + } else { |
| 24 | + let crypto = browser_crypto().ok_or(error::BINDGEN_WEB_CRYPTO)?; |
| 25 | + // https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues |
| 26 | + // > A QuotaExceededError DOMException is thrown if the |
| 27 | + // > requested length is greater than 65536 bytes. |
| 28 | + for chunk in dest.chunks_mut(65536) { |
| 29 | + if crypto.get_random_values_with_u8_array(chunk).is_err() { |
| 30 | + return Err(error::BINDGEN_WEB_FAILED); |
54 | 31 | }
|
55 |
| - }; |
56 |
| - Ok(()) |
57 |
| - }) |
58 |
| -} |
59 |
| - |
60 |
| -fn getrandom_init() -> Result<RngSource, Error> { |
61 |
| - if let Ok(self_) = Global::get_self() { |
62 |
| - // If `self` is defined then we're in a browser somehow (main window |
63 |
| - // or web worker). Here we want to try to use |
64 |
| - // `crypto.getRandomValues`, but if `crypto` isn't defined we assume |
65 |
| - // we're in an older web browser and the OS RNG isn't available. |
66 |
| - |
67 |
| - let crypto = self_.crypto(); |
68 |
| - if crypto.is_undefined() { |
69 |
| - return Err(BINDGEN_CRYPTO_UNDEF); |
70 | 32 | }
|
| 33 | + } |
| 34 | + Ok(()) |
| 35 | +} |
71 | 36 |
|
72 |
| - // Test if `crypto.getRandomValues` is undefined as well |
73 |
| - let crypto: BrowserCrypto = crypto.into(); |
74 |
| - if crypto.get_random_values_fn().is_undefined() { |
75 |
| - return Err(BINDGEN_GRV_UNDEF); |
76 |
| - } |
| 37 | +fn node_crypto() -> Option<NodeCrypto> { |
| 38 | + node_require("crypto").ok() |
| 39 | +} |
77 | 40 |
|
78 |
| - return Ok(RngSource::Browser(crypto)); |
| 41 | +fn browser_crypto() -> Option<Crypto> { |
| 42 | + // Support calling self.crypto in the main window or a Web Worker. |
| 43 | + if let Some(window) = window() { |
| 44 | + return window.crypto().ok(); |
79 | 45 | }
|
80 |
| - |
81 |
| - return Ok(RngSource::Node(node_require("crypto"))); |
| 46 | + let worker = global().dyn_into::<WorkerGlobalScope>().ok()?; |
| 47 | + worker.crypto().ok() |
82 | 48 | }
|
83 | 49 |
|
84 | 50 | #[wasm_bindgen]
|
85 | 51 | extern "C" {
|
86 |
| - type Global; |
87 |
| - #[wasm_bindgen(getter, catch, static_method_of = Global, js_class = self, js_name = self)] |
88 |
| - fn get_self() -> Result<Self_, JsValue>; |
89 |
| - |
90 |
| - type Self_; |
91 |
| - #[wasm_bindgen(method, getter, structural)] |
92 |
| - fn crypto(me: &Self_) -> JsValue; |
93 |
| - |
94 |
| - #[derive(Clone, Debug)] |
95 |
| - 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 |
| - #[wasm_bindgen(js_name = require)] |
106 |
| - fn node_require(s: &str) -> NodeCrypto; |
| 52 | + #[wasm_bindgen(catch, js_name = require)] |
| 53 | + fn node_require(s: &str) -> Result<NodeCrypto, JsValue>; |
107 | 54 |
|
108 | 55 | #[derive(Clone, Debug)]
|
109 | 56 | type NodeCrypto;
|
110 | 57 |
|
111 |
| - #[wasm_bindgen(method, js_name = randomFillSync, structural)] |
112 |
| - fn random_fill_sync(me: &NodeCrypto, buf: &mut [u8]); |
| 58 | + #[wasm_bindgen(catch, method, js_name = randomFillSync)] |
| 59 | + fn random_fill_sync(me: &NodeCrypto, buf: &mut [u8]) -> Result<(), JsValue>; |
113 | 60 | }
|
0 commit comments