Skip to content

Commit f1537d7

Browse files
committed
Add support for crypto with emscripten by fetching mbedtls
Also run allow stdlib tests to be executed with emscripten NodeJS build by disabling networking tests. Signed-off-by: Paul Guyot <pguyot@kallisys.net>
1 parent c60d126 commit f1537d7

File tree

8 files changed

+184
-14
lines changed

8 files changed

+184
-14
lines changed

.github/workflows/wasm-build.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
cd build
4848
cmake .. -G Ninja -DAVM_WARNINGS_ARE_ERRORS=ON
4949
# test_eavmlib does not work with wasm due to http + ssl test
50-
ninja AtomVM atomvmlib test_etest test_alisp hello_world run_script call_cast html5_events wasm_webserver
50+
ninja AtomVM atomvmlib erlang_test_modules test_etest test_alisp test_estdlib hello_world run_script call_cast html5_events wasm_webserver
5151
5252
- name: Upload AtomVM and test modules
5353
uses: actions/upload-artifact@v4
@@ -112,9 +112,11 @@ jobs:
112112
node src/AtomVM.js ../../../../build/examples/erlang/hello_world.beam ../../../../build/libs/eavmlib/src/eavmlib.avm
113113
# Run tests that pass
114114
node src/AtomVM.js ../../../../build/tests/libs/alisp/test_alisp.avm
115+
node src/AtomVM.js ../../../../build/tests/libs/estdlib/test_estdlib.avm
115116
# test_eavmlib does not work with wasm due to http + ssl test
116117
# node src/AtomVM.js ../../../../build/tests/libs/eavmlib/test_eavmlib.avm
117118
node src/AtomVM.js ../../../../build/tests/libs/etest/test_etest.avm
119+
node src/AtomVM.js ../../../../build/tests/erlang_tests/test_crypto.beam
118120
119121
- name: "Rename and write sha256sum (node)"
120122
if: startsWith(github.ref, 'refs/tags/')

CMakeModules/FetchMbedTLS.cmake

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#
2+
# This file is part of AtomVM.
3+
#
4+
# Copyright 2025 Paul Guyot <pguyot@kallisys.net>
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
#
20+
21+
include(FetchContent)
22+
23+
FetchContent_Declare(
24+
mbedtls
25+
GIT_REPOSITORY http://github.com/mbed-TLS/mbedtls.git
26+
GIT_TAG v3.6.2
27+
GIT_SHALLOW 1
28+
)
29+
30+
FetchContent_MakeAvailable(mbedtls)

src/platforms/emscripten/src/lib/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,19 @@
2020

2121
cmake_minimum_required (VERSION 3.13)
2222
project (libAtomVMPlatformEmscripten)
23+
include(FetchMbedTLS)
2324

2425
set(HEADER_FILES
2526
emscripten_sys.h
27+
"../../../../libAtomVM/otp_crypto.h"
2628
)
2729

2830
set(SOURCE_FILES
2931
platform_defaultatoms.c
3032
platform_nifs.c
3133
smp.c
3234
sys.c
35+
"../../../../libAtomVM/otp_crypto.c"
3336
)
3437

