Skip to content

Commit

Permalink
Account for SharedArrayBuffer not being available
Browse files Browse the repository at this point in the history
  • Loading branch information
daxpedda committed Dec 5, 2024
1 parent 81e43f7 commit 59c32bf
Showing 1 changed file with 64 additions and 31 deletions.
95 changes: 64 additions & 31 deletions src/backends/wasm_js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@
extern crate std;

use crate::Error;
use core::{
mem::MaybeUninit,
sync::atomic::{AtomicU8, Ordering},
};
use core::mem::MaybeUninit;
#[cfg(feature = "std")]
use std::thread_local;

Expand All @@ -18,42 +15,18 @@ 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!");

use js_sys::{SharedArrayBuffer, Uint8Array, WebAssembly::Memory};
use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
use js_sys::{JsString, 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;

const MEMORY_KIND_UNINIT: u8 = 0;
const MEMORY_KIND_NOT_SHARED: u8 = 1;
const MEMORY_KIND_SHARED: u8 = 2;

static MEMORY_KIND: AtomicU8 = AtomicU8::new(0);

pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
CRYPTO.with(|crypto| {
let crypto = crypto.as_ref().ok_or(Error::WEB_CRYPTO)?;

let shared = loop {
break match MEMORY_KIND.load(Ordering::Relaxed) {
MEMORY_KIND_NOT_SHARED => false,
MEMORY_KIND_SHARED => true,
MEMORY_KIND_UNINIT => {
let memory: Memory = wasm_bindgen::memory().unchecked_into();
let val = if memory.buffer().is_instance_of::<SharedArrayBuffer>() {
MEMORY_KIND_SHARED
} else {
MEMORY_KIND_NOT_SHARED
};
MEMORY_KIND.store(val, Ordering::Relaxed);
continue;
}
_ => unreachable!(),
};
};

if shared {
if is_sab() {
// 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());
Expand Down Expand Up @@ -88,7 +61,62 @@ pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
})
}

#[cfg(not(target_feature = "atomics"))]
fn is_sab() -> bool {
use core::sync::atomic::{AtomicU8, Ordering};

use js_sys::WebAssembly::Memory;
use js_sys::{Object, SharedArrayBuffer};
use wasm_bindgen::JsCast;

const MEMORY_KIND_UNINIT: u8 = 0;
const MEMORY_KIND_NOT_SHARED: u8 = 1;
const MEMORY_KIND_SHARED: u8 = 2;

static MEMORY_KIND: AtomicU8 = AtomicU8::new(0);

loop {
break match MEMORY_KIND.load(Ordering::Relaxed) {
MEMORY_KIND_NOT_SHARED => false,
MEMORY_KIND_SHARED => true,
MEMORY_KIND_UNINIT => {
let buffer = wasm_bindgen::memory().unchecked_into::<Memory>().buffer();

// `SharedArrayBuffer` is only available with COOP & COEP. But even without its
// possible to create a shared `WebAssembly.Memory`, so we check for that via
// the constructor name.
//
// Keep in mind that `crossOriginIsolated` is not available on Node.js, in
// which case we can still use `instanceof` because `SharedArrayBuffer` is
// always available.
let shared = match CROSS_ORIGIN_ISOLATED.with(Option::clone) {
Some(true) | None => buffer.is_instance_of::<SharedArrayBuffer>(),
Some(false) => {
let constructor_name = Object::from(buffer).constructor().name();
SHARED_ARRAY_BUFFER_NAME.with(|name| &constructor_name == name)
}
};

let val = if shared {
MEMORY_KIND_SHARED
} else {
MEMORY_KIND_NOT_SHARED
};
MEMORY_KIND.store(val, Ordering::Relaxed);
continue;
}
_ => unreachable!(),
};
}
}

#[cfg(target_feature = "atomics")]
fn is_sab() -> bool {
true
}

#[wasm_bindgen]
#[rustfmt::skip]
extern "C" {
// Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/)
type Crypto;
Expand All @@ -100,4 +128,9 @@ extern "C" {
fn get_random_values(this: &Crypto, buf: &Uint8Array) -> Result<(), JsValue>;
#[wasm_bindgen(method, js_name = getRandomValues, catch)]
fn get_random_values_ref(this: &Crypto, buf: &mut [u8]) -> Result<(), JsValue>;
// Returns the [`crossOriginIsolated`](https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated) global property.
#[wasm_bindgen(thread_local_v2, js_namespace = globalThis, js_name = crossOriginIsolated)]
static CROSS_ORIGIN_ISOLATED: Option<bool>;
#[wasm_bindgen(thread_local_v2, static_string)]
static SHARED_ARRAY_BUFFER_NAME: JsString = "SharedArrayBuffer";
}

0 comments on commit 59c32bf

Please sign in to comment.