-
Notifications
You must be signed in to change notification settings - Fork 219
wasm_js: remove the separate codepath for Node.js and TLS caching #557
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+46
−169
Merged
Changes from 10 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
fad53d3
wasm_js: remove TLS
newpavlov d661ff0
fix
newpavlov 9fe766c
add wasm32v1-none support
newpavlov 084c339
Remove separate codepath for Node.js
newpavlov 6abdd33
fix imports
newpavlov 20ac4c7
tweak readme
newpavlov e24e348
Update changelog
newpavlov 5f8f355
Remove outdated node.js section
newpavlov d0c19e8
Remove `getrandom_browser_test` from Cargo.toml
newpavlov dfa56a8
Tweak readme
newpavlov 085dd26
Update README.md
newpavlov 5fba3fd
Apply review suggestions
newpavlov abd06d5
Remove outdated errors
newpavlov 8fa4829
fmt
newpavlov f72daf8
Fix `Error::internal_desc`
newpavlov 27d9dc0
Update src/backends/wasm_js.rs
newpavlov 20827a7
Remove outdated links
newpavlov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,158 +1,55 @@ | ||
//! Implementation for WASM based on Web and Node.js | ||
use crate::Error; | ||
|
||
extern crate std; | ||
use std::{mem::MaybeUninit, thread_local}; | ||
use core::mem::MaybeUninit; | ||
|
||
pub use crate::util::{inner_u32, inner_u64}; | ||
|
||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown",)))] | ||
#[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] | ||
compile_error!("`wasm_js` backend can be enabled only for OS-less WASM targets!"); | ||
|
||
use js_sys::{global, Function, Uint8Array}; | ||
use js_sys::{global, Uint8Array}; | ||
use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; | ||
|
||
// Size of our temporary Uint8Array buffer used with WebCrypto methods | ||
// Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues | ||
const WEB_CRYPTO_BUFFER_SIZE: u16 = 256; | ||
newpavlov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Node.js's crypto.randomFillSync requires the size to be less than 2**31. | ||
const NODE_MAX_BUFFER_SIZE: usize = (1 << 31) - 1; | ||
|
||
enum RngSource { | ||
Node(NodeCrypto), | ||
Web(WebCrypto, Uint8Array), | ||
} | ||
|
||
// JsValues are always per-thread, so we initialize RngSource for each thread. | ||
// See: https://github.com/rustwasm/wasm-bindgen/pull/955 | ||
thread_local!( | ||
static RNG_SOURCE: Result<RngSource, Error> = getrandom_init(); | ||
); | ||
|
||
pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> { | ||
RNG_SOURCE.with(|result| { | ||
let source = result.as_ref().map_err(|&e| e)?; | ||
|
||
match source { | ||
RngSource::Node(n) => { | ||
for chunk in dest.chunks_mut(NODE_MAX_BUFFER_SIZE) { | ||
// SAFETY: chunk is never used directly, the memory is only | ||
// modified via the Uint8Array view, which is passed | ||
// directly to JavaScript. Also, crypto.randomFillSync does | ||
// not resize the buffer. We know the length is less than | ||
// u32::MAX because of the chunking above. | ||
// Note that this uses the fact that JavaScript doesn't | ||
// have a notion of "uninitialized memory", this is purely | ||
// a Rust/C/C++ concept. | ||
let res = n.random_fill_sync(unsafe { | ||
Uint8Array::view_mut_raw(chunk.as_mut_ptr().cast::<u8>(), chunk.len()) | ||
}); | ||
if res.is_err() { | ||
return Err(Error::NODE_RANDOM_FILL_SYNC); | ||
} | ||
} | ||
} | ||
RngSource::Web(crypto, buf) => { | ||
// getRandomValues does not work with all types of WASM memory, | ||
// so we initially write to browser memory to avoid exceptions. | ||
for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE.into()) { | ||
let chunk_len: u32 = chunk | ||
.len() | ||
.try_into() | ||
.expect("chunk length is bounded by WEB_CRYPTO_BUFFER_SIZE"); | ||
// The chunk can be smaller than buf's length, so we call to | ||
// JS to create a smaller view of buf without allocation. | ||
let sub_buf = buf.subarray(0, chunk_len); | ||
|
||
if crypto.get_random_values(&sub_buf).is_err() { | ||
return Err(Error::WEB_GET_RANDOM_VALUES); | ||
} | ||
|
||
// SAFETY: `sub_buf`'s length is the same length as `chunk` | ||
unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr().cast::<u8>()) }; | ||
} | ||
} | ||
}; | ||
Ok(()) | ||
}) | ||
} | ||
|
||
fn getrandom_init() -> Result<RngSource, Error> { | ||
let global: Global = global().unchecked_into(); | ||
|
||
// Get the Web Crypto interface if we are in a browser, Web Worker, Deno, | ||
// or another environment that supports the Web Cryptography API. This | ||
// also allows for user-provided polyfills in unsupported environments. | ||
let crypto = global.crypto(); | ||
newpavlov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if crypto.is_object() { | ||
let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE.into()); | ||
Ok(RngSource::Web(crypto, buf)) | ||
} else if is_node(&global) { | ||
// If module.require isn't a valid function, we are in an ES module. | ||
let require_fn = Module::require_fn() | ||
.and_then(JsCast::dyn_into::<Function>) | ||
.map_err(|_| Error::NODE_ES_MODULE)?; | ||
let n = require_fn | ||
.call1(&global, &JsValue::from_str("crypto")) | ||
.map_err(|_| Error::NODE_CRYPTO)? | ||
.unchecked_into(); | ||
Ok(RngSource::Node(n)) | ||
} else { | ||
Err(Error::WEB_CRYPTO) | ||
} | ||
} | ||
|
||
// Taken from https://www.npmjs.com/package/browser-or-node | ||
fn is_node(global: &Global) -> bool { | ||
let process = global.process(); | ||
if process.is_object() { | ||
let versions = process.versions(); | ||
if versions.is_object() { | ||
return versions.node().is_string(); | ||
// getRandomValues does not work with all types of WASM memory, | ||
// so we initially write to browser memory to avoid exceptions. | ||
let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE.into()); | ||
for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE.into()) { | ||
let chunk_len: u32 = chunk | ||
.len() | ||
.try_into() | ||
.expect("chunk length is bounded by WEB_CRYPTO_BUFFER_SIZE"); | ||
newpavlov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// The chunk can be smaller than buf's length, so we call to | ||
// JS to create a smaller view of buf without allocation. | ||
let sub_buf = buf.subarray(0, chunk_len); | ||
|
||
if crypto.get_random_values(&sub_buf).is_err() { | ||
return Err(Error::WEB_GET_RANDOM_VALUES); | ||
} | ||
|
||
// SAFETY: `sub_buf`'s length is the same length as `chunk` | ||
unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr().cast::<u8>()) }; | ||
} | ||
false | ||
Ok(()) | ||
} | ||
|
||
#[wasm_bindgen] | ||
extern "C" { | ||
// Return type of js_sys::global() | ||
type Global; | ||
|
||
// Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/) | ||
type WebCrypto; | ||
newpavlov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Getters for the WebCrypto API | ||
#[wasm_bindgen(method, getter)] | ||
fn crypto(this: &Global) -> WebCrypto; | ||
#[wasm_bindgen(method, getter, js_name = msCrypto)] | ||
fn ms_crypto(this: &Global) -> WebCrypto; | ||
// Crypto.getRandomValues() | ||
#[wasm_bindgen(method, js_name = getRandomValues, catch)] | ||
fn get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>; | ||
|
||
// Node JS crypto module (https://nodejs.org/api/crypto.html) | ||
type NodeCrypto; | ||
// crypto.randomFillSync() | ||
#[wasm_bindgen(method, js_name = randomFillSync, catch)] | ||
fn random_fill_sync(this: &NodeCrypto, buf: Uint8Array) -> Result<(), JsValue>; | ||
|
||
// Ideally, we would just use `fn require(s: &str)` here. However, doing | ||
// this causes a Webpack warning. So we instead return the function itself | ||
// and manually invoke it using call1. This also lets us to check that the | ||
// function actually exists, allowing for better error messages. See: | ||
// https://github.com/rust-random/getrandom/issues/224 | ||
// https://github.com/rust-random/getrandom/issues/256 | ||
type Module; | ||
#[wasm_bindgen(getter, static_method_of = Module, js_class = module, js_name = require, catch)] | ||
fn require_fn() -> Result<JsValue, JsValue>; | ||
|
||
// Node JS process Object (https://nodejs.org/api/process.html) | ||
#[wasm_bindgen(method, getter)] | ||
fn process(this: &Global) -> Process; | ||
type Process; | ||
#[wasm_bindgen(method, getter)] | ||
fn versions(this: &Process) -> Versions; | ||
type Versions; | ||
#[wasm_bindgen(method, getter)] | ||
fn node(this: &Versions) -> JsValue; | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.