3538
set(
@@ -41,3 +44,4 @@ add_library(libAtomVM${PLATFORM_LIB_SUFFIX} ${SOURCE_FILES} ${HEADER_FILES})
4144
target_compile_features(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC c_std_11)
4245

4346
target_link_libraries(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC libAtomVM)
47+
target_link_libraries(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC MbedTLS::mbedcrypto)

src/platforms/emscripten/src/lib/emscripten_sys.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@
3333
#include <emscripten/fetch.h>
3434
#include <emscripten/promise.h>
3535

36+
#include <mbedtls/ctr_drbg.h>
37+
#include <mbedtls/entropy.h>
38+
39+
#if defined(MBEDTLS_VERSION_NUMBER) && (MBEDTLS_VERSION_NUMBER >= 0x03000000)
40+
#include <mbedtls/build_info.h>
41+
#else
42+
#include <mbedtls/config.h>
43+
#endif
44+
45+
#include "sys_mbedtls.h"
46+
3647
struct PromiseResource
3748
{
3849
em_promise_t promise;
@@ -104,6 +115,18 @@ struct EmscriptenPlatformData
104115
struct ListHead messages;
105116
ErlNifResourceType *promise_resource_type;
106117
ErlNifResourceType *htmlevent_user_data_resource_type;
118+
119+
#ifndef AVM_NO_SMP
120+
Mutex *entropy_mutex;
121+
#endif
122+
mbedtls_entropy_context entropy_ctx;
123+
bool entropy_is_initialized;
124+
125+
#ifndef AVM_NO_SMP
126+
Mutex *random_mutex;
127+
#endif
128+
mbedtls_ctr_drbg_context random_ctx;
129+
bool random_is_initialized;
107130
};
108131

109132
void sys_enqueue_emscripten_cast_message(GlobalContext *glb, const char *target, const char *message);

src/platforms/emscripten/src/lib/platform_nifs.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <interop.h>
2626
#include <memory.h>
2727
#include <nifs.h>
28+
#include <otp_crypto.h>
2829
#include <term.h>
2930
#include <term_typedef.h>
3031

@@ -771,6 +772,9 @@ const struct Nif *platform_nifs_get_nif(const char *nifname)
771772
if (strcmp("atomvm:random/0", nifname) == 0) {
772773
return &atomvm_random_nif;
773774
}
775+
if (memcmp("crypto:", nifname, strlen("crypro:")) == 0) {
776+
return otp_crypto_nif_get_nif(nifname);
777+
}
774778
if (memcmp("emscripten:", nifname, strlen("emscripten:"))) {
775779
return NULL;
776780
}

src/platforms/emscripten/src/lib/sys.c

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,20 @@ void sys_init_platform(GlobalContext *glb)
169169
fprintf(stderr, "Cannot initialize promise_resource_type");
170170
AVM_ABORT();
171171
}
172+
173+
#ifndef AVM_NO_SMP
174+
platform->entropy_mutex = smp_mutex_create();
175+
if (IS_NULL_PTR(platform->entropy_mutex)) {
176+
AVM_ABORT();
177+
}
178+
platform->random_mutex = smp_mutex_create();
179+
if (IS_NULL_PTR(platform->random_mutex)) {
180+
AVM_ABORT();
181+
}
182+
#endif
183+
platform->entropy_is_initialized = false;
184+
platform->random_is_initialized = false;
185+
172186
glb->platform_data = platform;
173187
}
174188

@@ -177,6 +191,12 @@ void sys_free_platform(GlobalContext *glb)
177191
struct EmscriptenPlatformData *platform = glb->platform_data;
178192
pthread_cond_destroy(&platform->poll_cond);
179193
pthread_mutex_destroy(&platform->poll_mutex);
194+
if (platform->random_is_initialized) {
195+
mbedtls_ctr_drbg_free(&platform->random_ctx);
196+
}
197+
if (platform->entropy_is_initialized) {
198+
mbedtls_entropy_free(&platform->entropy_ctx);
199+
}
180200
free(platform);
181201
}
182202

@@ -730,3 +750,70 @@ term sys_get_info(Context *ctx, term key)
730750
UNUSED(key);
731751
return UNDEFINED_ATOM;
732752
}
753+
754+
int sys_mbedtls_entropy_func(void *entropy, unsigned char *buf, size_t size)
755+
{
756+
#ifndef MBEDTLS_THREADING_C
757+
struct EmscriptenPlatformData *platform
758+
= CONTAINER_OF(entropy, struct EmscriptenPlatformData, entropy_ctx);
759+
SMP_MUTEX_LOCK(platform->entropy_mutex);
760+
int result = mbedtls_entropy_func(entropy, buf, size);
761+
SMP_MUTEX_UNLOCK(platform->entropy_mutex);
762+
763+
return result;
764+
#else
765+
return mbedtls_entropy_func(entropy, buf, size);
766+
#endif
767+
}
768+
769+
mbedtls_entropy_context *sys_mbedtls_get_entropy_context_lock(GlobalContext *global)
770+
{
771+
struct EmscriptenPlatformData *platform = global->platform_data;
772+
773+
SMP_MUTEX_LOCK(platform->entropy_mutex);
774+
775+
if (!platform->entropy_is_initialized) {
776+
mbedtls_entropy_init(&platform->entropy_ctx);
777+
platform->entropy_is_initialized = true;
778+
}
779+
780+
return &platform->entropy_ctx;
781+
}
782+
783+
void sys_mbedtls_entropy_context_unlock(GlobalContext *global)
784+
{
785+
struct EmscriptenPlatformData *platform = global->platform_data;
786+
SMP_MUTEX_UNLOCK(platform->entropy_mutex);
787+
}
788+
789+
mbedtls_ctr_drbg_context *sys_mbedtls_get_ctr_drbg_context_lock(GlobalContext *global)
790+
{
791+
struct EmscriptenPlatformData *platform = global->platform_data;
792+
793+
SMP_MUTEX_LOCK(platform->random_mutex);
794+
795+
if (!platform->random_is_initialized) {
796+
mbedtls_ctr_drbg_init(&platform->random_ctx);
797+
798+
mbedtls_entropy_context *entropy_ctx = sys_mbedtls_get_entropy_context_lock(global);
799+
// Safe to unlock it now, sys_mbedtls_entropy_func will lock it again later
800+
sys_mbedtls_entropy_context_unlock(global);
801+
802+
const char *seed = "AtomVM Mbed-TLS initial seed.";
803+
int seed_len = strlen(seed);
804+
int seed_err = mbedtls_ctr_drbg_seed(&platform->random_ctx, sys_mbedtls_entropy_func,
805+
entropy_ctx, (const unsigned char *) seed, seed_len);
806+
if (UNLIKELY(seed_err != 0)) {
807+
abort();
808+
}
809+
platform->random_is_initialized = true;
810+
}
811+
812+
return &platform->random_ctx;
813+
}
814+
815+
void sys_mbedtls_ctr_drbg_context_unlock(GlobalContext *global)
816+
{
817+
struct EmscriptenPlatformData *platform = global->platform_data;
818+
SMP_MUTEX_UNLOCK(platform->random_mutex);
819+
}

