diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 93363ba0..fefdcdf9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -229,13 +229,28 @@ jobs: # run: cargo test web: - name: Web + name: Web ${{ matrix.rust.description }} runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + rust: + - { version: stable, atomics: false } + - { + description: with Atomics, + atomics: true, + version: nightly, + components: rust-src, + flags: '-Ctarget-feature=+atomics,+bulk-memory', + args: '-Zbuild-std=panic_abort,std', + } steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@master with: + toolchain: ${{ matrix.rust.version }} targets: wasm32-unknown-unknown + components: ${{ matrix.rust.components }} # - name: Install precompiled wasm-bindgen # shell: bash # run: | @@ -251,39 +266,39 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Test (Node) env: - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" - run: cargo test --target wasm32-unknown-unknown --features std + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} + run: cargo test --target wasm32-unknown-unknown --features std ${{ matrix.rust.args }} - name: Test (Firefox) env: GECKODRIVER: geckodriver WASM_BINDGEN_USE_BROWSER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" - run: cargo test --target wasm32-unknown-unknown --features std + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} + run: cargo test --target wasm32-unknown-unknown --features std ${{ matrix.rust.args }} - name: Test (Chrome) env: CHROMEDRIVER: chromedriver WASM_BINDGEN_USE_BROWSER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" - run: cargo test --target wasm32-unknown-unknown --features std + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} + run: cargo test --target wasm32-unknown-unknown --features std ${{ matrix.rust.args }} - name: Test (dedicated worker) env: GECKODRIVER: geckodriver WASM_BINDGEN_USE_DEDICATED_WORKER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" - run: cargo test --target wasm32-unknown-unknown --features std + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} + run: cargo test --target wasm32-unknown-unknown --features std ${{ matrix.rust.args }} - name: Test (shared worker) env: GECKODRIVER: geckodriver WASM_BINDGEN_USE_SHARED_WORKER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" - run: cargo test --target wasm32-unknown-unknown --features std + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} + run: cargo test --target wasm32-unknown-unknown --features std ${{ matrix.rust.args }} - name: Test (service worker) env: # Firefox doesn't support module service workers and therefor can't import scripts CHROMEDRIVER: chromedriver WASM_BINDGEN_USE_SERVICE_WORKER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" - run: cargo test --target wasm32-unknown-unknown --features std + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} + run: cargo test --target wasm32-unknown-unknown --features std ${{ matrix.rust.args }} wasi: name: WASI diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 2653984d..7f62034a 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -49,6 +49,10 @@ jobs: env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" run: cargo clippy -Zbuild-std --target wasm32-unknown-unknown + - name: Web WASM with atomics (wasm_js.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" -Ctarget-feature=+atomics,+bulk-memory + run: cargo clippy -Zbuild-std --target wasm32-unknown-unknown - name: Linux (linux_android.rs) env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index d42e62db..0fb410d0 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -8,39 +8,51 @@ pub use crate::util::{inner_u32, inner_u64}; #[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!"); +#[cfg(target_feature = "atomics")] use js_sys::Uint8Array; use wasm_bindgen::{prelude::wasm_bindgen, 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 CRYPTO_BUFFER_SIZE: u16 = 256; +// Maximum buffer size allowed in `Crypto.getRandomValuesSize` is 65536 bytes. +// See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues +const MAX_BUFFER_SIZE: u16 = 256; pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { CRYPTO.with(|crypto| { let crypto = crypto.as_ref().ok_or(Error::WEB_CRYPTO)?; - // 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(CRYPTO_BUFFER_SIZE.into()); - for chunk in dest.chunks_mut(CRYPTO_BUFFER_SIZE.into()) { - let chunk_len: u32 = chunk - .len() - .try_into() - .expect("chunk length is bounded by 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 = if chunk_len == u32::from(CRYPTO_BUFFER_SIZE) { - buf.clone() - } else { - buf.subarray(0, chunk_len) - }; - - if crypto.get_random_values(&sub_buf).is_err() { - return Err(Error::WEB_GET_RANDOM_VALUES); + #[cfg(not(target_feature = "atomics"))] + { + for chunk in dest.chunks_mut(MAX_BUFFER_SIZE.into()) { + if crypto.get_random_values(chunk).is_err() { + return Err(Error::WEB_GET_RANDOM_VALUES); + } } + } + #[cfg(target_feature = "atomics")] + { + // 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(MAX_BUFFER_SIZE.into()); + for chunk in dest.chunks_mut(MAX_BUFFER_SIZE.into()) { + let chunk_len: u32 = chunk + .len() + .try_into() + .expect("chunk length is bounded by 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 = if chunk_len == u32::from(MAX_BUFFER_SIZE) { + buf.clone() + } else { + 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::()) }; + // SAFETY: `sub_buf`'s length is the same length as `chunk` + unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr().cast::()) }; + } } Ok(()) }) @@ -54,6 +66,10 @@ extern "C" { #[wasm_bindgen(thread_local_v2, js_name = crypto)] static CRYPTO: Option; // Crypto.getRandomValues() + #[cfg(not(target_feature = "atomics"))] + #[wasm_bindgen(method, js_name = getRandomValues, catch)] + fn get_random_values(this: &Crypto, buf: &mut [MaybeUninit]) -> Result<(), JsValue>; + #[cfg(target_feature = "atomics")] #[wasm_bindgen(method, js_name = getRandomValues, catch)] fn get_random_values(this: &Crypto, buf: &Uint8Array) -> Result<(), JsValue>; }