Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Erhardt <eric.erhardt@microsoft.com>2022-07-07 22:27:35 +0300
committerGitHub <noreply@github.com>2022-07-07 22:27:35 +0300
commita2990b5e245ce08a9e9643361b79dc2f461da773 (patch)
treec9217496579f44ce12a9b0472d3deb5f1a475a4f /src/mono/wasm/runtime
parent961032035a4bd8428acdbd7d3bd2f84fb1771037 (diff)
Better error handling in SubtleCrypto workers (#71693)
* Better error handling in SubtleCrypto workers Handle exceptions from SubtleCrypto by catching and logging exceptions coming from the crypto stack. Reset web worker when a request fails. Also, fix race conditions where the web worker can read its own response as part of the next request. Contributes to #69740
Diffstat (limited to 'src/mono/wasm/runtime')
-rw-r--r--src/mono/wasm/runtime/crypto-worker.ts236
-rw-r--r--src/mono/wasm/runtime/debug.ts69
-rw-r--r--src/mono/wasm/runtime/workers/dotnet-crypto-worker.js201
3 files changed, 370 insertions, 136 deletions
diff --git a/src/mono/wasm/runtime/crypto-worker.ts b/src/mono/wasm/runtime/crypto-worker.ts
index 674f1700d22..d27e185ac63 100644
--- a/src/mono/wasm/runtime/crypto-worker.ts
+++ b/src/mono/wasm/runtime/crypto-worker.ts
@@ -4,6 +4,13 @@
import { Module } from "./imports";
import { mono_assert } from "./types";
+class OperationFailedError extends Error { }
+
+const ERR_ARGS = -1;
+const ERR_WORKER_FAILED = -2;
+const ERR_OP_FAILED = -3;
+const ERR_UNKNOWN = -100;
+
let mono_wasm_crypto: {
channel: LibraryChannel
worker: Worker
@@ -14,28 +21,27 @@ export function dotnet_browser_can_use_subtle_crypto_impl(): number {
}
export function dotnet_browser_simple_digest_hash(ver: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number {
- mono_assert(!!mono_wasm_crypto, "subtle crypto not initialized");
-
const msg = {
func: "digest",
type: ver,
data: Array.from(Module.HEAPU8.subarray(input_buffer, input_buffer + input_len))
};
- const response = mono_wasm_crypto.channel.send_msg(JSON.stringify(msg));
- const digest = JSON.parse(response);
- if (digest.length > output_len) {
- console.info("call_digest: about to throw!");
- throw "DIGEST HASH: Digest length exceeds output length: " + digest.length + " > " + output_len;
+ const result = _send_msg_worker(msg);
+ if (typeof result === "number") {
+ return result;
}
- Module.HEAPU8.set(digest, output_buffer);
- return 1;
+ if (result.length > output_len) {
+ console.error("DIGEST HASH: Digest length exceeds output length: " + result.length + " > " + output_len);
+ return ERR_ARGS;
+ }
+
+ Module.HEAPU8.set(result, output_buffer);
+ return 0;
}
export function dotnet_browser_sign(hashAlgorithm: number, key_buffer: number, key_len: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number {
- mono_assert(!!mono_wasm_crypto, "subtle crypto not initialized");
-
const msg = {
func: "sign",
type: hashAlgorithm,
@@ -43,22 +49,23 @@ export function dotnet_browser_sign(hashAlgorithm: number, key_buffer: number, k
data: Array.from(Module.HEAPU8.subarray(input_buffer, input_buffer + input_len))
};
- const response = mono_wasm_crypto.channel.send_msg(JSON.stringify(msg));
- const signResult = JSON.parse(response);
- if (signResult.length > output_len) {
- console.info("dotnet_browser_sign: about to throw!");
- throw "SIGN HASH: Sign length exceeds output length: " + signResult.length + " > " + output_len;
+ const result = _send_msg_worker(msg);
+ if (typeof result === "number") {
+ return result;
}
- Module.HEAPU8.set(signResult, output_buffer);
- return 1;
+ if (result.length > output_len) {
+ console.error("SIGN HASH: Sign length exceeds output length: " + result.length + " > " + output_len);
+ return ERR_ARGS;
+ }
+
+ Module.HEAPU8.set(result, output_buffer);
+ return 0;
}
const AesBlockSizeBytes = 16; // 128 bits
export function dotnet_browser_encrypt_decrypt(isEncrypting: boolean, key_buffer: number, key_len: number, iv_buffer: number, iv_len: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number {
- mono_assert(!!mono_wasm_crypto, "subtle crypto not initialized");
-
if (input_len <= 0 || input_len % AesBlockSizeBytes !== 0) {
throw "ENCRYPT DECRYPT: data was not a full block: " + input_len;
}
@@ -71,12 +78,14 @@ export function dotnet_browser_encrypt_decrypt(isEncrypting: boolean, key_buffer
data: Array.from(Module.HEAPU8.subarray(input_buffer, input_buffer + input_len))
};
- const response = mono_wasm_crypto.channel.send_msg(JSON.stringify(msg));
- const result = JSON.parse(response);
+ const result = _send_msg_worker(msg);
+ if (typeof result === "number") {
+ return result;
+ }
if (result.length > output_len) {
- console.info("dotnet_browser_encrypt_decrypt: about to throw!");
- throw "ENCRYPT DECRYPT: Encrypt/Decrypt length exceeds output length: " + result.length + " > " + output_len;
+ console.error("ENCRYPT DECRYPT: Encrypt/Decrypt length exceeds output length: " + result.length + " > " + output_len);
+ return ERR_ARGS;
}
Module.HEAPU8.set(result, output_buffer);
@@ -108,6 +117,33 @@ export function init_crypto(): void {
}
}
+function _send_msg_worker(msg: any): any {
+ mono_assert(!!mono_wasm_crypto, "subtle crypto not initialized");
+
+ try {
+ const response = mono_wasm_crypto.channel.send_msg(JSON.stringify(msg));
+ const responseJson = JSON.parse(response);
+
+ if (responseJson.error !== undefined) {
+ console.error(`Worker failed with: ${responseJson.error}`);
+ if (responseJson.error_type == "ArgumentsError")
+ return ERR_ARGS;
+ if (responseJson.error_type == "WorkerFailedError")
+ return ERR_WORKER_FAILED;
+
+ return ERR_UNKNOWN;
+ }
+
+ return responseJson.result;
+ } catch (err) {
+ if (err instanceof Error && err.stack !== undefined)
+ console.error(`${err.stack}`);
+ else
+ console.error(`_send_msg_worker failed: ${err}`);
+ return ERR_OP_FAILED;
+ }
+}
+
class LibraryChannel {
private msg_char_len: number;
private comm_buf: SharedArrayBuffer;
@@ -133,6 +169,8 @@ class LibraryChannel {
private get STATE_REQ_P(): number { return 3; } // Request has multiple parts
private get STATE_RESP_P(): number { return 4; } // Response has multiple parts
private get STATE_AWAIT(): number { return 5; } // Awaiting the next part
+ private get STATE_REQ_FAILED(): number { return 6; } // The Request failed
+ private get STATE_RESET(): number { return 7; } // Reset to a known state
private constructor(msg_char_len: number) {
this.msg_char_len = msg_char_len;
@@ -156,21 +194,50 @@ class LibraryChannel {
public get_comm_buffer(): SharedArrayBuffer { return this.comm_buf; }
public send_msg(msg: string): string {
- if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_IDLE) {
- throw "OWNER: Invalid sync communication channel state. " + Atomics.load(this.comm, this.STATE_IDX);
+ try {
+ let state = Atomics.load(this.comm, this.STATE_IDX);
+ if (state !== this.STATE_IDLE)
+ console.log(`send_msg, waiting for idle now, ${state}`);
+ state = this.wait_for_state(pstate => pstate == this.STATE_IDLE, "waiting");
+
+ this.send_request(msg);
+ return this.read_response();
+ } catch (err) {
+ this.reset(LibraryChannel._stringify_err(err));
+ throw err;
+ }
+ finally {
+ const state = Atomics.load(this.comm, this.STATE_IDX);
+ if (state !== this.STATE_IDLE)
+ console.log(`state at end of send_msg: ${state}`);
}
- this.send_request(msg);
- return this.read_response();
}
public shutdown(): void {
- if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_IDLE) {
- throw "OWNER: Invalid sync communication channel state. " + Atomics.load(this.comm, this.STATE_IDX);
- }
+ console.debug("Shutting down crypto");
+ const state = Atomics.load(this.comm, this.STATE_IDX);
+ if (state !== this.STATE_IDLE)
+ throw new Error(`OWNER: Invalid sync communication channel state: ${state}`);
// Notify webworker
Atomics.store(this.comm, this.MSG_SIZE_IDX, 0);
- Atomics.store(this.comm, this.STATE_IDX, this.STATE_SHUTDOWN);
+ this._change_state_locked(this.STATE_SHUTDOWN);
+ Atomics.notify(this.comm, this.STATE_IDX);
+ }
+
+ private reset(reason: string): void {
+ console.debug(`reset: ${reason}`);
+ const state = Atomics.load(this.comm, this.STATE_IDX);
+ if (state === this.STATE_SHUTDOWN)
+ return;
+
+ if (state === this.STATE_RESET || state === this.STATE_IDLE) {
+ console.debug(`state is already RESET or idle: ${state}`);
+ return;
+ }
+
+ Atomics.store(this.comm, this.MSG_SIZE_IDX, 0);
+ this._change_state_locked(this.STATE_RESET);
Atomics.notify(this.comm, this.STATE_IDX);
}
@@ -182,20 +249,22 @@ class LibraryChannel {
for (; ;) {
this.acquire_lock();
- // Write the message and return how much was written.
- const wrote = this.write_to_msg(msg, msg_written, msg_len);
- msg_written += wrote;
+ try {
+ // Write the message and return how much was written.
+ const wrote = this.write_to_msg(msg, msg_written, msg_len);
+ msg_written += wrote;
- // Indicate how much was written to the this.msg buffer.
- Atomics.store(this.comm, this.MSG_SIZE_IDX, wrote);
+ // Indicate how much was written to the this.msg buffer.
+ Atomics.store(this.comm, this.MSG_SIZE_IDX, wrote);
- // Indicate if this was the whole message or part of it.
- state = msg_written === msg_len ? this.STATE_REQ : this.STATE_REQ_P;
+ // Indicate if this was the whole message or part of it.
+ state = msg_written === msg_len ? this.STATE_REQ : this.STATE_REQ_P;
- // Notify webworker
- Atomics.store(this.comm, this.STATE_IDX, state);
-
- this.release_lock();
+ // Notify webworker
+ this._change_state_locked(state);
+ } finally {
+ this.release_lock();
+ }
Atomics.notify(this.comm, this.STATE_IDX);
@@ -203,11 +272,7 @@ class LibraryChannel {
if (state === this.STATE_REQ)
break;
- // Wait for the worker to be ready for the next part.
- // - Atomics.wait() is not permissible on the main thread.
- do {
- state = Atomics.load(this.comm, this.STATE_IDX);
- } while (state !== this.STATE_AWAIT);
+ this.wait_for_state(state => state == this.STATE_AWAIT, "send_request");
}
}
@@ -223,45 +288,61 @@ class LibraryChannel {
}
private read_response(): string {
- let state;
let response = "";
for (; ;) {
- // Wait for webworker response.
- // - Atomics.wait() is not permissible on the main thread.
- do {
- state = Atomics.load(this.comm, this.STATE_IDX);
- } while (state !== this.STATE_RESP && state !== this.STATE_RESP_P);
-
+ const state = this.wait_for_state(state => state == this.STATE_RESP || state == this.STATE_RESP_P, "read_response");
this.acquire_lock();
- const size_to_read = Atomics.load(this.comm, this.MSG_SIZE_IDX);
+ try {
+ const size_to_read = Atomics.load(this.comm, this.MSG_SIZE_IDX);
+
+ // Append the latest part of the message.
+ response += this.read_from_msg(0, size_to_read);
- // Append the latest part of the message.
- response += this.read_from_msg(0, size_to_read);
+ // The response is complete.
+ if (state === this.STATE_RESP) {
+ Atomics.store(this.comm, this.MSG_SIZE_IDX, 0);
+ break;
+ }
- // The response is complete.
- if (state === this.STATE_RESP) {
+ // Reset the size and transition to await state.
+ Atomics.store(this.comm, this.MSG_SIZE_IDX, 0);
+ this._change_state_locked(this.STATE_AWAIT);
+ } finally {
this.release_lock();
- break;
}
-
- // Reset the size and transition to await state.
- Atomics.store(this.comm, this.MSG_SIZE_IDX, 0);
- Atomics.store(this.comm, this.STATE_IDX, this.STATE_AWAIT);
-
- this.release_lock();
-
Atomics.notify(this.comm, this.STATE_IDX);
}
// Reset the communication channel's state and let the
// webworker know we are done.
- Atomics.store(this.comm, this.STATE_IDX, this.STATE_IDLE);
+ this._change_state_locked(this.STATE_IDLE);
Atomics.notify(this.comm, this.STATE_IDX);
return response;
}
+ private _change_state_locked(newState: number): void {
+ Atomics.store(this.comm, this.STATE_IDX, newState);
+ }
+
+ private wait_for_state(is_ready: (state: number) => boolean, msg: string): number {
+ // Wait for webworker
+ // - Atomics.wait() is not permissible on the main thread.
+ for (; ;) {
+ const lock_state = Atomics.load(this.comm, this.LOCK_IDX);
+ if (lock_state !== this.LOCK_UNLOCKED)
+ continue;
+
+ const state = Atomics.load(this.comm, this.STATE_IDX);
+ if (state == this.STATE_REQ_FAILED)
+ throw new OperationFailedError(`Worker failed during ${msg} with state=${state}`);
+
+ if (is_ready(state))
+ return state;
+ }
+ }
+
private read_from_msg(begin: number, end: number): string {
const slicedMessage: number[] = [];
this.msg.slice(begin, end).forEach((value, index) => slicedMessage[index] = value);
@@ -269,18 +350,29 @@ class LibraryChannel {
}
private acquire_lock() {
- while (Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_UNLOCKED, this.LOCK_OWNED) !== this.LOCK_UNLOCKED) {
- // empty
+ for (; ;) {
+ const lock_state = Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_UNLOCKED, this.LOCK_OWNED);
+
+ if (lock_state === this.LOCK_UNLOCKED) {
+ const state = Atomics.load(this.comm, this.STATE_IDX);
+ if (state === this.STATE_REQ_FAILED)
+ throw new OperationFailedError("Worker failed");
+ return;
+ }
}
}
private release_lock() {
const result = Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_OWNED, this.LOCK_UNLOCKED);
if (result !== this.LOCK_OWNED) {
- throw "CRYPTO: LibraryChannel tried to release a lock that wasn't acquired: " + result;
+ throw new Error("CRYPTO: LibraryChannel tried to release a lock that wasn't acquired: " + result);
}
}
+ private static _stringify_err(err: any) {
+ return (err instanceof Error && err.stack !== undefined) ? err.stack : err;
+ }
+
public static create(msg_char_len: number): LibraryChannel {
if (msg_char_len === undefined) {
msg_char_len = 1024; // Default size is arbitrary but is in 'char' units (i.e. UTF-16 code points).
diff --git a/src/mono/wasm/runtime/debug.ts b/src/mono/wasm/runtime/debug.ts
index 0b13121b27a..5ae913e3c75 100644
--- a/src/mono/wasm/runtime/debug.ts
+++ b/src/mono/wasm/runtime/debug.ts
@@ -472,6 +472,75 @@ export function mono_wasm_trace_logger(log_domain_ptr: CharPtr, log_level_ptr: C
}
}
+export function setup_proxy_console(id: string, originalConsole: Console, origin: string): void {
+ function proxyConsoleMethod(prefix: string, func: any, asJson: boolean) {
+ return function (...args: any[]) {
+ try {
+ let payload = args[0];
+ if (payload === undefined) payload = "undefined";
+ else if (payload === null) payload = "null";
+ else if (typeof payload === "function") payload = payload.toString();
+ else if (typeof payload !== "string") {
+ try {
+ payload = JSON.stringify(payload);
+ } catch (e) {
+ payload = payload.toString();
+ }
+ }
+
+ if (typeof payload === "string")
+ payload = `[${id}] ${payload}`;
+
+ if (asJson) {
+ func(JSON.stringify({
+ method: prefix,
+ payload: payload,
+ arguments: args
+ }));
+ } else {
+ func([prefix + payload, ...args.slice(1)]);
+ }
+ } catch (err) {
+ originalConsole.error(`proxyConsole failed: ${err}`);
+ }
+ };
+ }
+
+ const originalConsoleObj : any = originalConsole;
+ const methods = ["debug", "trace", "warn", "info", "error"];
+ for (const m of methods) {
+ if (typeof (originalConsoleObj[m]) !== "function") {
+ originalConsoleObj[m] = proxyConsoleMethod(`console.${m}: `, originalConsole.log, false);
+ }
+ }
+
+ const consoleUrl = `${origin}/console`.replace("https://", "wss://").replace("http://", "ws://");
+
+ const consoleWebSocket = new WebSocket(consoleUrl);
+ consoleWebSocket.onopen = function () {
+ originalConsole.log(`browser: [${id}] Console websocket connected.`);
+ };
+ consoleWebSocket.onerror = function (event) {
+ originalConsole.error(`[${id}] websocket error: ${event}`, event);
+ };
+ consoleWebSocket.onclose = function (event) {
+ originalConsole.error(`[${id}] websocket closed: ${event}`, event);
+ };
+
+ const send = (msg: string) => {
+ if (consoleWebSocket.readyState === WebSocket.OPEN) {
+ consoleWebSocket.send(msg);
+ }
+ else {
+ originalConsole.log(msg);
+ }
+ };
+
+ // redirect output early, so that when emscripten starts it's already redirected
+ for (const m of ["log", ...methods])
+ originalConsoleObj[m] = proxyConsoleMethod(`console.${m}`, send, true);
+}
+
type CallDetails = {
value: string
}
diff --git a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.js b/src/mono/wasm/runtime/workers/dotnet-crypto-worker.js
index f6d66c82da6..5fa89790c22 100644
--- a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.js
+++ b/src/mono/wasm/runtime/workers/dotnet-crypto-worker.js
@@ -5,6 +5,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+import { setup_proxy_console } from "../debug";
+
+class FailedOrStoppedLoopError extends Error {}
+class ArgumentsError extends Error {}
+class WorkerFailedError extends Error {}
+
var ChannelWorker = {
_impl: class {
// LOCK states
@@ -24,6 +30,8 @@ var ChannelWorker = {
get STATE_REQ_P() { return 3; } // Request has multiple parts
get STATE_RESP_P() { return 4; } // Response has multiple parts
get STATE_AWAIT() { return 5; } // Awaiting the next part
+ get STATE_REQ_FAILED() { return 6; } // The Request failed
+ get STATE_RESET() { return 7; } // Reset to a known state
// END ChannelOwner contract - shared constants.
constructor(comm_buf, msg_buf, msg_char_len) {
@@ -32,61 +40,102 @@ var ChannelWorker = {
this.msg_char_len = msg_char_len;
}
- async await_request(async_call) {
+ async run_message_loop(async_op) {
for (;;) {
- // Wait for signal to perform operation
- Atomics.wait(this.comm, this.STATE_IDX, this.STATE_IDLE);
-
- // Read in request
- var req = this._read_request();
- if (req === this.STATE_SHUTDOWN)
- break;
-
- var resp = null;
try {
- // Perform async action based on request
- resp = await async_call(req);
- }
- catch (err) {
- console.log("Request error: " + err);
- resp = JSON.stringify(err);
+ // Wait for signal to perform operation
+ let state;
+ do {
+ this._wait(this.STATE_IDLE);
+ state = Atomics.load(this.comm, this.STATE_IDX);
+ } while (state !== this.STATE_REQ && state !== this.STATE_REQ_P && state !== this.STATE_SHUTDOWN && state !== this.STATE_REQ_FAILED && state !== this.STATE_RESET);
+
+ this._throw_if_reset_or_shutdown();
+
+ // Read in request
+ var req = this._read_request();
+ var resp = {};
+ try {
+ // Perform async action based on request
+ resp.result = await async_op(req);
+ }
+ catch (err) {
+ resp.error_type = typeof err;
+ resp.error = _stringify_err(err);
+ console.error(`Request error: ${resp.error}. req was: ${req}`);
+ }
+
+ // Send response
+ this._send_response(JSON.stringify(resp));
+ } catch (err) {
+ if (err instanceof FailedOrStoppedLoopError) {
+ const state = Atomics.load(this.comm, this.STATE_IDX);
+ if (state === this.STATE_SHUTDOWN)
+ break;
+ if (state === this.STATE_RESET)
+ console.debug(`caller failed, reseting worker`);
+ } else {
+ console.error(`Worker failed to handle the request: ${_stringify_err(err)}`);
+ this._change_state_locked(this.STATE_REQ_FAILED);
+ Atomics.store(this.comm, this.LOCK_IDX, this.LOCK_UNLOCKED);
+
+ console.debug(`set state to failed, now waiting to get RESET`);
+ Atomics.wait(this.comm, this.STATE_IDX, this.STATE_REQ_FAILED);
+ const state = Atomics.load(this.comm, this.STATE_IDX);
+ if (state !== this.STATE_RESET) {
+ throw new WorkerFailedError(`expected to RESET, but got ${state}`);
+ }
+ }
+
+ Atomics.store(this.comm, this.MSG_SIZE_IDX, 0);
+ Atomics.store(this.comm, this.LOCK_IDX, this.LOCK_UNLOCKED);
+ this._change_state_locked(this.STATE_IDLE);
}
- // Send response
- this._send_response(resp);
+ const state = Atomics.load(this.comm, this.STATE_IDX);
+ const lock_state = Atomics.load(this.comm, this.LOCK_IDX);
+
+ if (state !== this.STATE_IDLE && state !== this.STATE_REQ && state !== this.STATE_REQ_P)
+ console.error(`-- state is not idle at the top of the loop: ${state}, and lock_state: ${lock_state}`);
+ if (lock_state !== this.LOCK_UNLOCKED && state !== this.STATE_REQ && state !== this.STATE_REQ_P && state !== this.STATE_IDLE)
+ console.error(`-- lock is not unlocked at the top of the loop: ${lock_state}, and state: ${state}`);
}
+
+ Atomics.store(this.comm, this.MSG_SIZE_IDX, 0);
+ this._change_state_locked(this.STATE_SHUTDOWN);
+ console.debug("******* run_message_loop ending");
}
_read_request() {
var request = "";
for (;;) {
this._acquire_lock();
+ try {
+ this._throw_if_reset_or_shutdown();
- // Get the current state and message size
- var state = Atomics.load(this.comm, this.STATE_IDX);
- var size_to_read = Atomics.load(this.comm, this.MSG_SIZE_IDX);
+ // Get the current state and message size
+ var state = Atomics.load(this.comm, this.STATE_IDX);
+ var size_to_read = Atomics.load(this.comm, this.MSG_SIZE_IDX);
- // Append the latest part of the message.
- request += this._read_from_msg(0, size_to_read);
+ // Append the latest part of the message.
+ request += this._read_from_msg(0, size_to_read);
- // The request is complete.
- if (state === this.STATE_REQ) {
- this._release_lock();
- break;
- }
+ // The request is complete.
+ if (state === this.STATE_REQ) {
+ break;
+ }
- // Shutdown the worker.
- if (state === this.STATE_SHUTDOWN) {
+ // Shutdown the worker.
+ this._throw_if_reset_or_shutdown();
+
+ // Reset the size and transition to await state.
+ Atomics.store(this.comm, this.MSG_SIZE_IDX, 0);
+ this._change_state_locked(this.STATE_AWAIT);
+ } finally {
this._release_lock();
- return this.STATE_SHUTDOWN;
}
- // Reset the size and transition to await state.
- Atomics.store(this.comm, this.MSG_SIZE_IDX, 0);
- Atomics.store(this.comm, this.STATE_IDX, this.STATE_AWAIT);
- this._release_lock();
-
- Atomics.wait(this.comm, this.STATE_IDX, this.STATE_AWAIT);
+ this._wait(this.STATE_AWAIT);
}
return request;
@@ -98,7 +147,7 @@ var ChannelWorker = {
_send_response(msg) {
if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ)
- throw "WORKER: Invalid sync communication channel state.";
+ throw new WorkerFailedError(`WORKER: Invalid sync communication channel state.`);
var state; // State machine variable
const msg_len = msg.length;
@@ -107,24 +156,26 @@ var ChannelWorker = {
for (;;) {
this._acquire_lock();
- // Write the message and return how much was written.
- var wrote = this._write_to_msg(msg, msg_written, msg_len);
- msg_written += wrote;
-
- // Indicate how much was written to the this.msg buffer.
- Atomics.store(this.comm, this.MSG_SIZE_IDX, wrote);
+ try {
+ // Write the message and return how much was written.
+ var wrote = this._write_to_msg(msg, msg_written, msg_len);
+ msg_written += wrote;
- // Indicate if this was the whole message or part of it.
- state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P;
+ // Indicate how much was written to the this.msg buffer.
+ Atomics.store(this.comm, this.MSG_SIZE_IDX, wrote);
- // Update the state
- Atomics.store(this.comm, this.STATE_IDX, state);
+ // Indicate if this was the whole message or part of it.
+ state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P;
- this._release_lock();
+ // Update the state
+ this._change_state_locked(state);
+ } finally {
+ this._release_lock();
+ }
// Wait for the transition to know the main thread has
// received the response by moving onto a new state.
- Atomics.wait(this.comm, this.STATE_IDX, state);
+ this._wait(state);
// Done sending response.
if (state === this.STATE_RESP)
@@ -143,18 +194,37 @@ var ChannelWorker = {
return ii - start;
}
+ _change_state_locked(newState) {
+ Atomics.store(this.comm, this.STATE_IDX, newState);
+ }
+
_acquire_lock() {
- while (Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_UNLOCKED, this.LOCK_OWNED) !== this.LOCK_UNLOCKED) {
- // empty
+ for (;;) {
+ const lockState = Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_UNLOCKED, this.LOCK_OWNED);
+ this._throw_if_reset_or_shutdown();
+
+ if (lockState === this.LOCK_UNLOCKED)
+ return;
}
}
_release_lock() {
const result = Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_OWNED, this.LOCK_UNLOCKED);
if (result !== this.LOCK_OWNED) {
- throw "CRYPTO: ChannelWorker tried to release a lock that wasn't acquired: " + result;
+ throw new WorkerFailedError("CRYPTO: ChannelWorker tried to release a lock that wasn't acquired: " + result);
}
}
+
+ _wait(expected_state) {
+ Atomics.wait(this.comm, this.STATE_IDX, expected_state);
+ this._throw_if_reset_or_shutdown();
+ }
+
+ _throw_if_reset_or_shutdown() {
+ const state = Atomics.load(this.comm, this.STATE_IDX);
+ if (state === this.STATE_RESET || state === this.STATE_SHUTDOWN)
+ throw new FailedOrStoppedLoopError();
+ }
},
create: function (comm_buf, msg_buf, msg_char_len) {
@@ -193,7 +263,7 @@ function get_hash_name(type) {
case 2: return "SHA-384";
case 3: return "SHA-512";
default:
- throw "CRYPTO: Unknown digest: " + type;
+ throw new ArgumentsError("CRYPTO: Unknown digest: " + type);
}
}
@@ -245,7 +315,7 @@ async function decrypt(algorithm, cryptoKey, data) {
);
const encryptedPaddingBlock = new Uint8Array(encryptedPaddingBlockResult);
- for (var i = 0; i < encryptedPaddingBlock.length; i++) {
+ for (let i = 0; i < encryptedPaddingBlock.length; i++) {
data.push(encryptedPaddingBlock[i]);
}
@@ -267,28 +337,31 @@ function importKey(key, algorithmName, keyUsage) {
}
// Operation to perform.
-async function async_call(msg) {
+async function handle_req_async(msg) {
const req = JSON.parse(msg);
if (req.func === "digest") {
- const digestArr = await call_digest(req.type, new Uint8Array(req.data));
- return JSON.stringify(digestArr);
+ return await call_digest(req.type, new Uint8Array(req.data));
}
else if (req.func === "sign") {
- const signResult = await sign(req.type, new Uint8Array(req.key), new Uint8Array(req.data));
- return JSON.stringify(signResult);
+ return await sign(req.type, new Uint8Array(req.key), new Uint8Array(req.data));
}
else if (req.func === "encrypt_decrypt") {
- const signResult = await encrypt_decrypt(req.isEncrypting, req.key, req.iv, req.data);
- return JSON.stringify(signResult);
+ return await encrypt_decrypt(req.isEncrypting, req.key, req.iv, req.data);
}
else {
- throw "CRYPTO: Unknown request: " + req.func;
+ throw new ArgumentsError("CRYPTO: Unknown request: " + req.func);
}
}
+function _stringify_err(err) {
+ return (err instanceof Error && err.stack !== undefined) ? err.stack : err;
+}
+
var s_channel;
+setup_proxy_console("crypto-worker", console, self.location.origin);
+
// Initialize WebWorker
onmessage = function (p) {
var data = p;
@@ -296,5 +369,5 @@ onmessage = function (p) {
data = p.data;
}
s_channel = ChannelWorker.create(data.comm_buf, data.msg_buf, data.msg_char_len);
- s_channel.await_request(async_call);
+ s_channel.run_message_loop(handle_req_async);
};