src/platforms/emscripten/src/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ static int start(void)
104104
fprintf(stdout, "\n");
105105

106106
int status;
107-
if (ret_value == OK_ATOM) {
107+
if (ret_value == OK_ATOM || ret_value == term_from_int(0)) {
108108
status = EXIT_SUCCESS;
109109
} else {
110110
status = EXIT_FAILURE;

tests/libs/estdlib/tests.erl

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,26 @@
2323
-export([start/0]).
2424

2525
start() ->
26-
ok = etest:test(get_tests(get_otp_version())).
26+
OTPVersion = get_otp_version(),
27+
NonNetworkingTests = get_non_networking_tests(OTPVersion),
28+
Networking =
29+
case OTPVersion of
30+
atomvm ->
31+
case atomvm:platform() of
32+
emscripten ->
33+
false;
34+
_ ->
35+
true
36+
end;
37+
_ ->
38+
true
39+
end,
40+
NetworkingTests =
41+
if
42+
Networking -> get_networking_tests(OTPVersion);
43+
true -> []
44+
end,
45+
ok = etest:test(NonNetworkingTests ++ NetworkingTests).
2746

2847
get_otp_version() ->
2948
case erlang:system_info(machine) of
@@ -33,25 +52,19 @@ get_otp_version() ->
3352
atomvm
3453
end.
3554

36-
get_tests(OTPVersion) when
37-
(is_integer(OTPVersion) andalso OTPVersion >= 27) orelse OTPVersion == atomvm
55+
% test_sets heavily relies on is_equal that is from OTP-27
56+
get_non_networking_tests(OTPVersion) when
57+
(is_integer(OTPVersion) andalso OTPVersion >= 27) orelse OTPVersion =:= atomvm
3858
->
39-
[test_tcp_socket, test_udp_socket, test_net, test_ssl, test_sets | get_tests(undefined)];
40-
get_tests(OTPVersion) when
41-
(is_integer(OTPVersion) andalso OTPVersion >= 24)
42-
->
43-
% test_sets heavily relies on is_equal that is from OTP-27
44-
[test_tcp_socket, test_udp_socket, test_net, test_ssl | get_tests(undefined)];
45-
get_tests(_OTPVersion) ->
59+
[test_sets | get_non_networking_tests(undefined)];
60+
get_non_networking_tests(_OTPVersion) ->
4661
[
4762
test_apply,
4863
test_lists,
4964
test_calendar,
5065
test_gen_event,
5166
test_gen_server,
5267
test_gen_statem,
53-
test_gen_udp,
54-
test_gen_tcp,
5568
test_io_lib,
5669
test_logger,
5770
test_maps,
@@ -62,3 +75,10 @@ get_tests(_OTPVersion) ->
6275
test_supervisor,
6376
test_lists_subtraction
6477
].
78+
79+
get_networking_tests(OTPVersion) when
80+
(is_integer(OTPVersion) andalso OTPVersion >= 24) orelse OTPVersion =:= atomvm
81+
->
82+
[test_tcp_socket, test_udp_socket, test_net, test_ssl | get_networking_tests(undefined)];
83+
get_networking_tests(_OTPVersion) ->
84+
[test_gen_udp, test_gen_tcp].

0 commit comments

Comments
 (0)