From 6035e71c8895ee74932c26dca8d826ef0f9b0aaa Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Wed, 23 Feb 2022 14:21:45 -0800 Subject: [PATCH 01/19] Use SubtleCrypto API on browser DOM scenarios --- eng/SignCheckExclusionsFile.txt | 1 + eng/liveBuilds.targets | 1 + .../Directory.Build.props | 4 + .../src/Interop/Browser/Interop.Libraries.cs | 12 ++ .../Interop.SimpleDigestHash.cs | 31 ++++ .../src/System.Security.Cryptography.csproj | 12 +- .../HashProviderDispenser.Browser.cs | 13 +- ....cs => SHAHashProvider.Browser.Managed.cs} | 6 +- .../SHAHashProvider.Browser.Native.cs | 96 ++++++++++ src/mono/wasm/runtime/dotnet_crypto_worker.js | 173 ++++++++++++++++++ src/mono/wasm/runtime/exports.ts | 6 + src/mono/wasm/runtime/library_channel.js | 147 +++++++++++++++ src/mono/wasm/wasm.proj | 2 +- src/native/libs/CMakeLists.txt | 2 +- .../CMakeLists.txt | 15 ++ .../pal_browser.h | 18 ++ .../pal_crypto_webworker.c | 30 +++ .../pal_crypto_webworker.h | 23 +++ .../pal_crypto_webworker.js | 41 +++++ 19 files changed, 617 insertions(+), 16 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Browser/Interop.Libraries.cs create mode 100644 src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs rename src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/{SHAHashProvider.Browser.cs => SHAHashProvider.Browser.Managed.cs} (99%) create mode 100644 src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs create mode 100644 src/mono/wasm/runtime/dotnet_crypto_worker.js create mode 100644 src/mono/wasm/runtime/library_channel.js create mode 100644 src/native/libs/System.Security.Cryptography.Native.Browser/CMakeLists.txt create mode 100644 src/native/libs/System.Security.Cryptography.Native.Browser/pal_browser.h create mode 100644 src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c create mode 100644 src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h create mode 100644 src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js diff --git a/eng/SignCheckExclusionsFile.txt b/eng/SignCheckExclusionsFile.txt index b45e2daaceb8d..da5efe7fe1361 100644 --- a/eng/SignCheckExclusionsFile.txt +++ b/eng/SignCheckExclusionsFile.txt @@ -13,3 +13,4 @@ *comhosttemplatecomhostdll.dll;;Template, DO-NOT-SIGN, https://github.com/dotnet/core-setup/pull/7549 *staticapphosttemplateapphostexe.exe;;Template, DO-NOT-SIGN, https://github.com/dotnet/core-setup/pull/7549 *dotnet.js;;Workaround, https://github.com/dotnet/core-eng/issues/9933 +*dotnet_crypto_worker.js;;Workaround, https://github.com/dotnet/core-eng/issues/9933 \ No newline at end of file diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index 77e9a90b97d30..80d7214c4a461 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -185,6 +185,7 @@ $(LibrariesNativeArtifactsPath)dotnet.wasm; $(LibrariesNativeArtifactsPath)dotnet.js.symbols; $(LibrariesNativeArtifactsPath)dotnet.timezones.blat; + $(LibrariesNativeArtifactsPath)dotnet_crypto_worker.js; $(LibrariesNativeArtifactsPath)*.dat;" IsNative="true" /> + @@ -216,13 +217,16 @@ + + + diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Libraries.cs b/src/libraries/Common/src/Interop/Browser/Interop.Libraries.cs new file mode 100644 index 0000000000000..b28d723f0bfc9 --- /dev/null +++ b/src/libraries/Common/src/Interop/Browser/Interop.Libraries.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +internal static partial class Interop +{ + internal static partial class Libraries + { + // Shims + internal const string SystemNative = "libSystem.Native"; + internal const string CryptoNative = "libSystem.Security.Cryptography.Native.Browser"; + } +} diff --git a/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs b/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs new file mode 100644 index 0000000000000..a2455b07817c1 --- /dev/null +++ b/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class BrowserCrypto + { + internal enum SimpleDigest + { + Sha1, + Sha256, + Sha384, + Sha512, + }; + + [DllImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_CanUseSimpleDigestHash")] + internal static extern unsafe bool CanUseSimpleDigestHash(); + + [DllImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_SimpleDigestHash")] + internal static extern unsafe int SimpleDigestHash( + SimpleDigest hash, + byte* input_buffer, + int input_len, + byte* output_buffer, + int output_len); + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index 12a3b430744d6..c89fc99c3af3b 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -1,4 +1,4 @@ - + true $(DefineConstants);INTERNAL_ASYMMETRIC_IMPLEMENTATIONS @@ -529,12 +529,15 @@ - + + + @@ -560,7 +563,8 @@ - + + diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs index d49be47509f26..0170afc26f006 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs @@ -1,17 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using Microsoft.Win32.SafeHandles; using Internal.Cryptography; namespace System.Security.Cryptography { internal static partial class HashProviderDispenser { + internal static readonly bool CanUseSubtleCryptoImpl = Interop.BrowserCrypto.CanUseSimpleDigestHash(); + public static HashProvider CreateHashProvider(string hashAlgorithmId) { switch (hashAlgorithmId) @@ -20,7 +17,9 @@ public static HashProvider CreateHashProvider(string hashAlgorithmId) case HashAlgorithmNames.SHA256: case HashAlgorithmNames.SHA384: case HashAlgorithmNames.SHA512: - return new SHAHashProvider(hashAlgorithmId); + return CanUseSubtleCryptoImpl + ? new SHANativeHashProvider(hashAlgorithmId) + : new SHAManagedHashProvider(hashAlgorithmId); } throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId)); } @@ -38,7 +37,7 @@ public static unsafe int MacData( public static int HashData(string hashAlgorithmId, ReadOnlySpan source, Span destination) { - HashProvider provider = HashProviderDispenser.CreateHashProvider(hashAlgorithmId); + HashProvider provider = CreateHashProvider(hashAlgorithmId); provider.AppendHashData(source); return provider.FinalizeHashAndReset(destination); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Managed.cs similarity index 99% rename from src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.cs rename to src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Managed.cs index 5a72d4de54b07..574cdf8790b8a 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Managed.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.IO; @@ -9,13 +9,13 @@ namespace System.Security.Cryptography { - internal sealed class SHAHashProvider : HashProvider + internal sealed class SHAManagedHashProvider : HashProvider { private int hashSizeInBytes; private SHAManagedImplementationBase impl; private MemoryStream? buffer; - public SHAHashProvider(string hashAlgorithmId) + public SHAManagedHashProvider(string hashAlgorithmId) { switch (hashAlgorithmId) { diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs new file mode 100644 index 0000000000000..4527252d93661 --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.Diagnostics; +using System.Security.Cryptography; + +using SimpleDigest = Interop.BrowserCrypto.SimpleDigest; + +namespace Internal.Cryptography +{ + internal sealed class SHANativeHashProvider : HashProvider + { + private readonly int hashSizeInBytes; + private readonly SimpleDigest impl; + private MemoryStream? buffer; + + public SHANativeHashProvider(string hashAlgorithmId) + { + Debug.Assert(HashProviderDispenser.CanUseSubtleCryptoImpl); + + switch (hashAlgorithmId) + { + case HashAlgorithmNames.SHA1: + impl = SimpleDigest.Sha1; + hashSizeInBytes = 20; + break; + case HashAlgorithmNames.SHA256: + impl = SimpleDigest.Sha256; + hashSizeInBytes = 32; + break; + case HashAlgorithmNames.SHA384: + impl = SimpleDigest.Sha384; + hashSizeInBytes = 48; + break; + case HashAlgorithmNames.SHA512: + impl = SimpleDigest.Sha512; + hashSizeInBytes = 64; + break; + default: + throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId)); + } + } + + public override void AppendHashData(ReadOnlySpan data) + { + buffer ??= new MemoryStream(1000); + buffer.Write(data); + } + + public override int FinalizeHashAndReset(Span destination) + { + GetCurrentHash(destination); + buffer = null; + + return hashSizeInBytes; + } + + public override int GetCurrentHash(Span destination) + { + Debug.Assert(destination.Length >= hashSizeInBytes); + + byte[] srcArray = Array.Empty(); + int srcLength = 0; + if (buffer != null) + { + srcArray = buffer.GetBuffer(); + srcLength = (int)buffer.Length; + } + + unsafe + { + fixed (byte* src = srcArray) + fixed (byte* dest = destination) + { + int res = Interop.BrowserCrypto.SimpleDigestHash(impl, src, srcLength, dest, destination.Length); + Debug.Assert(res != 0); + } + } + + return hashSizeInBytes; + } + + public override int HashSizeInBytes => hashSizeInBytes; + + public override void Dispose(bool disposing) + { + } + + public override void Reset() + { + buffer = null; + } + } +} diff --git a/src/mono/wasm/runtime/dotnet_crypto_worker.js b/src/mono/wasm/runtime/dotnet_crypto_worker.js new file mode 100644 index 0000000000000..5ea07d2bfc09a --- /dev/null +++ b/src/mono/wasm/runtime/dotnet_crypto_worker.js @@ -0,0 +1,173 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +var ChannelWorker = { + _impl: class { + // BEGIN ChannelOwner contract - shared constants. + get STATE_IDX() { return 0; } + get MSG_SIZE_IDX() { return 1; } + + // Communication states. + get STATE_SHUTDOWN() { return -1; } // Shutdown + get STATE_IDLE() { return 0; } + get STATE_REQ() { return 1; } + get STATE_RESP() { return 2; } + 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 + // END ChannelOwner contract - shared constants. + + constructor(comm_buf, msg_buf, msg_char_len) { + this.comm = new Int32Array(comm_buf); + this.msg = new Uint16Array(msg_buf); + this.msg_char_len = msg_char_len; + } + + async await_request(async_call) { + // console.log("await_request()"); + + 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(); + // console.log("Request: " + req); + 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); + } + + // Send response + this._send_response(resp); + } + } + + _read_request() { + var request = ""; + for (;;) { + // 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); + + // The request is complete. + if (state === this.STATE_REQ) + break; + + // Shutdown the worker. + if (state === this.STATE_SHUTDOWN) + 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); + Atomics.wait(this.comm, this.STATE_IDX, this.STATE_AWAIT); + } + + return request; + } + + _read_from_msg(begin, end) { + return String.fromCharCode.apply(null, this.msg.slice(begin, end)); + } + + _send_response(msg) { + if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ) + throw "WORKER: Invalid sync communication channel state."; + + var state; // State machine variable + const msg_len = msg.length; + var msg_written = 0; + + for (;;) { + // 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); + + // Indicate if this was the whole message or part of it. + state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P; + + // Update the state + Atomics.store(this.comm, this.STATE_IDX, state); + + // 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); + + // Done sending response. + if (state === this.STATE_RESP) + break; + } + } + + _write_to_msg(input, start, input_len) { + var mi = 0; + var ii = start; + while (mi < this.msg_char_len && ii < input_len) { + this.msg[mi] = input.charCodeAt(ii); + ii++; // Next character + mi++; // Next buffer index + } + return ii - start; + } + }, + + create: function (comm_buf, msg_buf, msg_char_len) { + return new this._impl(comm_buf, msg_buf, msg_char_len); + } +}; + +async function call_digest(type, data) { + var digest_type = ""; + switch(type) { + case 0: digest_type = "SHA-1"; break; + case 1: digest_type = "SHA-256"; break; + case 2: digest_type = "SHA-384"; break; + case 3: digest_type = "SHA-512"; break; + default: + throw "CRYPTO: Unknown digest: " + type; + } + + // The 'crypto' API is not available in non-browser + // environments (for example, v8 server). + var digest = await crypto.subtle.digest(digest_type, data); + return Array.from(new Uint8Array(digest)); +} + +// Operation to perform. +async function async_call(msg) { + const req = JSON.parse(msg); + + if (req.func === "digest") { + var digestArr = await call_digest(req.type, new Uint8Array(req.data)); + return JSON.stringify(digestArr); + } else { + throw "CRYPTO: Unknown request: " + req.func; + } +} + +var s_channel; + +// Initialize WebWorker +onmessage = function (p) { + var data = p; + if (p.data !== undefined) { + data = p.data; + } + s_channel = ChannelWorker.create(data.comm_buf, data.msg_buf, data.msg_char_len); + s_channel.await_request(async_call); +} diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts index 064ddcfa21478..ab678899dd8d6 100644 --- a/src/mono/wasm/runtime/exports.ts +++ b/src/mono/wasm/runtime/exports.ts @@ -388,6 +388,12 @@ const INTERNAL: any = { mono_wasm_raise_debug_event, mono_wasm_change_debugger_log_level, mono_wasm_runtime_is_ready: runtimeHelpers.mono_wasm_runtime_is_ready, + + // used for browser crypto + mono_wasm_crypto: { + channel: null, + worker: null + }, }; diff --git a/src/mono/wasm/runtime/library_channel.js b/src/mono/wasm/runtime/library_channel.js new file mode 100644 index 0000000000000..a2cf2b8cdb406 --- /dev/null +++ b/src/mono/wasm/runtime/library_channel.js @@ -0,0 +1,147 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +Module [ 'channel' ] = { + _impl: class { + // Index constants for the communication buffer. + get STATE_IDX() { return 0; } + get MSG_SIZE_IDX() { return 1; } + get COMM_LAST_IDX() { return this.MSG_SIZE_IDX; } + + // Communication states. + get STATE_SHUTDOWN() { return -1; } // Shutdown + get STATE_IDLE() { return 0; } + get STATE_REQ() { return 1; } + get STATE_RESP() { return 2; } + 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 + + constructor(msg_char_len) { + this.msg_char_len = msg_char_len; + + const int_bytes = 4; + const comm_byte_len = int_bytes * (this.COMM_LAST_IDX + 1); + this.comm_buf = new SharedArrayBuffer(comm_byte_len); + + // JavaScript character encoding is UTF-16. + const char_bytes = 2; + const msg_byte_len = char_bytes * this.msg_char_len; + this.msg_buf = new SharedArrayBuffer(msg_byte_len); + + // Create the local arrays to use. + this.comm = new Int32Array(this.comm_buf); + this.msg = new Uint16Array(this.msg_buf); + } + + get_msg_len() { return this.msg_char_len; } + get_msg_buffer() { return this.msg_buf; } + get_comm_buffer() { return this.comm_buf; } + + send_msg(msg) { + 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); + } + this._send_request(msg); + return this._read_response(); + } + + shutdown() { + 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); + } + + // Notify webworker + Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); + Atomics.store(this.comm, this.STATE_IDX, this.STATE_SHUTDOWN); + Atomics.notify(this.comm, this.STATE_IDX); + } + + _send_request(msg) { + var state; + const msg_len = msg.length; + var msg_written = 0; + + for (;;) { + // 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); + + // 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); + Atomics.notify(this.comm, this.STATE_IDX); + + // The send message is complete. + 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); + } + } + + _write_to_msg(input, start, input_len) { + var mi = 0; + var ii = start; + while (mi < this.msg_char_len && ii < input_len) { + this.msg[mi] = input.charCodeAt(ii); + ii++; // Next character + mi++; // Next buffer index + } + return ii - start; + } + + _read_response() { + var state; + var 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); + + var 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); + + // The response is complete. + if (state === this.STATE_RESP) + 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); + 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); + Atomics.notify(this.comm, this.STATE_IDX); + + return response; + } + + _read_from_msg(begin, end) { + return String.fromCharCode.apply(null, this.msg.slice(begin, end)); + } + }, + + create: function (msg_char_len) { + if (msg_char_len === undefined) { + msg_char_len = 1024; // Default size is arbitrary but is in 'char' units (i.e. UTF-16 code points). + } + return new this._impl(msg_char_len); + } +}; diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index e439c173cc61f..92b8a0893e08c 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -185,7 +185,7 @@ $(ArtifactsObjDir)wasm/pinvoke-table.h - -g -Os -s -DDEBUG=1 -DENABLE_AOT_PROFILER=1 + -g4 -Os -s -DDEBUG=1 -DENABLE_AOT_PROFILER=1 -Oz $(CMakeConfigurationEmccFlags) diff --git a/src/native/libs/CMakeLists.txt b/src/native/libs/CMakeLists.txt index a6087be939399..779b301f0686e 100644 --- a/src/native/libs/CMakeLists.txt +++ b/src/native/libs/CMakeLists.txt @@ -149,7 +149,7 @@ if (CLR_CMAKE_TARGET_UNIX OR CLR_CMAKE_TARGET_BROWSER) add_subdirectory(System.Native) if (CLR_CMAKE_TARGET_BROWSER) - # skip for now + add_subdirectory(System.Security.Cryptography.Native.Browser) elseif (CLR_CMAKE_TARGET_MACCATALYST) add_subdirectory(System.Net.Security.Native) # System.Security.Cryptography.Native is intentionally disabled on iOS diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/CMakeLists.txt b/src/native/libs/System.Security.Cryptography.Native.Browser/CMakeLists.txt new file mode 100644 index 0000000000000..a90593f7bda1a --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native.Browser/CMakeLists.txt @@ -0,0 +1,15 @@ +project(System.Security.Cryptography.Native.Browser C) + +set (NATIVE_SOURCES + pal_crypto_webworker.c +) + +add_library (System.Security.Cryptography.Native.Browser-Static + STATIC + ${NATIVE_SOURCES} + ${VERSION_FILE_PATH} +) + +set_target_properties(System.Security.Cryptography.Native.Browser-Static PROPERTIES OUTPUT_NAME System.Security.Cryptography.Native.Browser CLEAN_DIRECT_OUTPUT 1) + +install (TARGETS System.Security.Cryptography.Native.Browser-Static DESTINATION ${STATIC_LIB_DESTINATION}) diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_browser.h b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_browser.h new file mode 100644 index 0000000000000..775fe634536e2 --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_browser.h @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include + +#ifndef __EMSCRIPTEN__ +#error Cryptography Native Browser is designed to be compiled with Emscripten. +#endif // __EMSCRIPTEN__ + +#ifndef PALEXPORT +#ifdef TARGET_UNIX +#define PALEXPORT __attribute__ ((__visibility__ ("default"))) +#else +#define PALEXPORT __declspec(dllexport) +#endif +#endif // PALEXPORT diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c new file mode 100644 index 0000000000000..2dd032de7cd5f --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_browser.h" +#include "pal_crypto_webworker.h" + +// Forward declarations +extern int32_t dotnet_browser_simple_digest_hash( + enum simple_digest ver, + uint8_t* input_buffer, + int32_t input_len, + uint8_t* output_buffer, + int32_t output_len); + +extern int32_t dotnet_browser_can_use_simple_digest_hash(); + +int32_t SystemCryptoNativeBrowser_SimpleDigestHash( + enum simple_digest ver, + uint8_t* input_buffer, + int32_t input_len, + uint8_t* output_buffer, + int32_t output_len) +{ + return dotnet_browser_simple_digest_hash(ver, input_buffer, input_len, output_buffer, output_len); +} + +int32_t SystemCryptoNativeBrowser_CanUseSimpleDigestHash() +{ + return dotnet_browser_can_use_simple_digest_hash(); +} \ No newline at end of file diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h new file mode 100644 index 0000000000000..6615d523d9ea3 --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include + +enum simple_digest +{ + sd_sha_1, + sd_sha_256, + sd_sha_384, + sd_sha_512, +}; + +PALEXPORT int32_t SystemCryptoNativeBrowser_SimpleDigestHash( + enum simple_digest ver, + uint8_t* input_buffer, + int32_t input_len, + uint8_t* output_buffer, + int32_t output_len); + +PALEXPORT int32_t SystemCryptoNativeBrowser_CanUseSimpleDigestHash(); \ No newline at end of file diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js new file mode 100644 index 0000000000000..fa1803117b45f --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +var CryptoWebWorkerLib = { + $CRYPTOWEBWORKER: { + call_digest: function (hash, input_buffer, input_len, output_buffer, output_len) { + if (this.can_call_digest() !== 1) { + return 0; // Not supported. Caller should have validated this first. + } + + var msg = { + func: "digest", + type: hash, + data: Array.from(Module.HEAPU8.subarray (input_buffer, input_buffer + input_len)) + }; + var response = INTERNAL.mono_wasm_crypto.channel.send_msg (JSON.stringify (msg)); + var digest = JSON.parse (response); + if (digest.length > output_len) { + throw "DIGEST HASH: Digest length exceeds output length: " + digest.length + " > " + output_len; + } + + Module.HEAPU8.set (digest, output_buffer); + return 1; + }, + can_call_digest: function () { + if (INTERNAL.mono_wasm_crypto.channel === null || typeof SharedArrayBuffer === "undefined") { + return 0; // Not supported + } + return 1; + } + }, + dotnet_browser_simple_digest_hash: function (hash, input_buffer, input_len, output_buffer, output_len) { + return CRYPTOWEBWORKER.call_digest (hash, input_buffer, input_len, output_buffer, output_len); + }, + dotnet_browser_can_use_simple_digest_hash: function () { + return CRYPTOWEBWORKER.can_call_digest (); + }, +}; + +autoAddDeps(CryptoWebWorkerLib, '$CRYPTOWEBWORKER') +mergeInto(LibraryManager.library, CryptoWebWorkerLib) From 2f99382f2b8f9558ecbfac7daabaea74709e9bae Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Tue, 12 Apr 2022 14:29:19 -0700 Subject: [PATCH 02/19] Add sync over async implementation --- src/mono/wasm/runtime/dotnet-crypto-worker.ts | 175 ++++++++++++++++++ src/mono/wasm/runtime/dotnet.d.ts | 11 ++ src/mono/wasm/runtime/dotnet_crypto_worker.js | 173 ----------------- src/mono/wasm/runtime/exports.ts | 3 +- src/mono/wasm/runtime/library-channel.ts | 153 +++++++++++++++ src/mono/wasm/runtime/library_channel.js | 147 --------------- src/mono/wasm/runtime/run.ts | 3 + src/mono/wasm/runtime/startup.ts | 18 +- src/mono/wasm/runtime/types.ts | 10 +- 9 files changed, 369 insertions(+), 324 deletions(-) create mode 100644 src/mono/wasm/runtime/dotnet-crypto-worker.ts delete mode 100644 src/mono/wasm/runtime/dotnet_crypto_worker.js create mode 100644 src/mono/wasm/runtime/library-channel.ts delete mode 100644 src/mono/wasm/runtime/library_channel.js diff --git a/src/mono/wasm/runtime/dotnet-crypto-worker.ts b/src/mono/wasm/runtime/dotnet-crypto-worker.ts new file mode 100644 index 0000000000000..ed5976343f338 --- /dev/null +++ b/src/mono/wasm/runtime/dotnet-crypto-worker.ts @@ -0,0 +1,175 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +class ChannelWorker { + comm: Int32Array; + msg: Uint16Array; + msg_char_len: number; + + // BEGIN ChannelOwner contract - shared constants. + get STATE_IDX() { return 0; } + get MSG_SIZE_IDX() { return 1; } + + // Communication states. + get STATE_SHUTDOWN() { return -1; } // Shutdown + get STATE_IDLE() { return 0; } + get STATE_REQ() { return 1; } + get STATE_RESP() { return 2; } + 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 + // END ChannelOwner contract - shared constants. + + constructor(comm_buf: number[], msg_buf: number[], msg_char_len: number) { + this.comm = new Int32Array(comm_buf); + this.msg = new Uint16Array(msg_buf); + this.msg_char_len = msg_char_len; + } + + async await_request(async_call: Function) { + 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(); + // console.log("Request: " + req); + 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); + } + + // Send response + this._send_response(resp); + } + } + + _read_request() { + var request = ""; + for (;;) { + // 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); + + // The request is complete. + if (state === this.STATE_REQ) + break; + + // Shutdown the worker. + if (state === this.STATE_SHUTDOWN) + 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); + Atomics.wait(this.comm, this.STATE_IDX, this.STATE_AWAIT); + } + + return request; + } + + _read_from_msg(begin: number, end: number) { + const slicedMessage: number[] = []; + this.msg.slice(begin, end).forEach((value, index) => slicedMessage[index] = value); + return String.fromCharCode.apply(null, slicedMessage); + } + + _send_response(msg: string) { + if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ) + throw "WORKER: Invalid sync communication channel state."; + + var state; // State machine variable + const msg_len = msg.length; + var msg_written = 0; + + for (;;) { + // 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); + + // Indicate if this was the whole message or part of it. + state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P; + + // Update the state + Atomics.store(this.comm, this.STATE_IDX, state); + + // 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); + + // Done sending response. + if (state === this.STATE_RESP) + break; + } + } + + _write_to_msg(input: string, start: number, input_len: number) { + var mi = 0; + var ii = start; + while (mi < this.msg_char_len && ii < input_len) { + this.msg[mi] = input.charCodeAt(ii); + ii++; // Next character + mi++; // Next buffer index + } + return ii - start; + } + + static create(comm_buf: number[], msg_buf: number[], msg_char_len: number) { + return new ChannelWorker(comm_buf, msg_buf, msg_char_len); + } +}; + +async function call_digest(type: number, data: BufferSource) { + var digest_type = ""; + switch(type) { + case 0: digest_type = "SHA-1"; break; + case 1: digest_type = "SHA-256"; break; + case 2: digest_type = "SHA-384"; break; + case 3: digest_type = "SHA-512"; break; + default: + throw "CRYPTO: Unknown digest: " + type; + } + + // The 'crypto' API is not available in non-browser + // environments (for example, v8 server). + var digest = await crypto.subtle.digest(digest_type, data); + return Array.from(new Uint8Array(digest)); +} + +// Operation to perform. +async function async_call(msg: string) { + const req = JSON.parse(msg); + + if (req.func === "digest") { + var digestArr = await call_digest(req.type, new Uint8Array(req.data)); + return JSON.stringify(digestArr); + } else { + throw "CRYPTO: Unknown request: " + req.func; + } +} + +var s_channel; + +// Initialize WebWorker +onmessage = function (p: any) { + var data = p; + if (p.data !== undefined) { + data = p.data; + } + s_channel = ChannelWorker.create(data.comm_buf, data.msg_buf, data.msg_char_len); + s_channel.await_request(async_call); +} diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 0a4a8d48e8e7f..f927dd1f0b4cc 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -204,10 +204,14 @@ declare type CoverageProfilerOptions = { write_at?: string; send_to?: string; }; +declare type LibraryChannel = { + create: (msg_char_len: number) => LibraryChannel; +} declare type DotnetModuleConfig = { disableDotnet6Compatibility?: boolean; config?: MonoConfig | MonoConfigError; configSrc?: string; + channel: LibraryChannel, onConfigLoaded?: (config: MonoConfig) => Promise; onDotnetReady?: () => void; imports?: DotnetModuleConfigImports; @@ -234,6 +238,10 @@ declare type DotnetModuleConfigImports = { }; url?: any; }; +declare type MonoWasmCrypto = { + channel: LibraryChannel; + worker: Worker; +} declare function mono_wasm_runtime_ready(): void; @@ -307,6 +315,8 @@ declare function getF64(offset: _MemOffset): number; declare function mono_run_main_and_exit(main_assembly_name: string, args: string[]): Promise; declare function mono_run_main(main_assembly_name: string, args: string[]): Promise; +declare var mono_wasm_crypto: MonoWasmCrypto; + declare const MONO: { mono_wasm_setenv: typeof mono_wasm_setenv; mono_wasm_load_bytes_into_heap: typeof mono_wasm_load_bytes_into_heap; @@ -323,6 +333,7 @@ declare const MONO: { mono_run_main_and_exit: typeof mono_run_main_and_exit; mono_wasm_add_assembly: (name: string, data: VoidPtr, size: number) => number; mono_wasm_load_runtime: (unused: string, debug_level: number) => void; + mono_wasm_crypto: typeof mono_wasm_crypto; config: MonoConfig | MonoConfigError; loaded_files: string[]; setI8: typeof setI8; diff --git a/src/mono/wasm/runtime/dotnet_crypto_worker.js b/src/mono/wasm/runtime/dotnet_crypto_worker.js deleted file mode 100644 index 5ea07d2bfc09a..0000000000000 --- a/src/mono/wasm/runtime/dotnet_crypto_worker.js +++ /dev/null @@ -1,173 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -var ChannelWorker = { - _impl: class { - // BEGIN ChannelOwner contract - shared constants. - get STATE_IDX() { return 0; } - get MSG_SIZE_IDX() { return 1; } - - // Communication states. - get STATE_SHUTDOWN() { return -1; } // Shutdown - get STATE_IDLE() { return 0; } - get STATE_REQ() { return 1; } - get STATE_RESP() { return 2; } - 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 - // END ChannelOwner contract - shared constants. - - constructor(comm_buf, msg_buf, msg_char_len) { - this.comm = new Int32Array(comm_buf); - this.msg = new Uint16Array(msg_buf); - this.msg_char_len = msg_char_len; - } - - async await_request(async_call) { - // console.log("await_request()"); - - 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(); - // console.log("Request: " + req); - 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); - } - - // Send response - this._send_response(resp); - } - } - - _read_request() { - var request = ""; - for (;;) { - // 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); - - // The request is complete. - if (state === this.STATE_REQ) - break; - - // Shutdown the worker. - if (state === this.STATE_SHUTDOWN) - 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); - Atomics.wait(this.comm, this.STATE_IDX, this.STATE_AWAIT); - } - - return request; - } - - _read_from_msg(begin, end) { - return String.fromCharCode.apply(null, this.msg.slice(begin, end)); - } - - _send_response(msg) { - if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ) - throw "WORKER: Invalid sync communication channel state."; - - var state; // State machine variable - const msg_len = msg.length; - var msg_written = 0; - - for (;;) { - // 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); - - // Indicate if this was the whole message or part of it. - state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P; - - // Update the state - Atomics.store(this.comm, this.STATE_IDX, state); - - // 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); - - // Done sending response. - if (state === this.STATE_RESP) - break; - } - } - - _write_to_msg(input, start, input_len) { - var mi = 0; - var ii = start; - while (mi < this.msg_char_len && ii < input_len) { - this.msg[mi] = input.charCodeAt(ii); - ii++; // Next character - mi++; // Next buffer index - } - return ii - start; - } - }, - - create: function (comm_buf, msg_buf, msg_char_len) { - return new this._impl(comm_buf, msg_buf, msg_char_len); - } -}; - -async function call_digest(type, data) { - var digest_type = ""; - switch(type) { - case 0: digest_type = "SHA-1"; break; - case 1: digest_type = "SHA-256"; break; - case 2: digest_type = "SHA-384"; break; - case 3: digest_type = "SHA-512"; break; - default: - throw "CRYPTO: Unknown digest: " + type; - } - - // The 'crypto' API is not available in non-browser - // environments (for example, v8 server). - var digest = await crypto.subtle.digest(digest_type, data); - return Array.from(new Uint8Array(digest)); -} - -// Operation to perform. -async function async_call(msg) { - const req = JSON.parse(msg); - - if (req.func === "digest") { - var digestArr = await call_digest(req.type, new Uint8Array(req.data)); - return JSON.stringify(digestArr); - } else { - throw "CRYPTO: Unknown request: " + req.func; - } -} - -var s_channel; - -// Initialize WebWorker -onmessage = function (p) { - var data = p; - if (p.data !== undefined) { - data = p.data; - } - s_channel = ChannelWorker.create(data.comm_buf, data.msg_buf, data.msg_char_len); - s_channel.await_request(async_call); -} diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts index ab678899dd8d6..d2c30727a7083 100644 --- a/src/mono/wasm/runtime/exports.ts +++ b/src/mono/wasm/runtime/exports.ts @@ -66,7 +66,7 @@ import { import { create_weak_ref } from "./weak-ref"; import { fetch_like, readAsync_like } from "./polyfills"; import { EmscriptenModule } from "./types/emscripten"; -import { mono_run_main, mono_run_main_and_exit } from "./run"; +import { mono_run_main, mono_run_main_and_exit, mono_wasm_crypto } from "./run"; const MONO = { // current "public" MONO API @@ -83,6 +83,7 @@ const MONO = { mono_wasm_release_roots, mono_run_main, mono_run_main_and_exit, + mono_wasm_crypto, // for Blazor's future! mono_wasm_add_assembly: cwraps.mono_wasm_add_assembly, diff --git a/src/mono/wasm/runtime/library-channel.ts b/src/mono/wasm/runtime/library-channel.ts new file mode 100644 index 0000000000000..ffe3b1ba40409 --- /dev/null +++ b/src/mono/wasm/runtime/library-channel.ts @@ -0,0 +1,153 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +class LibraryChannel { + msg_char_len: number; + comm_buf: SharedArrayBuffer; + msg_buf: SharedArrayBuffer; + comm: Int32Array; + msg: Uint16Array; + + // Index constants for the communication buffer. + get STATE_IDX() { return 0; } + get MSG_SIZE_IDX() { return 1; } + get COMM_LAST_IDX() { return this.MSG_SIZE_IDX; } + + // Communication states. + get STATE_SHUTDOWN() { return -1; } // Shutdown + get STATE_IDLE() { return 0; } + get STATE_REQ() { return 1; } + get STATE_RESP() { return 2; } + 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 + + constructor(msg_char_len: number) { + this.msg_char_len = msg_char_len; + + const int_bytes = 4; + const comm_byte_len = int_bytes * (this.COMM_LAST_IDX + 1); + this.comm_buf = new SharedArrayBuffer(comm_byte_len); + + // JavaScript character encoding is UTF-16. + const char_bytes = 2; + const msg_byte_len = char_bytes * this.msg_char_len; + this.msg_buf = new SharedArrayBuffer(msg_byte_len); + + // Create the local arrays to use. + this.comm = new Int32Array(this.comm_buf); + this.msg = new Uint16Array(this.msg_buf); + } + + get_msg_len() { return this.msg_char_len; } + get_msg_buffer() { return this.msg_buf; } + get_comm_buffer() { return this.comm_buf; } + + send_msg(msg: 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); + } + this._send_request(msg); + return this._read_response(); + } + + shutdown() { + 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); + } + + // Notify webworker + Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); + Atomics.store(this.comm, this.STATE_IDX, this.STATE_SHUTDOWN); + Atomics.notify(this.comm, this.STATE_IDX); + } + + _send_request(msg: string) { + var state; + const msg_len = msg.length; + var msg_written = 0; + + for (;;) { + // 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); + + // 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); + Atomics.notify(this.comm, this.STATE_IDX); + + // The send message is complete. + 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); + } + } + + _write_to_msg(input: string, start: number, input_len: number) { + var mi = 0; + var ii = start; + while (mi < this.msg_char_len && ii < input_len) { + this.msg[mi] = input.charCodeAt(ii); + ii++; // Next character + mi++; // Next buffer index + } + return ii - start; + } + + _read_response() { + var state; + var 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); + + var 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); + + // The response is complete. + if (state === this.STATE_RESP) + 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); + 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); + Atomics.notify(this.comm, this.STATE_IDX); + + return response; + } + + _read_from_msg(begin: number, end: number) { + const slicedMessage: number[] = []; + this.msg.slice(begin, end).forEach((value, index) => slicedMessage[index] = value); + return String.fromCharCode.apply(null, slicedMessage); + } + + static create(msg_char_len: number) { + if (msg_char_len === undefined) { + msg_char_len = 1024; // Default size is arbitrary but is in 'char' units (i.e. UTF-16 code points). + } + return new LibraryChannel(msg_char_len); + } +}; diff --git a/src/mono/wasm/runtime/library_channel.js b/src/mono/wasm/runtime/library_channel.js deleted file mode 100644 index a2cf2b8cdb406..0000000000000 --- a/src/mono/wasm/runtime/library_channel.js +++ /dev/null @@ -1,147 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -Module [ 'channel' ] = { - _impl: class { - // Index constants for the communication buffer. - get STATE_IDX() { return 0; } - get MSG_SIZE_IDX() { return 1; } - get COMM_LAST_IDX() { return this.MSG_SIZE_IDX; } - - // Communication states. - get STATE_SHUTDOWN() { return -1; } // Shutdown - get STATE_IDLE() { return 0; } - get STATE_REQ() { return 1; } - get STATE_RESP() { return 2; } - 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 - - constructor(msg_char_len) { - this.msg_char_len = msg_char_len; - - const int_bytes = 4; - const comm_byte_len = int_bytes * (this.COMM_LAST_IDX + 1); - this.comm_buf = new SharedArrayBuffer(comm_byte_len); - - // JavaScript character encoding is UTF-16. - const char_bytes = 2; - const msg_byte_len = char_bytes * this.msg_char_len; - this.msg_buf = new SharedArrayBuffer(msg_byte_len); - - // Create the local arrays to use. - this.comm = new Int32Array(this.comm_buf); - this.msg = new Uint16Array(this.msg_buf); - } - - get_msg_len() { return this.msg_char_len; } - get_msg_buffer() { return this.msg_buf; } - get_comm_buffer() { return this.comm_buf; } - - send_msg(msg) { - 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); - } - this._send_request(msg); - return this._read_response(); - } - - shutdown() { - 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); - } - - // Notify webworker - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - Atomics.store(this.comm, this.STATE_IDX, this.STATE_SHUTDOWN); - Atomics.notify(this.comm, this.STATE_IDX); - } - - _send_request(msg) { - var state; - const msg_len = msg.length; - var msg_written = 0; - - for (;;) { - // 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); - - // 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); - Atomics.notify(this.comm, this.STATE_IDX); - - // The send message is complete. - 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); - } - } - - _write_to_msg(input, start, input_len) { - var mi = 0; - var ii = start; - while (mi < this.msg_char_len && ii < input_len) { - this.msg[mi] = input.charCodeAt(ii); - ii++; // Next character - mi++; // Next buffer index - } - return ii - start; - } - - _read_response() { - var state; - var 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); - - var 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); - - // The response is complete. - if (state === this.STATE_RESP) - 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); - 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); - Atomics.notify(this.comm, this.STATE_IDX); - - return response; - } - - _read_from_msg(begin, end) { - return String.fromCharCode.apply(null, this.msg.slice(begin, end)); - } - }, - - create: function (msg_char_len) { - if (msg_char_len === undefined) { - msg_char_len = 1024; // Default size is arbitrary but is in 'char' units (i.e. UTF-16 code points). - } - return new this._impl(msg_char_len); - } -}; diff --git a/src/mono/wasm/runtime/run.ts b/src/mono/wasm/runtime/run.ts index 492188352e915..3e5aed5484668 100644 --- a/src/mono/wasm/runtime/run.ts +++ b/src/mono/wasm/runtime/run.ts @@ -1,6 +1,7 @@ import { ExitStatus, INTERNAL, Module, quit } from "./imports"; import { mono_call_assembly_entry_point } from "./method-calls"; import { mono_wasm_set_main_args, runtime_is_initialized_reject } from "./startup"; +import { MonoWasmCrypto } from "./types"; export async function mono_run_main_and_exit(main_assembly_name: string, args: string[]): Promise { try { @@ -39,3 +40,5 @@ function set_exit_code(exit_code: number, reason?: any) { } quit(exit_code, reason); } + +export var mono_wasm_crypto: MonoWasmCrypto; \ No newline at end of file diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index a8f3b7ce85b95..3ba505665b26c 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { AllAssetEntryTypes, assert, AssetEntry, CharPtrNull, DotnetModule, GlobalizationMode, MonoConfig, MonoConfigError, wasm_type_symbol, MonoObject } from "./types"; -import { ENVIRONMENT_IS_ESM, ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, INTERNAL, locateFile, Module, MONO, requirePromise, runtimeHelpers } from "./imports"; +import { ENVIRONMENT_IS_ESM, ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, INTERNAL, locateFile, Module, MONO, requirePromise, runtimeHelpers } from "./imports"; import cwraps from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; import { mono_wasm_globalization_init, mono_wasm_load_icu_data } from "./icu"; @@ -302,7 +302,7 @@ function finalize_startup(config: MonoConfig | MonoConfigError | undefined): voi const moduleExt = Module as DotnetModule; - if(!Module.disableDotnet6Compatibility && Module.exports){ + if (!Module.disableDotnet6Compatibility && Module.exports){ // Export emscripten defined in module through EXPORTED_RUNTIME_METHODS // Useful to export IDBFS or other similar types generally exposed as // global types when emscripten is not modularized. @@ -375,6 +375,20 @@ function finalize_startup(config: MonoConfig | MonoConfigError | undefined): voi } } + console.debug ("MONO_WASM: Initialize WebWorkers"); + + if (ENVIRONMENT_IS_WEB) { + const chan = Module.channel.create(1024); + const worker = new Worker("dotnet-crypto-worker.ts"); + worker.postMessage({ + comm_buff: chan.get_comm_buffer(), + msg_buf: chan.get_msg_buffer(), + msg_char_len: chan.get_msg_len() + }); + MONO.mono_wasm_crypto.channel = chan; + MONO.mono_wasm_crypto = worker; + } + runtime_is_initialized_resolve(); } catch (err: any) { Module.printErr("MONO_WASM: Error in finalize_startup: " + err); diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 2b85e89953816..1a19376a5a132 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -181,6 +181,9 @@ export type DotnetModuleConfig = { config?: MonoConfig | MonoConfigError, configSrc?: string, + + channel: LibraryChannel, + onConfigLoaded?: (config: MonoConfig) => Promise; onDotnetReady?: () => void; @@ -260,4 +263,9 @@ export const enum MarshalError { NULL_TYPE_POINTER = 514, UNSUPPORTED_TYPE = 515, FIRST = BUFFER_TOO_SMALL -} \ No newline at end of file +} + +export type MonoWasmCrypto = { + channel: LibraryChannel, + worker: Worker, +} From f7e68f949a0604d8de705b5aa69f6459bfb7bdc8 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Wed, 13 Apr 2022 10:23:53 -0700 Subject: [PATCH 03/19] Address misc feedback and make fixes --- eng/SignCheckExclusionsFile.txt | 3 +- eng/liveBuilds.targets | 1 - .../Directory.Build.props | 2 - .../Interop.SimpleDigestHash.cs | 8 +-- .../HashProviderDispenser.Browser.cs | 2 +- src/mono/wasm/runtime/dotnet-crypto-worker.ts | 40 ++++++------- src/mono/wasm/runtime/dotnet.d.ts | 41 +++++++++---- src/mono/wasm/runtime/exports.ts | 6 +- src/mono/wasm/runtime/library-channel.ts | 58 +++++++++---------- src/mono/wasm/runtime/run.ts | 2 +- src/mono/wasm/runtime/startup.ts | 3 +- src/mono/wasm/runtime/types.ts | 1 + src/mono/wasm/wasm.proj | 6 +- .../CMakeLists.txt | 1 - .../pal_crypto_webworker.c | 6 +- .../pal_crypto_webworker.h | 2 +- 16 files changed, 98 insertions(+), 84 deletions(-) diff --git a/eng/SignCheckExclusionsFile.txt b/eng/SignCheckExclusionsFile.txt index da5efe7fe1361..f41753338155e 100644 --- a/eng/SignCheckExclusionsFile.txt +++ b/eng/SignCheckExclusionsFile.txt @@ -12,5 +12,4 @@ *apphosttemplateapphostexe.exe;;Template, DO-NOT-SIGN, https://github.com/dotnet/core-setup/pull/7549 *comhosttemplatecomhostdll.dll;;Template, DO-NOT-SIGN, https://github.com/dotnet/core-setup/pull/7549 *staticapphosttemplateapphostexe.exe;;Template, DO-NOT-SIGN, https://github.com/dotnet/core-setup/pull/7549 -*dotnet.js;;Workaround, https://github.com/dotnet/core-eng/issues/9933 -*dotnet_crypto_worker.js;;Workaround, https://github.com/dotnet/core-eng/issues/9933 \ No newline at end of file +*dotnet.js;;Workaround, https://github.com/dotnet/core-eng/issues/9933 \ No newline at end of file diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index 80d7214c4a461..77e9a90b97d30 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -185,7 +185,6 @@ $(LibrariesNativeArtifactsPath)dotnet.wasm; $(LibrariesNativeArtifactsPath)dotnet.js.symbols; $(LibrariesNativeArtifactsPath)dotnet.timezones.blat; - $(LibrariesNativeArtifactsPath)dotnet_crypto_worker.js; $(LibrariesNativeArtifactsPath)*.dat;" IsNative="true" /> - - diff --git a/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs b/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs index a2455b07817c1..ed72c7328eda8 100644 --- a/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs +++ b/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs @@ -17,11 +17,11 @@ internal enum SimpleDigest Sha512, }; - [DllImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_CanUseSimpleDigestHash")] - internal static extern unsafe bool CanUseSimpleDigestHash(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_CanUseSimpleDigestHash")] + internal static partial int CanUseSimpleDigestHash(); - [DllImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_SimpleDigestHash")] - internal static extern unsafe int SimpleDigestHash( + [LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_SimpleDigestHash")] + internal static unsafe partial int SimpleDigestHash( SimpleDigest hash, byte* input_buffer, int input_len, diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs index 0170afc26f006..031d28ffea2ef 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs @@ -7,7 +7,7 @@ namespace System.Security.Cryptography { internal static partial class HashProviderDispenser { - internal static readonly bool CanUseSubtleCryptoImpl = Interop.BrowserCrypto.CanUseSimpleDigestHash(); + internal static readonly bool CanUseSubtleCryptoImpl = Interop.BrowserCrypto.CanUseSimpleDigestHash() == 1; public static HashProvider CreateHashProvider(string hashAlgorithmId) { diff --git a/src/mono/wasm/runtime/dotnet-crypto-worker.ts b/src/mono/wasm/runtime/dotnet-crypto-worker.ts index ed5976343f338..dfd935c7a967d 100644 --- a/src/mono/wasm/runtime/dotnet-crypto-worker.ts +++ b/src/mono/wasm/runtime/dotnet-crypto-worker.ts @@ -32,18 +32,18 @@ class ChannelWorker { Atomics.wait(this.comm, this.STATE_IDX, this.STATE_IDLE); // Read in request - var req = this._read_request(); - // console.log("Request: " + req); - if (req === this.STATE_SHUTDOWN) + const req = this._read_request(); + if (req === this.STATE_SHUTDOWN) { break; + } - var resp = null; + let resp = null; try { // Perform async action based on request resp = await async_call(req); } catch (err) { - console.log("Request error: " + err); + console.error("Request error: " + err); resp = JSON.stringify(err); } @@ -53,11 +53,11 @@ class ChannelWorker { } _read_request() { - var request = ""; + let request = ""; for (;;) { // 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); + const state = Atomics.load(this.comm, this.STATE_IDX); + const 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); @@ -89,13 +89,13 @@ class ChannelWorker { if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ) throw "WORKER: Invalid sync communication channel state."; - var state; // State machine variable + let state; // State machine variable const msg_len = msg.length; - var msg_written = 0; + let msg_written = 0; for (;;) { // Write the message and return how much was written. - var wrote = this._write_to_msg(msg, msg_written, msg_len); + const wrote = this._write_to_msg(msg, msg_written, msg_len); msg_written += wrote; // Indicate how much was written to the this.msg buffer. @@ -118,8 +118,8 @@ class ChannelWorker { } _write_to_msg(input: string, start: number, input_len: number) { - var mi = 0; - var ii = start; + let mi = 0; + let ii = start; while (mi < this.msg_char_len && ii < input_len) { this.msg[mi] = input.charCodeAt(ii); ii++; // Next character @@ -131,10 +131,10 @@ class ChannelWorker { static create(comm_buf: number[], msg_buf: number[], msg_char_len: number) { return new ChannelWorker(comm_buf, msg_buf, msg_char_len); } -}; +} async function call_digest(type: number, data: BufferSource) { - var digest_type = ""; + let digest_type = ""; switch(type) { case 0: digest_type = "SHA-1"; break; case 1: digest_type = "SHA-256"; break; @@ -146,7 +146,7 @@ async function call_digest(type: number, data: BufferSource) { // The 'crypto' API is not available in non-browser // environments (for example, v8 server). - var digest = await crypto.subtle.digest(digest_type, data); + const digest = await crypto.subtle.digest(digest_type, data); return Array.from(new Uint8Array(digest)); } @@ -155,21 +155,21 @@ async function async_call(msg: string) { const req = JSON.parse(msg); if (req.func === "digest") { - var digestArr = await call_digest(req.type, new Uint8Array(req.data)); + const digestArr = await call_digest(req.type, new Uint8Array(req.data)); return JSON.stringify(digestArr); } else { throw "CRYPTO: Unknown request: " + req.func; } } -var s_channel; +let s_channel: ChannelWorker; // Initialize WebWorker onmessage = function (p: any) { - var data = p; + let data = p; if (p.data !== undefined) { data = p.data; } s_channel = ChannelWorker.create(data.comm_buf, data.msg_buf, data.msg_char_len); s_channel.await_request(async_call); -} +}; diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index f927dd1f0b4cc..2c1ed0c460c40 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -125,6 +125,35 @@ interface WasmRoot { toString(): string; } +declare class LibraryChannel { + msg_char_len: number; + comm_buf: SharedArrayBuffer; + msg_buf: SharedArrayBuffer; + comm: Int32Array; + msg: Uint16Array; + get STATE_IDX(): number; + get MSG_SIZE_IDX(): number; + get COMM_LAST_IDX(): number; + get STATE_SHUTDOWN(): number; + get STATE_IDLE(): number; + get STATE_REQ(): number; + get STATE_RESP(): number; + get STATE_REQ_P(): number; + get STATE_RESP_P(): number; + get STATE_AWAIT(): number; + constructor(msg_char_len: number); + get_msg_len(): number; + get_msg_buffer(): SharedArrayBuffer; + get_comm_buffer(): SharedArrayBuffer; + send_msg(msg: string): string; + shutdown(): void; + _send_request(msg: string): void; + _write_to_msg(input: string, start: number, input_len: number): number; + _read_response(): string; + _read_from_msg(begin: number, end: number): string; + create(msg_char_len: number): LibraryChannel; +} + interface MonoObject extends ManagedPointer { __brandMonoObject: "MonoObject"; } @@ -204,14 +233,11 @@ declare type CoverageProfilerOptions = { write_at?: string; send_to?: string; }; -declare type LibraryChannel = { - create: (msg_char_len: number) => LibraryChannel; -} declare type DotnetModuleConfig = { disableDotnet6Compatibility?: boolean; config?: MonoConfig | MonoConfigError; configSrc?: string; - channel: LibraryChannel, + channel: LibraryChannel; onConfigLoaded?: (config: MonoConfig) => Promise; onDotnetReady?: () => void; imports?: DotnetModuleConfigImports; @@ -238,10 +264,6 @@ declare type DotnetModuleConfigImports = { }; url?: any; }; -declare type MonoWasmCrypto = { - channel: LibraryChannel; - worker: Worker; -} declare function mono_wasm_runtime_ready(): void; @@ -315,8 +337,6 @@ declare function getF64(offset: _MemOffset): number; declare function mono_run_main_and_exit(main_assembly_name: string, args: string[]): Promise; declare function mono_run_main(main_assembly_name: string, args: string[]): Promise; -declare var mono_wasm_crypto: MonoWasmCrypto; - declare const MONO: { mono_wasm_setenv: typeof mono_wasm_setenv; mono_wasm_load_bytes_into_heap: typeof mono_wasm_load_bytes_into_heap; @@ -333,7 +353,6 @@ declare const MONO: { mono_run_main_and_exit: typeof mono_run_main_and_exit; mono_wasm_add_assembly: (name: string, data: VoidPtr, size: number) => number; mono_wasm_load_runtime: (unused: string, debug_level: number) => void; - mono_wasm_crypto: typeof mono_wasm_crypto; config: MonoConfig | MonoConfigError; loaded_files: string[]; setI8: typeof setI8; diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts index d2c30727a7083..ece7a0bcde716 100644 --- a/src/mono/wasm/runtime/exports.ts +++ b/src/mono/wasm/runtime/exports.ts @@ -83,7 +83,6 @@ const MONO = { mono_wasm_release_roots, mono_run_main, mono_run_main_and_exit, - mono_wasm_crypto, // for Blazor's future! mono_wasm_add_assembly: cwraps.mono_wasm_add_assembly, @@ -391,10 +390,7 @@ const INTERNAL: any = { mono_wasm_runtime_is_ready: runtimeHelpers.mono_wasm_runtime_is_ready, // used for browser crypto - mono_wasm_crypto: { - channel: null, - worker: null - }, + mono_wasm_crypto, }; diff --git a/src/mono/wasm/runtime/library-channel.ts b/src/mono/wasm/runtime/library-channel.ts index ffe3b1ba40409..e99a824d6c170 100644 --- a/src/mono/wasm/runtime/library-channel.ts +++ b/src/mono/wasm/runtime/library-channel.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -class LibraryChannel { +export class LibraryChannel { msg_char_len: number; comm_buf: SharedArrayBuffer; msg_buf: SharedArrayBuffer; @@ -9,18 +9,18 @@ class LibraryChannel { msg: Uint16Array; // Index constants for the communication buffer. - get STATE_IDX() { return 0; } - get MSG_SIZE_IDX() { return 1; } - get COMM_LAST_IDX() { return this.MSG_SIZE_IDX; } + get STATE_IDX(): number { return 0; } + get MSG_SIZE_IDX(): number { return 1; } + get COMM_LAST_IDX(): number { return this.MSG_SIZE_IDX; } // Communication states. - get STATE_SHUTDOWN() { return -1; } // Shutdown - get STATE_IDLE() { return 0; } - get STATE_REQ() { return 1; } - get STATE_RESP() { return 2; } - 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_SHUTDOWN(): number { return -1; } // Shutdown + get STATE_IDLE(): number { return 0; } + get STATE_REQ(): number { return 1; } + get STATE_RESP(): number { return 2; } + get STATE_REQ_P(): number { return 3; } // Request has multiple parts + get STATE_RESP_P(): number { return 4; } // Response has multiple parts + get STATE_AWAIT(): number { return 5; } // Awaiting the next part constructor(msg_char_len: number) { this.msg_char_len = msg_char_len; @@ -39,11 +39,11 @@ class LibraryChannel { this.msg = new Uint16Array(this.msg_buf); } - get_msg_len() { return this.msg_char_len; } - get_msg_buffer() { return this.msg_buf; } - get_comm_buffer() { return this.comm_buf; } + get_msg_len(): number { return this.msg_char_len; } + get_msg_buffer(): SharedArrayBuffer { return this.msg_buf; } + get_comm_buffer(): SharedArrayBuffer { return this.comm_buf; } - send_msg(msg: string) { + 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); } @@ -51,7 +51,7 @@ class LibraryChannel { return this._read_response(); } - shutdown() { + 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); } @@ -62,14 +62,14 @@ class LibraryChannel { Atomics.notify(this.comm, this.STATE_IDX); } - _send_request(msg: string) { - var state; + _send_request(msg: string): void { + let state; const msg_len = msg.length; - var msg_written = 0; + let msg_written = 0; for (;;) { // Write the message and return how much was written. - var wrote = this._write_to_msg(msg, msg_written, msg_len); + const wrote = this._write_to_msg(msg, msg_written, msg_len); msg_written += wrote; // Indicate how much was written to the this.msg buffer. @@ -94,9 +94,9 @@ class LibraryChannel { } } - _write_to_msg(input: string, start: number, input_len: number) { - var mi = 0; - var ii = start; + _write_to_msg(input: string, start: number, input_len: number): number { + let mi = 0; + let ii = start; while (mi < this.msg_char_len && ii < input_len) { this.msg[mi] = input.charCodeAt(ii); ii++; // Next character @@ -105,9 +105,9 @@ class LibraryChannel { return ii - start; } - _read_response() { - var state; - var response = ""; + _read_response(): string { + let state; + let response = ""; for (;;) { // Wait for webworker response. // - Atomics.wait() is not permissible on the main thread. @@ -115,7 +115,7 @@ class LibraryChannel { state = Atomics.load(this.comm, this.STATE_IDX); } while (state !== this.STATE_RESP && state !== this.STATE_RESP_P); - var size_to_read = Atomics.load(this.comm, this.MSG_SIZE_IDX); + 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); @@ -138,7 +138,7 @@ class LibraryChannel { return response; } - _read_from_msg(begin: number, end: number) { + _read_from_msg(begin: number, end: number): string { const slicedMessage: number[] = []; this.msg.slice(begin, end).forEach((value, index) => slicedMessage[index] = value); return String.fromCharCode.apply(null, slicedMessage); @@ -150,4 +150,4 @@ class LibraryChannel { } return new LibraryChannel(msg_char_len); } -}; +} diff --git a/src/mono/wasm/runtime/run.ts b/src/mono/wasm/runtime/run.ts index 3e5aed5484668..6fbe20a09291c 100644 --- a/src/mono/wasm/runtime/run.ts +++ b/src/mono/wasm/runtime/run.ts @@ -41,4 +41,4 @@ function set_exit_code(exit_code: number, reason?: any) { quit(exit_code, reason); } -export var mono_wasm_crypto: MonoWasmCrypto; \ No newline at end of file +export let mono_wasm_crypto: MonoWasmCrypto; diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 3ba505665b26c..d5c21458e7195 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -15,6 +15,7 @@ import { VoidPtr, CharPtr } from "./types/emscripten"; import { DotnetPublicAPI } from "./exports"; import { mono_on_abort } from "./run"; import { mono_wasm_new_root } from "./roots"; +import { LibraryChannel } from "./library-channel"; export let runtime_is_initialized_resolve: Function; export let runtime_is_initialized_reject: Function; @@ -378,7 +379,7 @@ function finalize_startup(config: MonoConfig | MonoConfigError | undefined): voi console.debug ("MONO_WASM: Initialize WebWorkers"); if (ENVIRONMENT_IS_WEB) { - const chan = Module.channel.create(1024); + const chan = LibraryChannel.create(1024); const worker = new Worker("dotnet-crypto-worker.ts"); worker.postMessage({ comm_buff: chan.get_comm_buffer(), diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 1a19376a5a132..29e8fa042d947 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -3,6 +3,7 @@ import { bind_runtime_method } from "./method-binding"; import { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr } from "./types/emscripten"; +import { LibraryChannel } from "./library-channel"; export type GCHandle = { __brand: "GCHandle" diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 92b8a0893e08c..70bb841717fa7 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -24,6 +24,7 @@ <_EmccCompileRspPath>$(NativeBinDir)src\emcc-compile.rsp <_EmccLinkRspPath>$(NativeBinDir)src\emcc-link.rsp false + $(RepoRoot)\src\native\libs\System.Security.Cryptography.Native.Browser @@ -185,7 +186,7 @@ $(ArtifactsObjDir)wasm/pinvoke-table.h - -g4 -Os -s -DDEBUG=1 -DENABLE_AOT_PROFILER=1 + -g -Os -s -DDEBUG=1 -DENABLE_AOT_PROFILER=1 -Oz $(CMakeConfigurationEmccFlags) @@ -224,7 +225,8 @@ diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/CMakeLists.txt b/src/native/libs/System.Security.Cryptography.Native.Browser/CMakeLists.txt index a90593f7bda1a..c411aa9ee9cd6 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Browser/CMakeLists.txt +++ b/src/native/libs/System.Security.Cryptography.Native.Browser/CMakeLists.txt @@ -7,7 +7,6 @@ set (NATIVE_SOURCES add_library (System.Security.Cryptography.Native.Browser-Static STATIC ${NATIVE_SOURCES} - ${VERSION_FILE_PATH} ) set_target_properties(System.Security.Cryptography.Native.Browser-Static PROPERTIES OUTPUT_NAME System.Security.Cryptography.Native.Browser CLEAN_DIRECT_OUTPUT 1) diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c index 2dd032de7cd5f..5f4da5a98627a 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c +++ b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c @@ -12,7 +12,7 @@ extern int32_t dotnet_browser_simple_digest_hash( uint8_t* output_buffer, int32_t output_len); -extern int32_t dotnet_browser_can_use_simple_digest_hash(); +extern int32_t dotnet_browser_can_use_simple_digest_hash(void); int32_t SystemCryptoNativeBrowser_SimpleDigestHash( enum simple_digest ver, @@ -24,7 +24,7 @@ int32_t SystemCryptoNativeBrowser_SimpleDigestHash( return dotnet_browser_simple_digest_hash(ver, input_buffer, input_len, output_buffer, output_len); } -int32_t SystemCryptoNativeBrowser_CanUseSimpleDigestHash() +int32_t SystemCryptoNativeBrowser_CanUseSimpleDigestHash(void) { return dotnet_browser_can_use_simple_digest_hash(); -} \ No newline at end of file +} diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h index 6615d523d9ea3..d2687d4b63021 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h +++ b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h @@ -20,4 +20,4 @@ PALEXPORT int32_t SystemCryptoNativeBrowser_SimpleDigestHash( uint8_t* output_buffer, int32_t output_len); -PALEXPORT int32_t SystemCryptoNativeBrowser_CanUseSimpleDigestHash(); \ No newline at end of file +PALEXPORT int32_t SystemCryptoNativeBrowser_CanUseSimpleDigestHash(void); From f1653c5d05bbd9b73cb1defa43bbd20da0ae26e0 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Thu, 21 Apr 2022 14:28:51 -0700 Subject: [PATCH 04/19] Address pinvoke errors --- .../tests/InvalidUsageTests.cs | 1 + src/mono/wasm/runtime/CMakeLists.txt | 7 ++++--- src/mono/wasm/wasm.proj | 1 + .../pal_crypto_webworker.js | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Security.Cryptography/tests/InvalidUsageTests.cs b/src/libraries/System.Security.Cryptography/tests/InvalidUsageTests.cs index 4114d97167e95..56467efed3e8c 100644 --- a/src/libraries/System.Security.Cryptography/tests/InvalidUsageTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/InvalidUsageTests.cs @@ -26,6 +26,7 @@ public void InvalidHashCoreArgumentsFromDerivedType() [Fact] public void InvalidHashCoreArgumentsFromStream() { + //Diagnostics.Debugger.Launch(); using (SHA1 sha1 = SHA1.Create()) { AssertExtensions.Throws(null, () => sha1.ComputeHash(new BadReadStream(BadReadStream.ErrorCondition.TooLargeValueFromRead))); diff --git a/src/mono/wasm/runtime/CMakeLists.txt b/src/mono/wasm/runtime/CMakeLists.txt index dac8d63e719d3..647cc108eef69 100644 --- a/src/mono/wasm/runtime/CMakeLists.txt +++ b/src/mono/wasm/runtime/CMakeLists.txt @@ -26,11 +26,12 @@ target_link_libraries(dotnet ${MONO_ARTIFACTS_DIR}/libmono-wasm-eh-js.a ${MONO_ARTIFACTS_DIR}/libmono-profiler-aot.a ${NATIVE_BIN_DIR}/libSystem.Native.a - ${NATIVE_BIN_DIR}/libSystem.IO.Compression.Native.a) + ${NATIVE_BIN_DIR}/libSystem.IO.Compression.Native.a + ${NATIVE_BIN_DIR}/libSystem.Security.Cryptography.Native.Browser.a) set_target_properties(dotnet PROPERTIES - LINK_DEPENDS "${NATIVE_BIN_DIR}/src/emcc-default.rsp;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.pre.js;${NATIVE_BIN_DIR}/src/cjs/runtime.cjs.iffe.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.lib.js;${NATIVE_BIN_DIR}/src/pal_random.lib.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.post.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.extpost.js;" - LINK_FLAGS "@${NATIVE_BIN_DIR}/src/emcc-default.rsp @${NATIVE_BIN_DIR}/src/emcc-link.rsp ${CONFIGURATION_LINK_FLAGS} --extern-pre-js ${NATIVE_BIN_DIR}/src/cjs/runtime.cjs.iffe.js --pre-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.pre.js --js-library ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.lib.js --js-library ${NATIVE_BIN_DIR}/src/pal_random.lib.js --post-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.post.js --extern-post-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.extpost.js " + LINK_DEPENDS "${NATIVE_BIN_DIR}/src/emcc-default.rsp;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.pre.js;${NATIVE_BIN_DIR}/src/cjs/runtime.cjs.iffe.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.lib.js;${NATIVE_BIN_DIR}/src/pal_random.lib.js;${NATIVE_BIN_DIR}/src/pal_crypto_webworker.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.post.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.extpost.js;" + LINK_FLAGS "@${NATIVE_BIN_DIR}/src/emcc-default.rsp @${NATIVE_BIN_DIR}/src/emcc-link.rsp ${CONFIGURATION_LINK_FLAGS} --extern-pre-js ${NATIVE_BIN_DIR}/src/cjs/runtime.cjs.iffe.js --pre-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.pre.js --js-library ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.lib.js --js-library ${NATIVE_BIN_DIR}/src/pal_random.lib.js --js-library ${NATIVE_BIN_DIR}/src/pal_crypto_webworker.js --post-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.post.js --extern-post-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.extpost.js " RUNTIME_OUTPUT_DIRECTORY "${NATIVE_BIN_DIR}") if(CMAKE_BUILD_TYPE STREQUAL "Release") diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 70bb841717fa7..f3ecdd4dd1517 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -48,6 +48,7 @@ + diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js index fa1803117b45f..0fa1f3eacea38 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js +++ b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -var CryptoWebWorkerLib = { +const CryptoWebWorkerLib = { $CRYPTOWEBWORKER: { call_digest: function (hash, input_buffer, input_len, output_buffer, output_len) { if (this.can_call_digest() !== 1) { From d1da46af80b4c59720dacdbd629474dbf81bfeea Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Thu, 21 Apr 2022 17:05:27 -0700 Subject: [PATCH 05/19] [Attempt] Correct execution of native digest API call at wasm layer --- .../System.Security.Cryptography.Tests.csproj | 1 + src/mono/wasm/runtime/dotnet.d.ts | 30 ----------------- src/mono/wasm/runtime/library-channel.ts | 2 +- src/mono/wasm/runtime/startup.ts | 32 +++++++++++-------- src/mono/wasm/runtime/types.ts | 2 -- .../pal_crypto_webworker.js | 7 ++-- 6 files changed, 25 insertions(+), 49 deletions(-) diff --git a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj index 6fa63793c4d82..8ed095410d4d5 100644 --- a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj +++ b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj @@ -9,6 +9,7 @@ $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) + $(WasmXHarnessArgs) --web-server-use-cop true diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 2c1ed0c460c40..0a4a8d48e8e7f 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -125,35 +125,6 @@ interface WasmRoot { toString(): string; } -declare class LibraryChannel { - msg_char_len: number; - comm_buf: SharedArrayBuffer; - msg_buf: SharedArrayBuffer; - comm: Int32Array; - msg: Uint16Array; - get STATE_IDX(): number; - get MSG_SIZE_IDX(): number; - get COMM_LAST_IDX(): number; - get STATE_SHUTDOWN(): number; - get STATE_IDLE(): number; - get STATE_REQ(): number; - get STATE_RESP(): number; - get STATE_REQ_P(): number; - get STATE_RESP_P(): number; - get STATE_AWAIT(): number; - constructor(msg_char_len: number); - get_msg_len(): number; - get_msg_buffer(): SharedArrayBuffer; - get_comm_buffer(): SharedArrayBuffer; - send_msg(msg: string): string; - shutdown(): void; - _send_request(msg: string): void; - _write_to_msg(input: string, start: number, input_len: number): number; - _read_response(): string; - _read_from_msg(begin: number, end: number): string; - create(msg_char_len: number): LibraryChannel; -} - interface MonoObject extends ManagedPointer { __brandMonoObject: "MonoObject"; } @@ -237,7 +208,6 @@ declare type DotnetModuleConfig = { disableDotnet6Compatibility?: boolean; config?: MonoConfig | MonoConfigError; configSrc?: string; - channel: LibraryChannel; onConfigLoaded?: (config: MonoConfig) => Promise; onDotnetReady?: () => void; imports?: DotnetModuleConfigImports; diff --git a/src/mono/wasm/runtime/library-channel.ts b/src/mono/wasm/runtime/library-channel.ts index e99a824d6c170..0cee255c9e779 100644 --- a/src/mono/wasm/runtime/library-channel.ts +++ b/src/mono/wasm/runtime/library-channel.ts @@ -144,7 +144,7 @@ export class LibraryChannel { return String.fromCharCode.apply(null, slicedMessage); } - static create(msg_char_len: number) { + 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/startup.ts b/src/mono/wasm/runtime/startup.ts index d5c21458e7195..6075c32eef08c 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -363,6 +363,24 @@ function finalize_startup(config: MonoConfig | MonoConfigError | undefined): voi } } + console.debug ("MONO_WASM: Initialize WebWorkers"); + + if (ENVIRONMENT_IS_WEB && typeof SharedArrayBuffer !== undefined) { + const chan = LibraryChannel.create(1024); + const worker = new Worker("dotnet-crypto-worker.ts"); + const globalThisAny = globalThis as any; + globalThisAny.mono_wasm_crypto = { + channel: chan, + worker: worker, + }; + console.log("Initialized worker:", JSON.stringify(globalThisAny.mono_wasm_crypto)); + worker.postMessage({ + comm_buff: chan.get_comm_buffer(), + msg_buf: chan.get_msg_buffer(), + msg_char_len: chan.get_msg_len() + }); + } + if (moduleExt.onDotnetReady) { try { moduleExt.onDotnetReady(); @@ -376,20 +394,6 @@ function finalize_startup(config: MonoConfig | MonoConfigError | undefined): voi } } - console.debug ("MONO_WASM: Initialize WebWorkers"); - - if (ENVIRONMENT_IS_WEB) { - const chan = LibraryChannel.create(1024); - const worker = new Worker("dotnet-crypto-worker.ts"); - worker.postMessage({ - comm_buff: chan.get_comm_buffer(), - msg_buf: chan.get_msg_buffer(), - msg_char_len: chan.get_msg_len() - }); - MONO.mono_wasm_crypto.channel = chan; - MONO.mono_wasm_crypto = worker; - } - runtime_is_initialized_resolve(); } catch (err: any) { Module.printErr("MONO_WASM: Error in finalize_startup: " + err); diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 29e8fa042d947..b5b9d0192bb0f 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -183,8 +183,6 @@ export type DotnetModuleConfig = { config?: MonoConfig | MonoConfigError, configSrc?: string, - channel: LibraryChannel, - onConfigLoaded?: (config: MonoConfig) => Promise; onDotnetReady?: () => void; diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js index 0fa1f3eacea38..e580c2e0f8e50 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js +++ b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js @@ -13,9 +13,10 @@ const CryptoWebWorkerLib = { type: hash, data: Array.from(Module.HEAPU8.subarray (input_buffer, input_buffer + input_len)) }; - var response = INTERNAL.mono_wasm_crypto.channel.send_msg (JSON.stringify (msg)); + var response = globalThis.mono_wasm_crypto.channel.send_msg (JSON.stringify (msg)); var 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; } @@ -23,7 +24,9 @@ const CryptoWebWorkerLib = { return 1; }, can_call_digest: function () { - if (INTERNAL.mono_wasm_crypto.channel === null || typeof SharedArrayBuffer === "undefined") { + console.log("can_call_digest WasmCrypto:", JSON.stringify(globalThis.mono_wasm_crypto)); + console.log("can_call_digest SharedArrayBuffer:", JSON.stringify(typeof SharedArrayBuffer)); + if (!!globalThis.mono_wasm_crypto?.channel || typeof SharedArrayBuffer === "undefined") { return 0; // Not supported } return 1; From c23607f0afcd7aaa222ca4f286a0f34939cab486 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Mon, 25 Apr 2022 17:58:03 -0700 Subject: [PATCH 06/19] [Fix up] Correct execution of native digest API call at wasm layer --- eng/liveBuilds.targets | 1 + .../Directory.Build.props | 1 + src/mono/wasm/build/WasmApp.targets | 2 + src/mono/wasm/runtime/dotnet-crypto-worker.js | 176 ++++++++++++++++++ src/mono/wasm/runtime/dotnet-crypto-worker.ts | 175 ----------------- src/mono/wasm/runtime/exports.ts | 5 +- src/mono/wasm/runtime/library-channel.ts | 3 +- src/mono/wasm/runtime/startup.ts | 36 ++-- src/mono/wasm/wasm.proj | 5 +- .../pal_crypto_webworker.js | 11 +- .../Wasm.Build.Tests/BuildTestBase.cs | 1 + 11 files changed, 213 insertions(+), 203 deletions(-) create mode 100644 src/mono/wasm/runtime/dotnet-crypto-worker.js delete mode 100644 src/mono/wasm/runtime/dotnet-crypto-worker.ts diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index 77e9a90b97d30..d38e44645672b 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -180,6 +180,7 @@ + diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index cf8b18508cd7a..e0f1dfaf87a0d 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -261,6 +261,7 @@ <_HasDotnetWasm Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.wasm'">true <_HasDotnetJsWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.worker.js'">true + <_HasDotnetJsCryptoWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet-crypto-worker.js'">true <_HasDotnetJsSymbols Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js.symbols'">true <_HasDotnetJs Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'">true @@ -270,6 +271,7 @@ + diff --git a/src/mono/wasm/runtime/dotnet-crypto-worker.js b/src/mono/wasm/runtime/dotnet-crypto-worker.js new file mode 100644 index 0000000000000..b3d7068de4921 --- /dev/null +++ b/src/mono/wasm/runtime/dotnet-crypto-worker.js @@ -0,0 +1,176 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +var ChannelWorker = { + _impl: class { + // BEGIN ChannelOwner contract - shared constants. + get STATE_IDX() { return 0; } + get MSG_SIZE_IDX() { return 1; } + + // Communication states. + get STATE_SHUTDOWN() { return -1; } // Shutdown + get STATE_IDLE() { return 0; } + get STATE_REQ() { return 1; } + get STATE_RESP() { return 2; } + 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 + // END ChannelOwner contract - shared constants. + + constructor(comm_buf, msg_buf, msg_char_len) { + this.comm = new Int32Array(comm_buf); + this.msg = new Uint16Array(msg_buf); + this.msg_char_len = msg_char_len; + } + + async await_request(async_call) { + console.info("now awaiting requests in the web worker"); + for (;;) { + // eslint-disable-next-line no-debugger + debugger; + + // Wait for signal to perform operation + Atomics.wait(this.comm, this.STATE_IDX, this.STATE_IDLE); + + // Read in request + var req = this._read_request(); + // console.log("Request: " + req); + 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); + } + + // Send response + this._send_response(resp); + } + } + + _read_request() { + var request = ""; + for (;;) { + // 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); + + // The request is complete. + if (state === this.STATE_REQ) + break; + + // Shutdown the worker. + if (state === this.STATE_SHUTDOWN) + 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); + Atomics.wait(this.comm, this.STATE_IDX, this.STATE_AWAIT); + } + + return request; + } + + _read_from_msg(begin, end) { + return String.fromCharCode.apply(null, this.msg.slice(begin, end)); + } + + _send_response(msg) { + if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ) + throw "WORKER: Invalid sync communication channel state."; + + var state; // State machine variable + const msg_len = msg.length; + var msg_written = 0; + + for (;;) { + // 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); + + // Indicate if this was the whole message or part of it. + state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P; + + // Update the state + Atomics.store(this.comm, this.STATE_IDX, state); + + // 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); + + // Done sending response. + if (state === this.STATE_RESP) + break; + } + } + + _write_to_msg(input, start, input_len) { + var mi = 0; + var ii = start; + while (mi < this.msg_char_len && ii < input_len) { + this.msg[mi] = input.charCodeAt(ii); + ii++; // Next character + mi++; // Next buffer index + } + return ii - start; + } + }, + + create: function (comm_buf, msg_buf, msg_char_len) { + return new this._impl(comm_buf, msg_buf, msg_char_len); + } +}; + +async function call_digest(type, data) { + var digest_type = ""; + switch(type) { + case 0: digest_type = "SHA-1"; break; + case 1: digest_type = "SHA-256"; break; + case 2: digest_type = "SHA-384"; break; + case 3: digest_type = "SHA-512"; break; + default: + throw "CRYPTO: Unknown digest: " + type; + } + + // The 'crypto' API is not available in non-browser + // environments (for example, v8 server). + var digest = await crypto.subtle.digest(digest_type, data); + return Array.from(new Uint8Array(digest)); +} + +// Operation to perform. +async function async_call(msg) { + const req = JSON.parse(msg); + + if (req.func === "digest") { + var digestArr = await call_digest(req.type, new Uint8Array(req.data)); + return JSON.stringify(digestArr); + } else { + throw "CRYPTO: Unknown request: " + req.func; + } +} + +var s_channel; + +// Initialize WebWorker +onmessage = function (p) { + console.info("we are now initializing the webworker"); + var data = p; + if (p.data !== undefined) { + data = p.data; + } + s_channel = ChannelWorker.create(data.comm_buf, data.msg_buf, data.msg_char_len); + s_channel.await_request(async_call); +}; diff --git a/src/mono/wasm/runtime/dotnet-crypto-worker.ts b/src/mono/wasm/runtime/dotnet-crypto-worker.ts deleted file mode 100644 index dfd935c7a967d..0000000000000 --- a/src/mono/wasm/runtime/dotnet-crypto-worker.ts +++ /dev/null @@ -1,175 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -class ChannelWorker { - comm: Int32Array; - msg: Uint16Array; - msg_char_len: number; - - // BEGIN ChannelOwner contract - shared constants. - get STATE_IDX() { return 0; } - get MSG_SIZE_IDX() { return 1; } - - // Communication states. - get STATE_SHUTDOWN() { return -1; } // Shutdown - get STATE_IDLE() { return 0; } - get STATE_REQ() { return 1; } - get STATE_RESP() { return 2; } - 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 - // END ChannelOwner contract - shared constants. - - constructor(comm_buf: number[], msg_buf: number[], msg_char_len: number) { - this.comm = new Int32Array(comm_buf); - this.msg = new Uint16Array(msg_buf); - this.msg_char_len = msg_char_len; - } - - async await_request(async_call: Function) { - for (;;) { - // Wait for signal to perform operation - Atomics.wait(this.comm, this.STATE_IDX, this.STATE_IDLE); - - // Read in request - const req = this._read_request(); - if (req === this.STATE_SHUTDOWN) { - break; - } - - let resp = null; - try { - // Perform async action based on request - resp = await async_call(req); - } - catch (err) { - console.error("Request error: " + err); - resp = JSON.stringify(err); - } - - // Send response - this._send_response(resp); - } - } - - _read_request() { - let request = ""; - for (;;) { - // Get the current state and message size - const state = Atomics.load(this.comm, this.STATE_IDX); - const 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); - - // The request is complete. - if (state === this.STATE_REQ) - break; - - // Shutdown the worker. - if (state === this.STATE_SHUTDOWN) - 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); - Atomics.wait(this.comm, this.STATE_IDX, this.STATE_AWAIT); - } - - return request; - } - - _read_from_msg(begin: number, end: number) { - const slicedMessage: number[] = []; - this.msg.slice(begin, end).forEach((value, index) => slicedMessage[index] = value); - return String.fromCharCode.apply(null, slicedMessage); - } - - _send_response(msg: string) { - if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ) - throw "WORKER: Invalid sync communication channel state."; - - let state; // State machine variable - const msg_len = msg.length; - let msg_written = 0; - - for (;;) { - // 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 if this was the whole message or part of it. - state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P; - - // Update the state - Atomics.store(this.comm, this.STATE_IDX, state); - - // 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); - - // Done sending response. - if (state === this.STATE_RESP) - break; - } - } - - _write_to_msg(input: string, start: number, input_len: number) { - let mi = 0; - let ii = start; - while (mi < this.msg_char_len && ii < input_len) { - this.msg[mi] = input.charCodeAt(ii); - ii++; // Next character - mi++; // Next buffer index - } - return ii - start; - } - - static create(comm_buf: number[], msg_buf: number[], msg_char_len: number) { - return new ChannelWorker(comm_buf, msg_buf, msg_char_len); - } -} - -async function call_digest(type: number, data: BufferSource) { - let digest_type = ""; - switch(type) { - case 0: digest_type = "SHA-1"; break; - case 1: digest_type = "SHA-256"; break; - case 2: digest_type = "SHA-384"; break; - case 3: digest_type = "SHA-512"; break; - default: - throw "CRYPTO: Unknown digest: " + type; - } - - // The 'crypto' API is not available in non-browser - // environments (for example, v8 server). - const digest = await crypto.subtle.digest(digest_type, data); - return Array.from(new Uint8Array(digest)); -} - -// Operation to perform. -async function async_call(msg: string) { - const req = JSON.parse(msg); - - if (req.func === "digest") { - const digestArr = await call_digest(req.type, new Uint8Array(req.data)); - return JSON.stringify(digestArr); - } else { - throw "CRYPTO: Unknown request: " + req.func; - } -} - -let s_channel: ChannelWorker; - -// Initialize WebWorker -onmessage = function (p: any) { - let data = p; - if (p.data !== undefined) { - data = p.data; - } - s_channel = ChannelWorker.create(data.comm_buf, data.msg_buf, data.msg_char_len); - s_channel.await_request(async_call); -}; diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts index ece7a0bcde716..064ddcfa21478 100644 --- a/src/mono/wasm/runtime/exports.ts +++ b/src/mono/wasm/runtime/exports.ts @@ -66,7 +66,7 @@ import { import { create_weak_ref } from "./weak-ref"; import { fetch_like, readAsync_like } from "./polyfills"; import { EmscriptenModule } from "./types/emscripten"; -import { mono_run_main, mono_run_main_and_exit, mono_wasm_crypto } from "./run"; +import { mono_run_main, mono_run_main_and_exit } from "./run"; const MONO = { // current "public" MONO API @@ -388,9 +388,6 @@ const INTERNAL: any = { mono_wasm_raise_debug_event, mono_wasm_change_debugger_log_level, mono_wasm_runtime_is_ready: runtimeHelpers.mono_wasm_runtime_is_ready, - - // used for browser crypto - mono_wasm_crypto, }; diff --git a/src/mono/wasm/runtime/library-channel.ts b/src/mono/wasm/runtime/library-channel.ts index 0cee255c9e779..f45de6e78526d 100644 --- a/src/mono/wasm/runtime/library-channel.ts +++ b/src/mono/wasm/runtime/library-channel.ts @@ -121,8 +121,9 @@ export class LibraryChannel { response += this._read_from_msg(0, size_to_read); // The response is complete. - if (state === this.STATE_RESP) + if (state === this.STATE_RESP) { break; + } // Reset the size and transition to await state. Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 6075c32eef08c..0fcf7d47b84fc 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -50,6 +50,24 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: } } + console.debug ("MONO_WASM: Initialize WebWorkers"); + + if (ENVIRONMENT_IS_WEB && typeof SharedArrayBuffer !== "undefined") { + const chan = LibraryChannel.create(1024); + // // eslint-disable-next-line no-debugger + // debugger; + const worker = new Worker("dotnet-crypto-worker.js"); + (globalThis as any).mono_wasm_crypto = { + channel: chan, + worker: worker, + }; + worker.postMessage({ + comm_buf: chan.get_comm_buffer(), + msg_buf: chan.get_msg_buffer(), + msg_char_len: chan.get_msg_len() + }); + } + // these could be overriden on DotnetModuleConfig if (!module.preInit) { module.preInit = []; @@ -363,24 +381,6 @@ function finalize_startup(config: MonoConfig | MonoConfigError | undefined): voi } } - console.debug ("MONO_WASM: Initialize WebWorkers"); - - if (ENVIRONMENT_IS_WEB && typeof SharedArrayBuffer !== undefined) { - const chan = LibraryChannel.create(1024); - const worker = new Worker("dotnet-crypto-worker.ts"); - const globalThisAny = globalThis as any; - globalThisAny.mono_wasm_crypto = { - channel: chan, - worker: worker, - }; - console.log("Initialized worker:", JSON.stringify(globalThisAny.mono_wasm_crypto)); - worker.postMessage({ - comm_buff: chan.get_comm_buffer(), - msg_buf: chan.get_msg_buffer(), - msg_char_len: chan.get_msg_len() - }); - } - if (moduleExt.onDotnetReady) { try { moduleExt.onDotnetReady(); diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index f3ecdd4dd1517..388cd51552a35 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -226,6 +226,7 @@ diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js index e580c2e0f8e50..ab6aa1f933e85 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js +++ b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js @@ -13,6 +13,7 @@ const CryptoWebWorkerLib = { type: hash, data: Array.from(Module.HEAPU8.subarray (input_buffer, input_buffer + input_len)) }; + var response = globalThis.mono_wasm_crypto.channel.send_msg (JSON.stringify (msg)); var digest = JSON.parse (response); if (digest.length > output_len) { @@ -24,11 +25,13 @@ const CryptoWebWorkerLib = { return 1; }, can_call_digest: function () { - console.log("can_call_digest WasmCrypto:", JSON.stringify(globalThis.mono_wasm_crypto)); - console.log("can_call_digest SharedArrayBuffer:", JSON.stringify(typeof SharedArrayBuffer)); - if (!!globalThis.mono_wasm_crypto?.channel || typeof SharedArrayBuffer === "undefined") { - return 0; // Not supported + if (typeof globalThis.mono_wasm_crypto === "undefined" || + typeof globalThis.mono_wasm_crypto.channel === "undefined" || + typeof globalThis.mono_wasm_crypto.worker === "undefined" || + typeof SharedArrayBuffer === "undefined") { + return 0; } + return 1; } }, diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs index 076d87d069428..9c14682c61b43 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs @@ -500,6 +500,7 @@ protected static void AssertBasicAppBundle(string bundleDir, string projectName, "dotnet.wasm", "mono-config.json", "dotnet.js" + "dotnet-crypto-worker.js" }); AssertFilesExist(bundleDir, new[] { "run-v8.sh" }, expectToExist: hasV8Script); From 9cf48750ba25a2481a19746e70eee39c9f2a7d3b Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Wed, 4 May 2022 17:16:27 -0700 Subject: [PATCH 07/19] Update src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs --- src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs index 9c14682c61b43..5973c2b23a756 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs @@ -499,7 +499,7 @@ protected static void AssertBasicAppBundle(string bundleDir, string projectName, "dotnet.timezones.blat", "dotnet.wasm", "mono-config.json", - "dotnet.js" + "dotnet.js", "dotnet-crypto-worker.js" }); From 807a56689718d566c3d85d3e8abd99511e17695c Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Thu, 5 May 2022 14:34:50 -0700 Subject: [PATCH 08/19] Address feedback and clean up --- .../tests/InvalidUsageTests.cs | 1 - src/mono/wasm/runtime/dotnet-crypto-worker.js | 2 - src/mono/wasm/runtime/library-channel.ts | 62 +++++++++---------- src/mono/wasm/runtime/run.ts | 3 - src/mono/wasm/runtime/types.ts | 6 -- 5 files changed, 31 insertions(+), 43 deletions(-) diff --git a/src/libraries/System.Security.Cryptography/tests/InvalidUsageTests.cs b/src/libraries/System.Security.Cryptography/tests/InvalidUsageTests.cs index 56467efed3e8c..4114d97167e95 100644 --- a/src/libraries/System.Security.Cryptography/tests/InvalidUsageTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/InvalidUsageTests.cs @@ -26,7 +26,6 @@ public void InvalidHashCoreArgumentsFromDerivedType() [Fact] public void InvalidHashCoreArgumentsFromStream() { - //Diagnostics.Debugger.Launch(); using (SHA1 sha1 = SHA1.Create()) { AssertExtensions.Throws(null, () => sha1.ComputeHash(new BadReadStream(BadReadStream.ErrorCondition.TooLargeValueFromRead))); diff --git a/src/mono/wasm/runtime/dotnet-crypto-worker.js b/src/mono/wasm/runtime/dotnet-crypto-worker.js index b3d7068de4921..5bd462d9228b2 100644 --- a/src/mono/wasm/runtime/dotnet-crypto-worker.js +++ b/src/mono/wasm/runtime/dotnet-crypto-worker.js @@ -24,7 +24,6 @@ var ChannelWorker = { } async await_request(async_call) { - console.info("now awaiting requests in the web worker"); for (;;) { // eslint-disable-next-line no-debugger debugger; @@ -166,7 +165,6 @@ var s_channel; // Initialize WebWorker onmessage = function (p) { - console.info("we are now initializing the webworker"); var data = p; if (p.data !== undefined) { data = p.data; diff --git a/src/mono/wasm/runtime/library-channel.ts b/src/mono/wasm/runtime/library-channel.ts index f45de6e78526d..87452e46d1c85 100644 --- a/src/mono/wasm/runtime/library-channel.ts +++ b/src/mono/wasm/runtime/library-channel.ts @@ -2,27 +2,27 @@ // The .NET Foundation licenses this file to you under the MIT license. export class LibraryChannel { - msg_char_len: number; - comm_buf: SharedArrayBuffer; - msg_buf: SharedArrayBuffer; - comm: Int32Array; - msg: Uint16Array; + private msg_char_len: number; + private comm_buf: SharedArrayBuffer; + private msg_buf: SharedArrayBuffer; + private comm: Int32Array; + private msg: Uint16Array; // Index constants for the communication buffer. - get STATE_IDX(): number { return 0; } - get MSG_SIZE_IDX(): number { return 1; } - get COMM_LAST_IDX(): number { return this.MSG_SIZE_IDX; } + private get STATE_IDX(): number { return 0; } + private get MSG_SIZE_IDX(): number { return 1; } + private get COMM_LAST_IDX(): number { return this.MSG_SIZE_IDX; } // Communication states. - get STATE_SHUTDOWN(): number { return -1; } // Shutdown - get STATE_IDLE(): number { return 0; } - get STATE_REQ(): number { return 1; } - get STATE_RESP(): number { return 2; } - get STATE_REQ_P(): number { return 3; } // Request has multiple parts - get STATE_RESP_P(): number { return 4; } // Response has multiple parts - get STATE_AWAIT(): number { return 5; } // Awaiting the next part - - constructor(msg_char_len: number) { + private get STATE_SHUTDOWN(): number { return -1; } // Shutdown + private get STATE_IDLE(): number { return 0; } + private get STATE_REQ(): number { return 1; } + private get STATE_RESP(): number { return 2; } + 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 constructor(msg_char_len: number) { this.msg_char_len = msg_char_len; const int_bytes = 4; @@ -39,19 +39,19 @@ export class LibraryChannel { this.msg = new Uint16Array(this.msg_buf); } - get_msg_len(): number { return this.msg_char_len; } - get_msg_buffer(): SharedArrayBuffer { return this.msg_buf; } - get_comm_buffer(): SharedArrayBuffer { return this.comm_buf; } + public get_msg_len(): number { return this.msg_char_len; } + public get_msg_buffer(): SharedArrayBuffer { return this.msg_buf; } + public get_comm_buffer(): SharedArrayBuffer { return this.comm_buf; } - send_msg(msg: string): string { + 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); } - this._send_request(msg); - return this._read_response(); + this.send_request(msg); + return this.read_response(); } - shutdown(): void { + 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); } @@ -62,14 +62,14 @@ export class LibraryChannel { Atomics.notify(this.comm, this.STATE_IDX); } - _send_request(msg: string): void { + private send_request(msg: string): void { let state; const msg_len = msg.length; let msg_written = 0; for (;;) { // Write the message and return how much was written. - const wrote = this._write_to_msg(msg, msg_written, msg_len); + const wrote = this.write_to_msg(msg, msg_written, msg_len); msg_written += wrote; // Indicate how much was written to the this.msg buffer. @@ -94,7 +94,7 @@ export class LibraryChannel { } } - _write_to_msg(input: string, start: number, input_len: number): number { + private write_to_msg(input: string, start: number, input_len: number): number { let mi = 0; let ii = start; while (mi < this.msg_char_len && ii < input_len) { @@ -105,7 +105,7 @@ export class LibraryChannel { return ii - start; } - _read_response(): string { + private read_response(): string { let state; let response = ""; for (;;) { @@ -118,7 +118,7 @@ export class LibraryChannel { 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); + response += this.read_from_msg(0, size_to_read); // The response is complete. if (state === this.STATE_RESP) { @@ -139,13 +139,13 @@ export class LibraryChannel { return response; } - _read_from_msg(begin: number, end: number): string { + private read_from_msg(begin: number, end: number): string { const slicedMessage: number[] = []; this.msg.slice(begin, end).forEach((value, index) => slicedMessage[index] = value); return String.fromCharCode.apply(null, slicedMessage); } - static create(msg_char_len: number): LibraryChannel { + 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/run.ts b/src/mono/wasm/runtime/run.ts index 6fbe20a09291c..492188352e915 100644 --- a/src/mono/wasm/runtime/run.ts +++ b/src/mono/wasm/runtime/run.ts @@ -1,7 +1,6 @@ import { ExitStatus, INTERNAL, Module, quit } from "./imports"; import { mono_call_assembly_entry_point } from "./method-calls"; import { mono_wasm_set_main_args, runtime_is_initialized_reject } from "./startup"; -import { MonoWasmCrypto } from "./types"; export async function mono_run_main_and_exit(main_assembly_name: string, args: string[]): Promise { try { @@ -40,5 +39,3 @@ function set_exit_code(exit_code: number, reason?: any) { } quit(exit_code, reason); } - -export let mono_wasm_crypto: MonoWasmCrypto; diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index b5b9d0192bb0f..f32ed4cee2087 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -3,7 +3,6 @@ import { bind_runtime_method } from "./method-binding"; import { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr } from "./types/emscripten"; -import { LibraryChannel } from "./library-channel"; export type GCHandle = { __brand: "GCHandle" @@ -263,8 +262,3 @@ export const enum MarshalError { UNSUPPORTED_TYPE = 515, FIRST = BUFFER_TOO_SMALL } - -export type MonoWasmCrypto = { - channel: LibraryChannel, - worker: Worker, -} From 6a743909605fb5b1194cae6bf571c2e6ff059409 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Fri, 6 May 2022 13:20:06 -0700 Subject: [PATCH 09/19] Re-implement the crypto worker in ts --- eng/liveBuilds.targets | 2 +- .../Directory.Build.props | 2 +- src/mono/wasm/build/WasmApp.targets | 4 +- src/mono/wasm/runtime/dotnet-crypto-worker.js | 174 ----------------- src/mono/wasm/runtime/dotnet-crypto-worker.ts | 175 ++++++++++++++++++ src/mono/wasm/runtime/startup.ts | 2 +- src/mono/wasm/wasm.proj | 4 +- .../Wasm.Build.Tests/BuildTestBase.cs | 2 +- 8 files changed, 183 insertions(+), 182 deletions(-) delete mode 100644 src/mono/wasm/runtime/dotnet-crypto-worker.js create mode 100644 src/mono/wasm/runtime/dotnet-crypto-worker.ts diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index d38e44645672b..a012c4262c74c 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -180,7 +180,7 @@ - + diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index e0f1dfaf87a0d..63c7604aa1ba9 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -261,7 +261,7 @@ <_HasDotnetWasm Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.wasm'">true <_HasDotnetJsWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.worker.js'">true - <_HasDotnetJsCryptoWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet-crypto-worker.js'">true + <_HasDotnetJsCryptoWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet-crypto-worker.ts'">true <_HasDotnetJsSymbols Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js.symbols'">true <_HasDotnetJs Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'">true @@ -271,7 +271,7 @@ - + diff --git a/src/mono/wasm/runtime/dotnet-crypto-worker.js b/src/mono/wasm/runtime/dotnet-crypto-worker.js deleted file mode 100644 index 5bd462d9228b2..0000000000000 --- a/src/mono/wasm/runtime/dotnet-crypto-worker.js +++ /dev/null @@ -1,174 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -var ChannelWorker = { - _impl: class { - // BEGIN ChannelOwner contract - shared constants. - get STATE_IDX() { return 0; } - get MSG_SIZE_IDX() { return 1; } - - // Communication states. - get STATE_SHUTDOWN() { return -1; } // Shutdown - get STATE_IDLE() { return 0; } - get STATE_REQ() { return 1; } - get STATE_RESP() { return 2; } - 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 - // END ChannelOwner contract - shared constants. - - constructor(comm_buf, msg_buf, msg_char_len) { - this.comm = new Int32Array(comm_buf); - this.msg = new Uint16Array(msg_buf); - this.msg_char_len = msg_char_len; - } - - async await_request(async_call) { - for (;;) { - // eslint-disable-next-line no-debugger - debugger; - - // Wait for signal to perform operation - Atomics.wait(this.comm, this.STATE_IDX, this.STATE_IDLE); - - // Read in request - var req = this._read_request(); - // console.log("Request: " + req); - 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); - } - - // Send response - this._send_response(resp); - } - } - - _read_request() { - var request = ""; - for (;;) { - // 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); - - // The request is complete. - if (state === this.STATE_REQ) - break; - - // Shutdown the worker. - if (state === this.STATE_SHUTDOWN) - 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); - Atomics.wait(this.comm, this.STATE_IDX, this.STATE_AWAIT); - } - - return request; - } - - _read_from_msg(begin, end) { - return String.fromCharCode.apply(null, this.msg.slice(begin, end)); - } - - _send_response(msg) { - if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ) - throw "WORKER: Invalid sync communication channel state."; - - var state; // State machine variable - const msg_len = msg.length; - var msg_written = 0; - - for (;;) { - // 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); - - // Indicate if this was the whole message or part of it. - state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P; - - // Update the state - Atomics.store(this.comm, this.STATE_IDX, state); - - // 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); - - // Done sending response. - if (state === this.STATE_RESP) - break; - } - } - - _write_to_msg(input, start, input_len) { - var mi = 0; - var ii = start; - while (mi < this.msg_char_len && ii < input_len) { - this.msg[mi] = input.charCodeAt(ii); - ii++; // Next character - mi++; // Next buffer index - } - return ii - start; - } - }, - - create: function (comm_buf, msg_buf, msg_char_len) { - return new this._impl(comm_buf, msg_buf, msg_char_len); - } -}; - -async function call_digest(type, data) { - var digest_type = ""; - switch(type) { - case 0: digest_type = "SHA-1"; break; - case 1: digest_type = "SHA-256"; break; - case 2: digest_type = "SHA-384"; break; - case 3: digest_type = "SHA-512"; break; - default: - throw "CRYPTO: Unknown digest: " + type; - } - - // The 'crypto' API is not available in non-browser - // environments (for example, v8 server). - var digest = await crypto.subtle.digest(digest_type, data); - return Array.from(new Uint8Array(digest)); -} - -// Operation to perform. -async function async_call(msg) { - const req = JSON.parse(msg); - - if (req.func === "digest") { - var digestArr = await call_digest(req.type, new Uint8Array(req.data)); - return JSON.stringify(digestArr); - } else { - throw "CRYPTO: Unknown request: " + req.func; - } -} - -var s_channel; - -// Initialize WebWorker -onmessage = function (p) { - var data = p; - if (p.data !== undefined) { - data = p.data; - } - s_channel = ChannelWorker.create(data.comm_buf, data.msg_buf, data.msg_char_len); - s_channel.await_request(async_call); -}; diff --git a/src/mono/wasm/runtime/dotnet-crypto-worker.ts b/src/mono/wasm/runtime/dotnet-crypto-worker.ts new file mode 100644 index 0000000000000..b9e87f90264c0 --- /dev/null +++ b/src/mono/wasm/runtime/dotnet-crypto-worker.ts @@ -0,0 +1,175 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +class ChannelWorker { + private comm: Int32Array; + private msg: Uint16Array; + private msg_char_len: number; + + // BEGIN ChannelOwner contract - shared constants. + private get STATE_IDX() { return 0; } + private get MSG_SIZE_IDX() { return 1; } + + // Communication states. + private get STATE_SHUTDOWN() { return -1; } // Shutdown + private get STATE_IDLE() { return 0; } + private get STATE_REQ() { return 1; } + private get STATE_RESP() { return 2; } + private get STATE_REQ_P() { return 3; } // Request has multiple parts + private get STATE_RESP_P() { return 4; } // Response has multiple parts + private get STATE_AWAIT() { return 5; } // Awaiting the next part + // END ChannelOwner contract - shared constants. + + private constructor(comm_buf: number[], msg_buf: number[], msg_char_len: number) { + this.comm = new Int32Array(comm_buf); + this.msg = new Uint16Array(msg_buf); + this.msg_char_len = msg_char_len; + } + + public async await_request(async_call: Function) { + for (;;) { + // Wait for signal to perform operation + Atomics.wait(this.comm, this.STATE_IDX, this.STATE_IDLE); + + // Read in request + const req = this.read_request(); + if (req === this.STATE_SHUTDOWN) { + break; + } + + let resp = null; + try { + // Perform async action based on request + resp = await async_call(req); + } + catch (err) { + console.error("Request error: " + err); + resp = JSON.stringify(err); + } + + // Send response + this.send_response(resp); + } + } + + private read_request() { + let request = ""; + for (;;) { + // Get the current state and message size + const state = Atomics.load(this.comm, this.STATE_IDX); + const 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); + + // The request is complete. + if (state === this.STATE_REQ) + break; + + // Shutdown the worker. + if (state === this.STATE_SHUTDOWN) + 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); + Atomics.wait(this.comm, this.STATE_IDX, this.STATE_AWAIT); + } + + return request; + } + + private read_from_msg(begin: number, end: number) { + const slicedMessage: number[] = []; + this.msg.slice(begin, end).forEach((value, index) => slicedMessage[index] = value); + return String.fromCharCode.apply(null, slicedMessage); + } + + private send_response(msg: string) { + if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ) + throw "WORKER: Invalid sync communication channel state."; + + let state; // State machine variable + const msg_len = msg.length; + let msg_written = 0; + + for (;;) { + // 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 if this was the whole message or part of it. + state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P; + + // Update the state + Atomics.store(this.comm, this.STATE_IDX, state); + + // 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); + + // Done sending response. + if (state === this.STATE_RESP) + break; + } + } + + private write_to_msg(input: string, start: number, input_len: number) { + let mi = 0; + let ii = start; + while (mi < this.msg_char_len && ii < input_len) { + this.msg[mi] = input.charCodeAt(ii); + ii++; // Next character + mi++; // Next buffer index + } + return ii - start; + } + + public static create(comm_buf: number[], msg_buf: number[], msg_char_len: number) { + return new ChannelWorker(comm_buf, msg_buf, msg_char_len); + } +} + +async function call_digest(type: number, data: BufferSource) { + let digest_type = ""; + switch(type) { + case 0: digest_type = "SHA-1"; break; + case 1: digest_type = "SHA-256"; break; + case 2: digest_type = "SHA-384"; break; + case 3: digest_type = "SHA-512"; break; + default: + throw "CRYPTO: Unknown digest: " + type; + } + + // The 'crypto' API is not available in non-browser + // environments (for example, v8 server). + const digest = await crypto.subtle.digest(digest_type, data); + return Array.from(new Uint8Array(digest)); +} + +// Operation to perform. +async function async_call(msg: string) { + const req = JSON.parse(msg); + + if (req.func === "digest") { + const digestArr = await call_digest(req.type, new Uint8Array(req.data)); + return JSON.stringify(digestArr); + } else { + throw "CRYPTO: Unknown request: " + req.func; + } +} + +let s_channel: ChannelWorker; + +// Initialize WebWorker +onmessage = function (p: any) { + let data = p; + if (p.data !== undefined) { + data = p.data; + } + s_channel = ChannelWorker.create(data.comm_buf, data.msg_buf, data.msg_char_len); + s_channel.await_request(async_call); +}; diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 0fcf7d47b84fc..67a3a1d24ab14 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -56,7 +56,7 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: const chan = LibraryChannel.create(1024); // // eslint-disable-next-line no-debugger // debugger; - const worker = new Worker("dotnet-crypto-worker.js"); + const worker = new Worker("dotnet-crypto-worker.ts"); (globalThis as any).mono_wasm_crypto = { channel: chan, worker: worker, diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 388cd51552a35..49e2c30d2a467 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -226,7 +226,7 @@ diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs index 5973c2b23a756..2784ba13fbbad 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs @@ -500,7 +500,7 @@ protected static void AssertBasicAppBundle(string bundleDir, string projectName, "dotnet.wasm", "mono-config.json", "dotnet.js", - "dotnet-crypto-worker.js" + "dotnet-crypto-worker.ts" }); AssertFilesExist(bundleDir, new[] { "run-v8.sh" }, expectToExist: hasV8Script); From 0f859511076c97e5cd4b89789616b69286819215 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Fri, 6 May 2022 17:15:34 -0700 Subject: [PATCH 10/19] Address feedback --- .../Interop.SimpleDigestHash.cs | 1 + .../SHAHashProvider.Browser.Native.cs | 46 +++++++++---------- src/mono/wasm/runtime/startup.ts | 10 ++-- src/mono/wasm/runtime/types.ts | 3 +- .../pal_crypto_webworker.h | 2 + .../pal_crypto_webworker.js | 3 +- 6 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs b/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs index ed72c7328eda8..1304b45735b7e 100644 --- a/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs +++ b/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs @@ -9,6 +9,7 @@ internal static partial class Interop { internal static partial class BrowserCrypto { + // These values are also defined in the pal_crypto_webworker header file, and utilized in the dotnet-crypto-worker in the wasm runtime. internal enum SimpleDigest { Sha1, diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs index 4527252d93661..c037761aafb54 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs @@ -12,9 +12,9 @@ namespace Internal.Cryptography { internal sealed class SHANativeHashProvider : HashProvider { - private readonly int hashSizeInBytes; - private readonly SimpleDigest impl; - private MemoryStream? buffer; + private readonly int _hashSizeInBytes; + private readonly SimpleDigest _impl; + private MemoryStream? _buffer; public SHANativeHashProvider(string hashAlgorithmId) { @@ -23,20 +23,20 @@ public SHANativeHashProvider(string hashAlgorithmId) switch (hashAlgorithmId) { case HashAlgorithmNames.SHA1: - impl = SimpleDigest.Sha1; - hashSizeInBytes = 20; + _impl = SimpleDigest.Sha1; + _hashSizeInBytes = 20; break; case HashAlgorithmNames.SHA256: - impl = SimpleDigest.Sha256; - hashSizeInBytes = 32; + _impl = SimpleDigest.Sha256; + _hashSizeInBytes = 32; break; case HashAlgorithmNames.SHA384: - impl = SimpleDigest.Sha384; - hashSizeInBytes = 48; + _impl = SimpleDigest.Sha384; + _hashSizeInBytes = 48; break; case HashAlgorithmNames.SHA512: - impl = SimpleDigest.Sha512; - hashSizeInBytes = 64; + _impl = SimpleDigest.Sha512; + _hashSizeInBytes = 64; break; default: throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId)); @@ -45,28 +45,28 @@ public SHANativeHashProvider(string hashAlgorithmId) public override void AppendHashData(ReadOnlySpan data) { - buffer ??= new MemoryStream(1000); - buffer.Write(data); + _buffer ??= new MemoryStream(1000); + _buffer.Write(data); } public override int FinalizeHashAndReset(Span destination) { GetCurrentHash(destination); - buffer = null; + _buffer = null; - return hashSizeInBytes; + return _hashSizeInBytes; } public override int GetCurrentHash(Span destination) { - Debug.Assert(destination.Length >= hashSizeInBytes); + Debug.Assert(destination.Length >= _hashSizeInBytes); byte[] srcArray = Array.Empty(); int srcLength = 0; - if (buffer != null) + if (_buffer != null) { - srcArray = buffer.GetBuffer(); - srcLength = (int)buffer.Length; + srcArray = _buffer.GetBuffer(); + srcLength = (int)_buffer.Length; } unsafe @@ -74,15 +74,15 @@ public override int GetCurrentHash(Span destination) fixed (byte* src = srcArray) fixed (byte* dest = destination) { - int res = Interop.BrowserCrypto.SimpleDigestHash(impl, src, srcLength, dest, destination.Length); + int res = Interop.BrowserCrypto.SimpleDigestHash(_impl, src, srcLength, dest, destination.Length); Debug.Assert(res != 0); } } - return hashSizeInBytes; + return _hashSizeInBytes; } - public override int HashSizeInBytes => hashSizeInBytes; + public override int HashSizeInBytes => _hashSizeInBytes; public override void Dispose(bool disposing) { @@ -90,7 +90,7 @@ public override void Dispose(bool disposing) public override void Reset() { - buffer = null; + _buffer = null; } } } diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 67a3a1d24ab14..e11b8dbe08969 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -51,11 +51,9 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: } console.debug ("MONO_WASM: Initialize WebWorkers"); - - if (ENVIRONMENT_IS_WEB && typeof SharedArrayBuffer !== "undefined") { - const chan = LibraryChannel.create(1024); - // // eslint-disable-next-line no-debugger - // debugger; + // Consider the can_call_digest check in the pal_crypto_webworker when updating this check. + if (ENVIRONMENT_IS_WEB && typeof crypto.subtle === "undefined" && typeof SharedArrayBuffer !== "undefined") { + const chan = LibraryChannel.create(1024); // 1024 is the buffer size in char units. const worker = new Worker("dotnet-crypto-worker.ts"); (globalThis as any).mono_wasm_crypto = { channel: chan, @@ -321,7 +319,7 @@ function finalize_startup(config: MonoConfig | MonoConfigError | undefined): voi const moduleExt = Module as DotnetModule; - if (!Module.disableDotnet6Compatibility && Module.exports){ + if (!Module.disableDotnet6Compatibility && Module.exports) { // Export emscripten defined in module through EXPORTED_RUNTIME_METHODS // Useful to export IDBFS or other similar types generally exposed as // global types when emscripten is not modularized. diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index f32ed4cee2087..2b85e89953816 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -181,7 +181,6 @@ export type DotnetModuleConfig = { config?: MonoConfig | MonoConfigError, configSrc?: string, - onConfigLoaded?: (config: MonoConfig) => Promise; onDotnetReady?: () => void; @@ -261,4 +260,4 @@ export const enum MarshalError { NULL_TYPE_POINTER = 514, UNSUPPORTED_TYPE = 515, FIRST = BUFFER_TOO_SMALL -} +} \ No newline at end of file diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h index d2687d4b63021..fe8b4d2762bf2 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h +++ b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h @@ -5,6 +5,8 @@ #include +// These values are also defined in the System.Security.Cryptography library's +// browser-crypto implementation, and utilized in the dotnet-crypto-worker in the wasm runtime. enum simple_digest { sd_sha_1, diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js index ab6aa1f933e85..8df0f373658eb 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js +++ b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js @@ -27,8 +27,7 @@ const CryptoWebWorkerLib = { can_call_digest: function () { if (typeof globalThis.mono_wasm_crypto === "undefined" || typeof globalThis.mono_wasm_crypto.channel === "undefined" || - typeof globalThis.mono_wasm_crypto.worker === "undefined" || - typeof SharedArrayBuffer === "undefined") { + typeof globalThis.mono_wasm_crypto.worker === "undefined") { return 0; } From 72e47b8a6e82e405255282bdc0ff899a6f0da3e0 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Fri, 6 May 2022 17:33:49 -0700 Subject: [PATCH 11/19] Revert "Re-implement the crypto worker in ts" This reverts commit 6a743909605fb5b1194cae6bf571c2e6ff059409. --- eng/liveBuilds.targets | 2 +- .../Directory.Build.props | 2 +- src/mono/wasm/build/WasmApp.targets | 4 +- src/mono/wasm/runtime/dotnet-crypto-worker.js | 174 +++++++++++++++++ src/mono/wasm/runtime/dotnet-crypto-worker.ts | 175 ------------------ src/mono/wasm/runtime/startup.ts | 2 +- src/mono/wasm/wasm.proj | 4 +- .../Wasm.Build.Tests/BuildTestBase.cs | 2 +- 8 files changed, 182 insertions(+), 183 deletions(-) create mode 100644 src/mono/wasm/runtime/dotnet-crypto-worker.js delete mode 100644 src/mono/wasm/runtime/dotnet-crypto-worker.ts diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index a012c4262c74c..d38e44645672b 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -180,7 +180,7 @@ - + diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 63c7604aa1ba9..e0f1dfaf87a0d 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -261,7 +261,7 @@ <_HasDotnetWasm Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.wasm'">true <_HasDotnetJsWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.worker.js'">true - <_HasDotnetJsCryptoWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet-crypto-worker.ts'">true + <_HasDotnetJsCryptoWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet-crypto-worker.js'">true <_HasDotnetJsSymbols Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js.symbols'">true <_HasDotnetJs Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'">true @@ -271,7 +271,7 @@ - + diff --git a/src/mono/wasm/runtime/dotnet-crypto-worker.js b/src/mono/wasm/runtime/dotnet-crypto-worker.js new file mode 100644 index 0000000000000..5bd462d9228b2 --- /dev/null +++ b/src/mono/wasm/runtime/dotnet-crypto-worker.js @@ -0,0 +1,174 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +var ChannelWorker = { + _impl: class { + // BEGIN ChannelOwner contract - shared constants. + get STATE_IDX() { return 0; } + get MSG_SIZE_IDX() { return 1; } + + // Communication states. + get STATE_SHUTDOWN() { return -1; } // Shutdown + get STATE_IDLE() { return 0; } + get STATE_REQ() { return 1; } + get STATE_RESP() { return 2; } + 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 + // END ChannelOwner contract - shared constants. + + constructor(comm_buf, msg_buf, msg_char_len) { + this.comm = new Int32Array(comm_buf); + this.msg = new Uint16Array(msg_buf); + this.msg_char_len = msg_char_len; + } + + async await_request(async_call) { + for (;;) { + // eslint-disable-next-line no-debugger + debugger; + + // Wait for signal to perform operation + Atomics.wait(this.comm, this.STATE_IDX, this.STATE_IDLE); + + // Read in request + var req = this._read_request(); + // console.log("Request: " + req); + 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); + } + + // Send response + this._send_response(resp); + } + } + + _read_request() { + var request = ""; + for (;;) { + // 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); + + // The request is complete. + if (state === this.STATE_REQ) + break; + + // Shutdown the worker. + if (state === this.STATE_SHUTDOWN) + 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); + Atomics.wait(this.comm, this.STATE_IDX, this.STATE_AWAIT); + } + + return request; + } + + _read_from_msg(begin, end) { + return String.fromCharCode.apply(null, this.msg.slice(begin, end)); + } + + _send_response(msg) { + if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ) + throw "WORKER: Invalid sync communication channel state."; + + var state; // State machine variable + const msg_len = msg.length; + var msg_written = 0; + + for (;;) { + // 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); + + // Indicate if this was the whole message or part of it. + state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P; + + // Update the state + Atomics.store(this.comm, this.STATE_IDX, state); + + // 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); + + // Done sending response. + if (state === this.STATE_RESP) + break; + } + } + + _write_to_msg(input, start, input_len) { + var mi = 0; + var ii = start; + while (mi < this.msg_char_len && ii < input_len) { + this.msg[mi] = input.charCodeAt(ii); + ii++; // Next character + mi++; // Next buffer index + } + return ii - start; + } + }, + + create: function (comm_buf, msg_buf, msg_char_len) { + return new this._impl(comm_buf, msg_buf, msg_char_len); + } +}; + +async function call_digest(type, data) { + var digest_type = ""; + switch(type) { + case 0: digest_type = "SHA-1"; break; + case 1: digest_type = "SHA-256"; break; + case 2: digest_type = "SHA-384"; break; + case 3: digest_type = "SHA-512"; break; + default: + throw "CRYPTO: Unknown digest: " + type; + } + + // The 'crypto' API is not available in non-browser + // environments (for example, v8 server). + var digest = await crypto.subtle.digest(digest_type, data); + return Array.from(new Uint8Array(digest)); +} + +// Operation to perform. +async function async_call(msg) { + const req = JSON.parse(msg); + + if (req.func === "digest") { + var digestArr = await call_digest(req.type, new Uint8Array(req.data)); + return JSON.stringify(digestArr); + } else { + throw "CRYPTO: Unknown request: " + req.func; + } +} + +var s_channel; + +// Initialize WebWorker +onmessage = function (p) { + var data = p; + if (p.data !== undefined) { + data = p.data; + } + s_channel = ChannelWorker.create(data.comm_buf, data.msg_buf, data.msg_char_len); + s_channel.await_request(async_call); +}; diff --git a/src/mono/wasm/runtime/dotnet-crypto-worker.ts b/src/mono/wasm/runtime/dotnet-crypto-worker.ts deleted file mode 100644 index b9e87f90264c0..0000000000000 --- a/src/mono/wasm/runtime/dotnet-crypto-worker.ts +++ /dev/null @@ -1,175 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -class ChannelWorker { - private comm: Int32Array; - private msg: Uint16Array; - private msg_char_len: number; - - // BEGIN ChannelOwner contract - shared constants. - private get STATE_IDX() { return 0; } - private get MSG_SIZE_IDX() { return 1; } - - // Communication states. - private get STATE_SHUTDOWN() { return -1; } // Shutdown - private get STATE_IDLE() { return 0; } - private get STATE_REQ() { return 1; } - private get STATE_RESP() { return 2; } - private get STATE_REQ_P() { return 3; } // Request has multiple parts - private get STATE_RESP_P() { return 4; } // Response has multiple parts - private get STATE_AWAIT() { return 5; } // Awaiting the next part - // END ChannelOwner contract - shared constants. - - private constructor(comm_buf: number[], msg_buf: number[], msg_char_len: number) { - this.comm = new Int32Array(comm_buf); - this.msg = new Uint16Array(msg_buf); - this.msg_char_len = msg_char_len; - } - - public async await_request(async_call: Function) { - for (;;) { - // Wait for signal to perform operation - Atomics.wait(this.comm, this.STATE_IDX, this.STATE_IDLE); - - // Read in request - const req = this.read_request(); - if (req === this.STATE_SHUTDOWN) { - break; - } - - let resp = null; - try { - // Perform async action based on request - resp = await async_call(req); - } - catch (err) { - console.error("Request error: " + err); - resp = JSON.stringify(err); - } - - // Send response - this.send_response(resp); - } - } - - private read_request() { - let request = ""; - for (;;) { - // Get the current state and message size - const state = Atomics.load(this.comm, this.STATE_IDX); - const 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); - - // The request is complete. - if (state === this.STATE_REQ) - break; - - // Shutdown the worker. - if (state === this.STATE_SHUTDOWN) - 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); - Atomics.wait(this.comm, this.STATE_IDX, this.STATE_AWAIT); - } - - return request; - } - - private read_from_msg(begin: number, end: number) { - const slicedMessage: number[] = []; - this.msg.slice(begin, end).forEach((value, index) => slicedMessage[index] = value); - return String.fromCharCode.apply(null, slicedMessage); - } - - private send_response(msg: string) { - if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ) - throw "WORKER: Invalid sync communication channel state."; - - let state; // State machine variable - const msg_len = msg.length; - let msg_written = 0; - - for (;;) { - // 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 if this was the whole message or part of it. - state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P; - - // Update the state - Atomics.store(this.comm, this.STATE_IDX, state); - - // 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); - - // Done sending response. - if (state === this.STATE_RESP) - break; - } - } - - private write_to_msg(input: string, start: number, input_len: number) { - let mi = 0; - let ii = start; - while (mi < this.msg_char_len && ii < input_len) { - this.msg[mi] = input.charCodeAt(ii); - ii++; // Next character - mi++; // Next buffer index - } - return ii - start; - } - - public static create(comm_buf: number[], msg_buf: number[], msg_char_len: number) { - return new ChannelWorker(comm_buf, msg_buf, msg_char_len); - } -} - -async function call_digest(type: number, data: BufferSource) { - let digest_type = ""; - switch(type) { - case 0: digest_type = "SHA-1"; break; - case 1: digest_type = "SHA-256"; break; - case 2: digest_type = "SHA-384"; break; - case 3: digest_type = "SHA-512"; break; - default: - throw "CRYPTO: Unknown digest: " + type; - } - - // The 'crypto' API is not available in non-browser - // environments (for example, v8 server). - const digest = await crypto.subtle.digest(digest_type, data); - return Array.from(new Uint8Array(digest)); -} - -// Operation to perform. -async function async_call(msg: string) { - const req = JSON.parse(msg); - - if (req.func === "digest") { - const digestArr = await call_digest(req.type, new Uint8Array(req.data)); - return JSON.stringify(digestArr); - } else { - throw "CRYPTO: Unknown request: " + req.func; - } -} - -let s_channel: ChannelWorker; - -// Initialize WebWorker -onmessage = function (p: any) { - let data = p; - if (p.data !== undefined) { - data = p.data; - } - s_channel = ChannelWorker.create(data.comm_buf, data.msg_buf, data.msg_char_len); - s_channel.await_request(async_call); -}; diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index e11b8dbe08969..76a9d8875d835 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -54,7 +54,7 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: // Consider the can_call_digest check in the pal_crypto_webworker when updating this check. if (ENVIRONMENT_IS_WEB && typeof crypto.subtle === "undefined" && typeof SharedArrayBuffer !== "undefined") { const chan = LibraryChannel.create(1024); // 1024 is the buffer size in char units. - const worker = new Worker("dotnet-crypto-worker.ts"); + const worker = new Worker("dotnet-crypto-worker.js"); (globalThis as any).mono_wasm_crypto = { channel: chan, worker: worker, diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 49e2c30d2a467..388cd51552a35 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -226,7 +226,7 @@ diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs index 2784ba13fbbad..5973c2b23a756 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs @@ -500,7 +500,7 @@ protected static void AssertBasicAppBundle(string bundleDir, string projectName, "dotnet.wasm", "mono-config.json", "dotnet.js", - "dotnet-crypto-worker.ts" + "dotnet-crypto-worker.js" }); AssertFilesExist(bundleDir, new[] { "run-v8.sh" }, expectToExist: hasV8Script); From cf7cf370d18c14a3f01388132ef4bc845455a60c Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 9 May 2022 18:11:28 +0200 Subject: [PATCH 12/19] * moved stuff around and renamed it * initialization bit later --- .../Directory.Build.props | 1 - .../System.Security.Cryptography.Tests.csproj | 5 +- src/mono/wasm/runtime/CMakeLists.txt | 4 +- src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js | 4 ++ .../{library-channel.ts => crypto-worker.ts} | 62 +++++++++++++++++-- src/mono/wasm/runtime/es6/dotnet.es6.lib.js | 4 ++ src/mono/wasm/runtime/exports.ts | 5 ++ src/mono/wasm/runtime/startup.ts | 22 ++----- src/mono/wasm/wasm.proj | 6 +- .../pal_crypto_webworker.js | 46 -------------- 10 files changed, 83 insertions(+), 76 deletions(-) rename src/mono/wasm/runtime/{library-channel.ts => crypto-worker.ts} (75%) delete mode 100644 src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index dd64bd1ca68d7..6704af1c52832 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -225,7 +225,6 @@ - diff --git a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj index 8ed095410d4d5..f8e1b4697b804 100644 --- a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj +++ b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj @@ -6,10 +6,13 @@ true true + + WasmTestOnBrowser + $(WasmXHarnessArgs) --web-server-use-cop + $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) - $(WasmXHarnessArgs) --web-server-use-cop true diff --git a/src/mono/wasm/runtime/CMakeLists.txt b/src/mono/wasm/runtime/CMakeLists.txt index 647cc108eef69..9962dbff59c2b 100644 --- a/src/mono/wasm/runtime/CMakeLists.txt +++ b/src/mono/wasm/runtime/CMakeLists.txt @@ -30,8 +30,8 @@ target_link_libraries(dotnet ${NATIVE_BIN_DIR}/libSystem.Security.Cryptography.Native.Browser.a) set_target_properties(dotnet PROPERTIES - LINK_DEPENDS "${NATIVE_BIN_DIR}/src/emcc-default.rsp;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.pre.js;${NATIVE_BIN_DIR}/src/cjs/runtime.cjs.iffe.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.lib.js;${NATIVE_BIN_DIR}/src/pal_random.lib.js;${NATIVE_BIN_DIR}/src/pal_crypto_webworker.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.post.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.extpost.js;" - LINK_FLAGS "@${NATIVE_BIN_DIR}/src/emcc-default.rsp @${NATIVE_BIN_DIR}/src/emcc-link.rsp ${CONFIGURATION_LINK_FLAGS} --extern-pre-js ${NATIVE_BIN_DIR}/src/cjs/runtime.cjs.iffe.js --pre-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.pre.js --js-library ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.lib.js --js-library ${NATIVE_BIN_DIR}/src/pal_random.lib.js --js-library ${NATIVE_BIN_DIR}/src/pal_crypto_webworker.js --post-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.post.js --extern-post-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.extpost.js " + LINK_DEPENDS "${NATIVE_BIN_DIR}/src/emcc-default.rsp;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.pre.js;${NATIVE_BIN_DIR}/src/cjs/runtime.cjs.iffe.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.lib.js;${NATIVE_BIN_DIR}/src/pal_random.lib.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.post.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.extpost.js;" + LINK_FLAGS "@${NATIVE_BIN_DIR}/src/emcc-default.rsp @${NATIVE_BIN_DIR}/src/emcc-link.rsp ${CONFIGURATION_LINK_FLAGS} --extern-pre-js ${NATIVE_BIN_DIR}/src/cjs/runtime.cjs.iffe.js --pre-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.pre.js --js-library ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.lib.js --js-library ${NATIVE_BIN_DIR}/src/pal_random.lib.js --post-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.post.js --extern-post-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.extpost.js " RUNTIME_OUTPUT_DIRECTORY "${NATIVE_BIN_DIR}") if(CMAKE_BUILD_TYPE STREQUAL "Release") diff --git a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js index de07811bdd621..f76b2c3a4febd 100644 --- a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js +++ b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js @@ -66,6 +66,10 @@ const linked_functions = [ // pal_icushim_static.c "mono_wasm_load_icu_data", "mono_wasm_get_icudt_name", + + // pal_crypto_webworker.c + "dotnet_browser_simple_digest_hash", + "dotnet_browser_can_use_simple_digest_hash", ]; // -- this javascript file is evaluated by emcc during compilation! -- diff --git a/src/mono/wasm/runtime/library-channel.ts b/src/mono/wasm/runtime/crypto-worker.ts similarity index 75% rename from src/mono/wasm/runtime/library-channel.ts rename to src/mono/wasm/runtime/crypto-worker.ts index 87452e46d1c85..8968457936e6f 100644 --- a/src/mono/wasm/runtime/library-channel.ts +++ b/src/mono/wasm/runtime/crypto-worker.ts @@ -1,13 +1,67 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -export class LibraryChannel { +import { Module } from "./imports"; +import { assert } from "./types"; + +let mono_wasm_crypto: { + channel: LibraryChannel + worker: Worker +} | null = null; + +export function dotnet_browser_can_use_simple_digest_hash(): number { + return mono_wasm_crypto === null ? 0 : 1; +} + +export function dotnet_browser_simple_digest_hash(ver: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number { + 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; + } + + Module.HEAPU8.set(digest, output_buffer); + return 1; +} + +export function init_crypto(): void { + console.debug("MONO_WASM: Initialize Crypto WebWorker"); + if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined" + && typeof SharedArrayBuffer !== "undefined" + && typeof Worker !== "undefined" + ) { + const chan = LibraryChannel.create(1024); // 1024 is the buffer size in char units. + const worker = new Worker("dotnet-crypto-worker.js"); + if (chan && worker) { + mono_wasm_crypto = { + channel: chan, + worker: worker, + }; + worker.postMessage({ + comm_buf: chan.get_comm_buffer(), + msg_buf: chan.get_msg_buffer(), + msg_char_len: chan.get_msg_len() + }); + } + } +} + +class LibraryChannel { private msg_char_len: number; private comm_buf: SharedArrayBuffer; private msg_buf: SharedArrayBuffer; private comm: Int32Array; private msg: Uint16Array; - + // Index constants for the communication buffer. private get STATE_IDX(): number { return 0; } private get MSG_SIZE_IDX(): number { return 1; } @@ -67,7 +121,7 @@ export class LibraryChannel { const msg_len = msg.length; let msg_written = 0; - for (;;) { + for (; ;) { // Write the message and return how much was written. const wrote = this.write_to_msg(msg, msg_written, msg_len); msg_written += wrote; @@ -108,7 +162,7 @@ export class LibraryChannel { private read_response(): string { let state; let response = ""; - for (;;) { + for (; ;) { // Wait for webworker response. // - Atomics.wait() is not permissible on the main thread. do { diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index a110268beeb52..141412656a64e 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -103,6 +103,10 @@ const linked_functions = [ // pal_icushim_static.c "mono_wasm_load_icu_data", "mono_wasm_get_icudt_name", + + // pal_crypto_webworker.c + "dotnet_browser_simple_digest_hash", + "dotnet_browser_can_use_simple_digest_hash", ]; // -- this javascript file is evaluated by emcc during compilation! -- diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts index 064ddcfa21478..a6bfc3421642e 100644 --- a/src/mono/wasm/runtime/exports.ts +++ b/src/mono/wasm/runtime/exports.ts @@ -67,6 +67,7 @@ import { create_weak_ref } from "./weak-ref"; import { fetch_like, readAsync_like } from "./polyfills"; import { EmscriptenModule } from "./types/emscripten"; import { mono_run_main, mono_run_main_and_exit } from "./run"; +import { dotnet_browser_can_use_simple_digest_hash, dotnet_browser_simple_digest_hash } from "./crypto-worker"; const MONO = { // current "public" MONO API @@ -351,6 +352,10 @@ export const __linker_exports: any = { // also keep in sync with pal_icushim_static.c mono_wasm_load_icu_data, mono_wasm_get_icudt_name, + + // pal_crypto_webworker.c + dotnet_browser_simple_digest_hash, + dotnet_browser_can_use_simple_digest_hash, }; const INTERNAL: any = { diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 76a9d8875d835..c31952a0aafdd 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { AllAssetEntryTypes, assert, AssetEntry, CharPtrNull, DotnetModule, GlobalizationMode, MonoConfig, MonoConfigError, wasm_type_symbol, MonoObject } from "./types"; -import { ENVIRONMENT_IS_ESM, ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, INTERNAL, locateFile, Module, MONO, requirePromise, runtimeHelpers } from "./imports"; +import { ENVIRONMENT_IS_ESM, ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, INTERNAL, locateFile, Module, MONO, requirePromise, runtimeHelpers } from "./imports"; import cwraps from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; import { mono_wasm_globalization_init, mono_wasm_load_icu_data } from "./icu"; @@ -15,7 +15,7 @@ import { VoidPtr, CharPtr } from "./types/emscripten"; import { DotnetPublicAPI } from "./exports"; import { mono_on_abort } from "./run"; import { mono_wasm_new_root } from "./roots"; -import { LibraryChannel } from "./library-channel"; +import { init_crypto } from "./crypto-worker"; export let runtime_is_initialized_resolve: Function; export let runtime_is_initialized_reject: Function; @@ -50,22 +50,6 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: } } - console.debug ("MONO_WASM: Initialize WebWorkers"); - // Consider the can_call_digest check in the pal_crypto_webworker when updating this check. - if (ENVIRONMENT_IS_WEB && typeof crypto.subtle === "undefined" && typeof SharedArrayBuffer !== "undefined") { - const chan = LibraryChannel.create(1024); // 1024 is the buffer size in char units. - const worker = new Worker("dotnet-crypto-worker.js"); - (globalThis as any).mono_wasm_crypto = { - channel: chan, - worker: worker, - }; - worker.postMessage({ - comm_buf: chan.get_comm_buffer(), - msg_buf: chan.get_msg_buffer(), - msg_char_len: chan.get_msg_len() - }); - } - // these could be overriden on DotnetModuleConfig if (!module.preInit) { module.preInit = []; @@ -136,6 +120,8 @@ async function mono_wasm_pre_init(): Promise { await requirePromise; } + init_crypto(); + if (moduleExt.configSrc) { try { // sets MONO.config implicitly diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 388cd51552a35..267bf886396a6 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -227,8 +227,7 @@ runtime/pinvoke.c; runtime/corebindings.c; runtime/dotnet-crypto-worker.js; - $(SharedNativeRoot)libs\System.Native\pal_random.lib.js; - $(SystemCryptoNativeDir)\pal_crypto_webworker.js;" + $(SharedNativeRoot)libs\System.Native\pal_random.lib.js;" DestinationFolder="$(NativeBinDir)src" SkipUnchangedFiles="true" /> @@ -274,9 +273,8 @@ $(NativeBinDir)dotnet.d.ts; $(NativeBinDir)package.json; $(NativeBinDir)dotnet.wasm; - $(NativeBinDir)dotnet.timezones.blat; $(NativeBinDir)\src\dotnet-crypto-worker.js; - $(NativeBinDir)\src\pal_crypto_webworker.js;" + $(NativeBinDir)dotnet.timezones.blat" DestinationFolder="$(MicrosoftNetCoreAppRuntimePackNativeDir)" SkipUnchangedFiles="true" /> diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js deleted file mode 100644 index 8df0f373658eb..0000000000000 --- a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.js +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -const CryptoWebWorkerLib = { - $CRYPTOWEBWORKER: { - call_digest: function (hash, input_buffer, input_len, output_buffer, output_len) { - if (this.can_call_digest() !== 1) { - return 0; // Not supported. Caller should have validated this first. - } - - var msg = { - func: "digest", - type: hash, - data: Array.from(Module.HEAPU8.subarray (input_buffer, input_buffer + input_len)) - }; - - var response = globalThis.mono_wasm_crypto.channel.send_msg (JSON.stringify (msg)); - var 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; - } - - Module.HEAPU8.set (digest, output_buffer); - return 1; - }, - can_call_digest: function () { - if (typeof globalThis.mono_wasm_crypto === "undefined" || - typeof globalThis.mono_wasm_crypto.channel === "undefined" || - typeof globalThis.mono_wasm_crypto.worker === "undefined") { - return 0; - } - - return 1; - } - }, - dotnet_browser_simple_digest_hash: function (hash, input_buffer, input_len, output_buffer, output_len) { - return CRYPTOWEBWORKER.call_digest (hash, input_buffer, input_len, output_buffer, output_len); - }, - dotnet_browser_can_use_simple_digest_hash: function () { - return CRYPTOWEBWORKER.can_call_digest (); - }, -}; - -autoAddDeps(CryptoWebWorkerLib, '$CRYPTOWEBWORKER') -mergeInto(LibraryManager.library, CryptoWebWorkerLib) From 3f14a3ac93a3eee7971b9b134cdb153fbb442fe4 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Tue, 10 May 2022 18:24:51 -0700 Subject: [PATCH 13/19] Add code to handle errors in worker (particularly on init) --- src/mono/wasm/runtime/crypto-worker.ts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/mono/wasm/runtime/crypto-worker.ts b/src/mono/wasm/runtime/crypto-worker.ts index 8968457936e6f..4690c228e1ebf 100644 --- a/src/mono/wasm/runtime/crypto-worker.ts +++ b/src/mono/wasm/runtime/crypto-worker.ts @@ -41,17 +41,19 @@ export function init_crypto(): void { ) { const chan = LibraryChannel.create(1024); // 1024 is the buffer size in char units. const worker = new Worker("dotnet-crypto-worker.js"); - if (chan && worker) { - mono_wasm_crypto = { - channel: chan, - worker: worker, - }; - worker.postMessage({ - comm_buf: chan.get_comm_buffer(), - msg_buf: chan.get_msg_buffer(), - msg_char_len: chan.get_msg_len() - }); - } + mono_wasm_crypto = { + channel: chan, + worker: worker, + }; + worker.postMessage({ + comm_buf: chan.get_comm_buffer(), + msg_buf: chan.get_msg_buffer(), + msg_char_len: chan.get_msg_len() + }); + worker.onerror = event => { + console.warn(`MONO_WASM: Error in Crypto WebWorker. Cryptography digest calls will fallback to managed implementation. Error: ${event.message}`); + mono_wasm_crypto = null; + }; } } From 0f11bcaeedb5d20e9222e67bc032ab7d370944e8 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Wed, 11 May 2022 17:12:59 -0700 Subject: [PATCH 14/19] Clean up --- src/mono/wasm/runtime/dotnet-crypto-worker.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet-crypto-worker.js b/src/mono/wasm/runtime/dotnet-crypto-worker.js index 5bd462d9228b2..9511244db5074 100644 --- a/src/mono/wasm/runtime/dotnet-crypto-worker.js +++ b/src/mono/wasm/runtime/dotnet-crypto-worker.js @@ -25,9 +25,6 @@ var ChannelWorker = { async await_request(async_call) { for (;;) { - // eslint-disable-next-line no-debugger - debugger; - // Wait for signal to perform operation Atomics.wait(this.comm, this.STATE_IDX, this.STATE_IDLE); From 7d964be11f579a493e0db27a16e84be5230efb29 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Thu, 12 May 2022 14:03:30 -0700 Subject: [PATCH 15/19] Add crypto dll to wasm native project --- src/mono/wasm/build/WasmApp.Native.targets | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index b92320d735bef..a224ecb9b0fef 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -271,6 +271,7 @@ <_WasmPInvokeModules Include="libSystem.Native" /> <_WasmPInvokeModules Include="libSystem.IO.Compression.Native" /> <_WasmPInvokeModules Include="libSystem.Globalization.Native" /> + <_WasmPInvokeModules Include="libSystem.Security.Cryptography.Native.Browser" /> Date: Fri, 13 May 2022 18:19:20 -0700 Subject: [PATCH 16/19] Add e2e test --- src/mono/wasm/runtime/crypto-worker.ts | 3 +- .../Wasm.Build.Tests/NativeLibraryTests.cs | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/crypto-worker.ts b/src/mono/wasm/runtime/crypto-worker.ts index 4690c228e1ebf..417c89427ef53 100644 --- a/src/mono/wasm/runtime/crypto-worker.ts +++ b/src/mono/wasm/runtime/crypto-worker.ts @@ -34,11 +34,12 @@ export function dotnet_browser_simple_digest_hash(ver: number, input_buffer: num } export function init_crypto(): void { - console.debug("MONO_WASM: Initialize Crypto WebWorker"); if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined" && typeof SharedArrayBuffer !== "undefined" && typeof Worker !== "undefined" ) { + console.debug("MONO_WASM: Initialize Crypto WebWorker"); + const chan = LibraryChannel.create(1024); // 1024 is the buffer size in char units. const worker = new Worker("dotnet-crypto-worker.js"); mono_wasm_crypto = { diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs index e8892a9575d01..b3f0f41ad7b5b 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs @@ -93,5 +93,50 @@ public static int Main() Assert.Contains("Size: 26462 Height: 599, Width: 499", output); } + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [BuildAndRun(aot: false)] + [BuildAndRun(aot: true)] + public void ProjectUsingBrowserNativeCrypto(BuildArgs buildArgs, RunHost host, string id) + { + string projectName = $"AppUsingBrowserNativeCrypto"; + buildArgs = buildArgs with { ProjectName = projectName }; + buildArgs = ExpandBuildArgs(buildArgs); + + string programText = @" +using System.Security.Cryptography; + +public class Test +{ + public static void Main() + { + using (SHA256 mySHA256 = SHA256.Create()) + { + byte[] data = { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' }; + mySHA256.ComputeHash(data); + } + } +}"; + + BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), + DotnetWasmFromRuntimePack: !buildArgs.AOT && buildArgs.Config != "Release")); + + string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 0, + test: output => {}, + host: host, id: id); + + string cryptoInitializeSentinel = "MONO_WASM: Initialize Crypto WebWorker"; + if (host == RunHost.V8 || host == RunHost.NodeJS) + { + Assert.DoesNotContain(cryptoInitializeSentinel, output); + } + else + { + Assert.Contains(cryptoInitializeSentinel, output); + } + } } } From e7f89f57fc1d654db2f1910a9405c58f6e5e6d03 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Mon, 16 May 2022 18:08:48 -0700 Subject: [PATCH 17/19] Adjust test to reflect lack of SharedArrayBuffer for Chrome in test harness --- src/mono/wasm/runtime/crypto-worker.ts | 2 +- .../Wasm.Build.Tests/BuildTestBase.cs | 2 +- .../Wasm.Build.Tests/NativeLibraryTests.cs | 15 +++++++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/mono/wasm/runtime/crypto-worker.ts b/src/mono/wasm/runtime/crypto-worker.ts index 417c89427ef53..66e749c3e0fc9 100644 --- a/src/mono/wasm/runtime/crypto-worker.ts +++ b/src/mono/wasm/runtime/crypto-worker.ts @@ -38,7 +38,7 @@ export function init_crypto(): void { && typeof SharedArrayBuffer !== "undefined" && typeof Worker !== "undefined" ) { - console.debug("MONO_WASM: Initialize Crypto WebWorker"); + console.debug("MONO_WASM: Initializing Crypto WebWorker"); const chan = LibraryChannel.create(1024); // 1024 is the buffer size in char units. const worker = new Worker("dotnet-crypto-worker.js"); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs index aa8070ed0f59b..e6aa040076583 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs @@ -156,7 +156,7 @@ protected string RunAndTestWasmApp(BuildArgs buildArgs, { RunHost.V8 => ("wasm test", "--js-file=test-main.js --engine=V8 -v trace"), RunHost.NodeJS => ("wasm test", "--js-file=test-main.js --engine=NodeJS -v trace"), - _ => ("wasm test-browser", $"-v trace -b {host}") + _ => ("wasm test-browser", $"-v trace -b {host} --browser-arg=--enable-features=SharedArrayBuffer") }; string testLogPath = Path.Combine(_logPath, host.ToString()); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs index b3f0f41ad7b5b..3ff26be2e2e07 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs @@ -99,6 +99,12 @@ public static int Main() [BuildAndRun(aot: true)] public void ProjectUsingBrowserNativeCrypto(BuildArgs buildArgs, RunHost host, string id) { + if (host == RunHost.Chrome) + { + // SharedArrayBuffer not available in Chrome under this test harness. + return; + } + string projectName = $"AppUsingBrowserNativeCrypto"; buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs); @@ -108,12 +114,13 @@ public void ProjectUsingBrowserNativeCrypto(BuildArgs buildArgs, RunHost host, s public class Test { - public static void Main() + public static int Main() { using (SHA256 mySHA256 = SHA256.Create()) { byte[] data = { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' }; mySHA256.ComputeHash(data); + return 0; } } }"; @@ -128,14 +135,14 @@ public static void Main() test: output => {}, host: host, id: id); - string cryptoInitializeSentinel = "MONO_WASM: Initialize Crypto WebWorker"; + string cryptoInitMsg = "MONO_WASM: Initializing Crypto WebWorker"; if (host == RunHost.V8 || host == RunHost.NodeJS) { - Assert.DoesNotContain(cryptoInitializeSentinel, output); + Assert.DoesNotContain(cryptoInitMsg, output); } else { - Assert.Contains(cryptoInitializeSentinel, output); + Assert.Contains(cryptoInitMsg, output); } } } From a22037bff219f4685098dc92abcbda700a35e8b6 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Tue, 17 May 2022 10:22:06 -0700 Subject: [PATCH 18/19] Enable Chrome test and validate hashed value in tests --- src/mono/wasm/runtime/dotnet-crypto-worker.js | 1 - .../Wasm.Build.Tests/BuildTestBase.cs | 2 +- .../Wasm.Build.Tests/NativeLibraryTests.cs | 15 ++++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet-crypto-worker.js b/src/mono/wasm/runtime/dotnet-crypto-worker.js index 9511244db5074..c6416492a7157 100644 --- a/src/mono/wasm/runtime/dotnet-crypto-worker.js +++ b/src/mono/wasm/runtime/dotnet-crypto-worker.js @@ -30,7 +30,6 @@ var ChannelWorker = { // Read in request var req = this._read_request(); - // console.log("Request: " + req); if (req === this.STATE_SHUTDOWN) break; diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs index e6aa040076583..ccd314464587c 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs @@ -156,7 +156,7 @@ protected string RunAndTestWasmApp(BuildArgs buildArgs, { RunHost.V8 => ("wasm test", "--js-file=test-main.js --engine=V8 -v trace"), RunHost.NodeJS => ("wasm test", "--js-file=test-main.js --engine=NodeJS -v trace"), - _ => ("wasm test-browser", $"-v trace -b {host} --browser-arg=--enable-features=SharedArrayBuffer") + _ => ("wasm test-browser", $"-v trace -b {host} --web-server-use-cop") }; string testLogPath = Path.Combine(_logPath, host.ToString()); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs index 3ff26be2e2e07..29cc4186a0cba 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs @@ -99,17 +99,12 @@ public static int Main() [BuildAndRun(aot: true)] public void ProjectUsingBrowserNativeCrypto(BuildArgs buildArgs, RunHost host, string id) { - if (host == RunHost.Chrome) - { - // SharedArrayBuffer not available in Chrome under this test harness. - return; - } - string projectName = $"AppUsingBrowserNativeCrypto"; buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs); string programText = @" +using System; using System.Security.Cryptography; public class Test @@ -119,7 +114,9 @@ public static int Main() using (SHA256 mySHA256 = SHA256.Create()) { byte[] data = { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' }; - mySHA256.ComputeHash(data); + byte[] hashed = mySHA256.ComputeHash(data); + string asStr = string.Join(' ', hashed); + Console.WriteLine(""Hashed: "" + asStr); return 0; } } @@ -135,6 +132,10 @@ public static int Main() test: output => {}, host: host, id: id); + Assert.Contains( + "Hashed: 24 95 141 179 34 113 254 37 245 97 166 252 147 139 46 38 67 6 236 48 78 218 81 128 7 209 118 72 38 56 25 105", + output); + string cryptoInitMsg = "MONO_WASM: Initializing Crypto WebWorker"; if (host == RunHost.V8 || host == RunHost.NodeJS) { From c2ec92f1545ccb2de38b0805f2eb3088c1dc2305 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 24 May 2022 18:20:11 +0000 Subject: [PATCH 19/19] fix merge to track assert being renamed to mono_assert --- src/mono/wasm/runtime/crypto-worker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/runtime/crypto-worker.ts b/src/mono/wasm/runtime/crypto-worker.ts index 66e749c3e0fc9..ea7bd9e6fce5a 100644 --- a/src/mono/wasm/runtime/crypto-worker.ts +++ b/src/mono/wasm/runtime/crypto-worker.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { Module } from "./imports"; -import { assert } from "./types"; +import { mono_assert } from "./types"; let mono_wasm_crypto: { channel: LibraryChannel @@ -14,7 +14,7 @@ export function dotnet_browser_can_use_simple_digest_hash(): number { } export function dotnet_browser_simple_digest_hash(ver: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number { - assert(!!mono_wasm_crypto, "subtle crypto not initialized"); + mono_assert(!!mono_wasm_crypto, "subtle crypto not initialized"); const msg = { func: "digest",