quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

commit a9d2a10f9dfc2cacf17e574928e6b851e084beef
parent 6dba524755c9b5a3dccc4372cf74cf1ed6d46586
Author: Florian Dold <florian@dold.me>
Date:   Mon,  2 Jan 2023 22:51:14 +0100

wip

Diffstat:
M.gitignore | 3+++
Mmeson.build | 29+++++++++++++++++++++++++----
Mprelude.js | 4++++
Mqtart.c | 16++++++++++++++++
Mquickjs/quickjs-libc.c | 170++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mtaler_wallet_core_lib.c | 120++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mtaler_wallet_core_lib.h | 3+++
Mtart_module.c | 11++++++-----
Mwallet-client-example.c | 4+++-
9 files changed, 300 insertions(+), 60 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,2 +1,5 @@ tags .vscode + +# compiled wallet core file +taler-wallet-core-qjs.mjs diff --git a/meson.build b/meson.build @@ -56,16 +56,34 @@ qjsc_exe = executable('qjsc', [ curl_dep, sodium_dep]) -repl_c = custom_target('repl', +repl_c = custom_target('repl_c', input : ['quickjs/repl.js'], output : ['repl.c'], command : [qjsc_exe, '-c', '-m', '-o', '@OUTPUT@', '@INPUT@']) +prelude_c = custom_target('prelude_c', + input : ['prelude.js'], + output : ['prelude.c'], + command : [qjsc_exe, '-c', '-m', '-M', 'tart', + '-o', '@OUTPUT@', + '@INPUT@']) + +wallet_core_c = custom_target('wallet_core_c', + input : ['taler-wallet-core-qjs.mjs'], + output : ['wallet_core.c'], + command : [qjsc_exe, '-c', '-m', '-M', 'tart', '-N', 'qjsc_wallet_core', + '-o', '@OUTPUT@', + '@INPUT@']) + repl = static_library('repl', repl_c) +prelude = static_library('prelude', prelude_c) +wallet_core = static_library('wallet_core', wallet_core_c) -tart = static_library('tart', 'tart_module.c') +tart = static_library('tart', 'tart_module.c', dependencies : [ + mbedcrypto_dep, + ]) qtart_exe = executable('qtart', [ 'qtart.c', @@ -78,6 +96,8 @@ qtart_exe = executable('qtart', [ quickjs_libc, quickjs, repl, + wallet_core, + prelude, tart, ], dependencies: [ @@ -97,6 +117,8 @@ talerwalletcore_lib = shared_library('talerwalletcore', 'taler_wallet_core_lib.c quickjs_libc, quickjs, tart, + wallet_core, + prelude, ], dependencies: [ m_dep, @@ -109,4 +131,4 @@ talerwalletcore_lib = shared_library('talerwalletcore', 'taler_wallet_core_lib.c wallet_client_example_exe = executable('wallet-client-example', 'wallet-client-example.c', - link_with : [talerwalletcore_lib]) -\ No newline at end of file + link_with : [talerwalletcore_lib]) diff --git a/prelude.js b/prelude.js @@ -1,3 +1,7 @@ +// The prelude defines basic functionality +// that is expected by the Taler wallet core JavaScript, +// but not provided by quickjs or the "tart" module directly. + import * as os from "os"; import * as tart from "tart"; diff --git a/qtart.c b/qtart.c @@ -46,6 +46,12 @@ extern const uint8_t qjsc_repl[]; extern const uint32_t qjsc_repl_size; +extern const uint8_t qjsc_prelude[]; +extern const uint32_t qjsc_prelude_size; + +extern const uint8_t qjsc_wallet_core[]; +extern const uint32_t qjsc_wallet_core_size; + static int eval_buf(JSContext *ctx, const void *buf, int buf_len, const char *filename, int eval_flags) { @@ -294,6 +300,12 @@ void help(void) exit(1); } +void +handle_host_message(void *cls, const char *msg) +{ + printf("message from JS to host: %s\n", msg); +} + int main(int argc, char **argv) { JSRuntime *rt; @@ -436,6 +448,7 @@ int main(int argc, char **argv) js_std_set_worker_new_context_func(JS_NewCustomContext); js_std_init_handlers(rt); ctx = JS_NewCustomContext(rt); + js_os_set_host_message_handler(ctx, handle_host_message, NULL); if (!ctx) { fprintf(stderr, "qjs: cannot allocate JS context\n"); exit(2); @@ -461,6 +474,9 @@ int main(int argc, char **argv) eval_buf(ctx, str, strlen(str), "<input>", JS_EVAL_TYPE_MODULE); } + js_std_eval_binary(ctx, qjsc_prelude, qjsc_prelude_size, 0); + js_std_eval_binary(ctx, qjsc_wallet_core, qjsc_wallet_core_size, 0); + for(i = 0; i < include_count; i++) { if (eval_file(ctx, include_list[i], module)) goto fail; diff --git a/quickjs/quickjs-libc.c b/quickjs/quickjs-libc.c @@ -2340,8 +2340,10 @@ static void call_handler(JSContext *ctx, JSValueConst func) func1 = JS_DupValue(ctx, func); ret = JS_Call(ctx, func1, JS_UNDEFINED, 0, NULL); JS_FreeValue(ctx, func1); - if (JS_IsException(ret)) + if (JS_IsException(ret)) { + fprintf(stderr, "exception in handler\n"); js_std_dump_error(ctx); + } JS_FreeValue(ctx, ret); } @@ -2499,6 +2501,63 @@ static int handle_posted_message(JSRuntime *rt, JSContext *ctx, } #endif +/* return 1 if a message was handled, 0 if no message */ +static int handle_host_message(JSRuntime *rt, JSContext *ctx) +{ + JSThreadState *ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); + JSHostMessagePipe *hp = ts->host_pipe; + int ret; + struct list_head *el; + JSHostMessage *msg; + JSValue obj, func, retval; + + pthread_mutex_lock(&hp->mutex); + if (!list_empty(&hp->msg_queue)) { + el = hp->msg_queue.next; + msg = list_entry(el, JSHostMessage, link); + + /* remove the message from the queue */ + list_del(&msg->link); + + if (list_empty(&hp->msg_queue)) { + uint8_t buf[16]; + int ret; + for(;;) { + ret = read(hp->read_fd, buf, sizeof(buf)); + if (ret >= 0) + break; + if (errno != EAGAIN && errno != EINTR) + break; + } + } + + obj = JS_NewString(ctx, msg->msg_data); + + free(msg->msg_data); + free(msg); + + pthread_mutex_unlock(&hp->mutex); + + /* 'func' might be destroyed when calling itself (if it frees the + handler), so must take extra care */ + func = JS_DupValue(ctx, ts->on_host_message_func); + retval = JS_Call(ctx, func, JS_UNDEFINED, 1, (JSValueConst *)&obj); + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, func); + if (JS_IsException(retval)) { + fail: + js_std_dump_error(ctx); + } else { + JS_FreeValue(ctx, retval); + } + ret = 1; + } else { + pthread_mutex_unlock(&hp->mutex); + ret = 0; + } + return ret; +} + static int js_os_poll(JSContext *ctx) { JSRuntime *rt = JS_GetRuntime(ctx); @@ -2611,6 +2670,12 @@ static int js_os_poll(JSContext *ctx) } } } + + if (FD_ISSET(ts->host_pipe->read_fd, &rfds)) { + if (handle_host_message(rt, ctx)) { + goto done; + } + } } done: return 0; @@ -3474,6 +3539,27 @@ static JSWorkerMessagePipe *js_new_message_pipe(void) return ps; } +static JSHostMessagePipe *js_new_host_message_pipe(void) +{ + JSHostMessagePipe *ps; + int pipe_fds[2]; + + if (pipe(pipe_fds) < 0) + return NULL; + + ps = malloc(sizeof(*ps)); + if (!ps) { + close(pipe_fds[0]); + close(pipe_fds[1]); + return NULL; + } + init_list_head(&ps->msg_queue); + pthread_mutex_init(&ps->mutex, NULL); + ps->read_fd = pipe_fds[0]; + ps->write_fd = pipe_fds[1]; + return ps; +} + static JSWorkerMessagePipe *js_dup_message_pipe(JSWorkerMessagePipe *ps) { atomic_add_int(&ps->ref_count, 1); @@ -3515,6 +3601,34 @@ static void js_free_message_pipe(JSWorkerMessagePipe *ps) } } +static void js_free_host_message(JSHostMessage *msg) +{ + free(msg->msg_data); + free(msg); +} + +static void js_free_host_message_pipe(JSHostMessagePipe *ps) +{ + struct list_head *el, *el1; + JSHostMessage *msg; + int ref_count; + + if (!ps) + return; + + assert(ref_count >= 0); + if (ref_count == 0) { + list_for_each_safe(el, el1, &ps->msg_queue) { + msg = list_entry(el, JSHostMessage, link); + js_free_host_message(msg); + } + pthread_mutex_destroy(&ps->mutex); + close(ps->read_fd); + close(ps->write_fd); + free(ps); + } +} + static void js_free_port(JSRuntime *rt, JSWorkerMessageHandler *port) { if (port) { @@ -3874,6 +3988,23 @@ js_os_post_message_from_host(JSContext *ctx, const char *msg_str) return -1; } +static JSValue js_os_simulateHostMessage(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSThreadState *ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); + const char *s; + + s = JS_ToCString(ctx, argv[0]); + + if (!s) { + return JS_EXCEPTION; + } + + js_os_post_message_from_host(ctx, s); + + return JS_UNDEFINED; +} + void js_os_set_host_message_handler(JSContext *ctx, JSHostMessageHandlerFn f, void *cls) { JSThreadState *ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); @@ -3896,26 +4027,16 @@ static JSValue js_os_postHostMessage(JSContext *ctx, JSValueConst this_val, if (ts->host_message_handler_f) { ts->host_message_handler_f(ts->host_message_handler_cls, s); } -} -static JSValue js_os_get_onhostmessage(JSContext *ctx, JSValueConst this_val) -{ - JSThreadState *ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); - if (!ts) { - return JS_EXCEPTION; - } - if (JS_IsFunction(ctx, ts->on_host_message_func)) { - return JS_DupValue(ctx, ts->on_host_message_func); - } else { - return JS_NULL; - } + return JS_UNDEFINED; } -static JSValue js_os_set_onhostmessage(JSContext *ctx, JSValueConst this_val, - JSValueConst func) +static JSValue js_os_setMessageFromHostHandler(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) { JSRuntime *rt = JS_GetRuntime(ctx); JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSValue func = argv[0]; if (JS_IsNull(func)) { ts->on_host_message_func = JS_NULL; @@ -4028,8 +4149,9 @@ static const JSCFunctionListEntry js_os_funcs[] = { JS_CFUNC_DEF("dup", 1, js_os_dup ), JS_CFUNC_DEF("dup2", 2, js_os_dup2 ), #endif - JS_CFUNC_DEF("postHostMessage", 1, js_os_postHostMessage ), - JS_CGETSET_DEF("onhostmessage", js_os_get_onhostmessage, js_os_set_onhostmessage ), + JS_CFUNC_DEF("postMessageToHost", 1, js_os_postHostMessage ), + JS_CFUNC_DEF("simulateHostMessageFromHost", 1, js_os_simulateHostMessage ), + JS_CFUNC_DEF("setMessageFromHostHandler", 1, js_os_setMessageFromHostHandler ), }; static int js_os_init(JSContext *ctx, JSModuleDef *m) @@ -4157,6 +4279,11 @@ oom_fail: init_list_head(&ts->os_signal_handlers); init_list_head(&ts->os_timers); init_list_head(&ts->port_list); + ts->on_host_message_func = JS_NULL; + ts->host_pipe = js_new_host_message_pipe(); + if (!ts->host_pipe) { + goto oom_fail; + } JS_SetRuntimeOpaque(rt, ts); @@ -4195,12 +4322,16 @@ void js_std_free_handlers(JSRuntime *rt) free_timer(rt, th); } + JS_FreeValueRT(rt, ts->on_host_message_func); + #ifdef USE_WORKER /* XXX: free port_list ? */ js_free_message_pipe(ts->recv_pipe); js_free_message_pipe(ts->send_pipe); #endif + js_free_host_message_pipe(ts->host_pipe); + free(ts); JS_SetRuntimeOpaque(rt, NULL); /* fail safe */ } @@ -4215,6 +4346,7 @@ static void js_dump_obj(JSContext *ctx, FILE *f, JSValueConst val) JS_FreeCString(ctx, str); } else { fprintf(f, "[exception]\n"); + abort(); } } @@ -4222,9 +4354,11 @@ static void js_std_dump_error1(JSContext *ctx, JSValueConst exception_val) { JSValue val; BOOL is_error; - + is_error = JS_IsError(ctx, exception_val); + fprintf(stderr, "dumping error, is_error: %u\n", is_error); js_dump_obj(ctx, stderr, exception_val); + fprintf(stderr, "dumped value\n"); if (is_error) { val = JS_GetPropertyStr(ctx, exception_val, "stack"); if (!JS_IsUndefined(val)) { diff --git a/taler_wallet_core_lib.c b/taler_wallet_core_lib.c @@ -22,6 +22,13 @@ #include <pthread.h> #include "quickjs/list.h" #include <unistd.h> +#include <string.h> + +extern const uint8_t qjsc_prelude[]; +extern const uint32_t qjsc_prelude_size; + +extern const uint8_t qjsc_wallet_core[]; +extern const uint32_t qjsc_wallet_core_size; struct HostMessage { struct list_head link; @@ -29,18 +36,32 @@ struct HostMessage { }; struct TALER_WALLET_Handle { + char *test; + JSRuntime *rt; JSContext *ctx; - TALER_WALLET_MessageHandlerFn *handler_f; - void *handler_cls; + pthread_t wallet_thread; - int hostmsg_send_pipe; - int hostmsg_recv_pipe; - pthread_mutex_t hostmsg_mutex; - struct list_head hostmg_queue; /* list of HostMessage.link */ + TALER_WALLET_MessageHandlerFn handler_f; + void *handler_cls; }; +static int eval_buf(JSContext *ctx, const char *codestr, const char *filename) +{ + JSValue val; + int ret; + val = JS_Eval(ctx, codestr, strlen(codestr), filename, 0); + if (JS_IsException(val)) { + js_std_dump_error(ctx); + ret = -1; + } else { + ret = 0; + } + JS_FreeValue(ctx, val); + return ret; +} + /* also used to initialize the worker context */ static JSContext *JS_NewCustomContext(JSRuntime *rt) { @@ -59,9 +80,10 @@ static JSContext *JS_NewCustomContext(JSRuntime *rt) void TALER_WALLET_set_handler(struct TALER_WALLET_Handle *h, TALER_WALLET_MessageHandlerFn handler_f, - void *handler_p) + void *handler_cls) { - + h->handler_cls = handler_cls; + h->handler_f = handler_f; } int @@ -71,50 +93,84 @@ TALER_WALLET_send_message (struct TALER_WALLET_Handle *h, return js_os_post_message_from_host(h->ctx, msg); } +static void +wallet_host_message_handler(void *cls, const char *msg) +{ + struct TALER_WALLET_Handle *wh = cls; + + if (wh->handler_f) { + wh->handler_f(wh->handler_cls, msg); + } +} + struct TALER_WALLET_Handle * TALER_WALLET_create(void) { struct TALER_WALLET_Handle *wh; - JSRuntime *rt; - JSContext *ctx; + wh = malloc(sizeof (*wh)); + memset(wh, 0, sizeof *wh); + wh->test = "foo"; + + wh->rt = JS_NewRuntime(); + + return wh; +} + +static void * +run(void *cls) +{ + struct TALER_WALLET_Handle *wh = cls; - rt = JS_NewRuntime(); - js_std_init_handlers(rt); - ctx = JS_NewCustomContext(rt); + printf("TEST: %s\n", wh->test); - if (!ctx) { + js_std_init_handlers(wh->rt); + wh->ctx = JS_NewCustomContext(wh->rt); + + + if (!wh->ctx) { fprintf(stderr, "qjs: cannot allocate JS context\n"); return NULL; } - JS_SetHostPromiseRejectionTracker(rt, js_std_promise_rejection_tracker, + eval_buf(wh->ctx, "console.log('hi');", "<talerwallet>"); + + JS_SetHostPromiseRejectionTracker(wh->rt, js_std_promise_rejection_tracker, NULL); - wh = malloc(sizeof (*wh)); - wh->ctx = ctx; - wh->rt = rt; - wh->handler_cls = NULL; - wh->handler_f = NULL; - if (0 != pthread_mutex_init(&wh->hostmsg_mutex, NULL)) { - return NULL; - } + js_std_add_helpers(wh->ctx, 0, NULL); + js_std_eval_binary(wh->ctx, qjsc_prelude, qjsc_prelude_size, 0); + js_std_eval_binary(wh->ctx, qjsc_wallet_core, qjsc_wallet_core_size, 0); - return wh; -} + js_os_set_host_message_handler(wh->ctx, wallet_host_message_handler, wh); -static void * -run(void *cls) -{ - struct TALER_WALLET_Handle *wh = cls; + printf("starting main loop\n"); - js_std_loop(wh->ctx); + eval_buf(wh->ctx, "console.log('hi');", "<talerwallet>"); + eval_buf(wh->ctx, "console.log(typeof testWithLocal);", "<talerwallet>"); + eval_buf(wh->ctx, "console.log('hello, world, bla!');", "<talerwallet>"); + eval_buf(wh->ctx, "testWithLocal();", "<talerwallet>"); + + js_std_loop(wh->ctx); + + printf("done with main loop\n"); } void TALER_WALLET_run (struct TALER_WALLET_Handle *wh) { - pthread_t wallet_thread; + pthread_t wallet_thread; + char *line; + size_t line_sz; - pthread_create(&wallet_thread, NULL, run, wh); + //run(wh); + + pthread_create(&wallet_thread, NULL, run, wh); + + wh->wallet_thread = wallet_thread; +} + +void TALER_WALLET_join(struct TALER_WALLET_Handle *wh) +{ + pthread_join(wh->wallet_thread, NULL); } diff --git a/taler_wallet_core_lib.h b/taler_wallet_core_lib.h @@ -82,6 +82,9 @@ TALER_WALLET_send_message (struct TALER_WALLET_Handle *h, void TALER_WALLET_run (struct TALER_WALLET_Handle *h); +void +TALER_WALLET_join (struct TALER_WALLET_Handle *wh); + /** * Destroy the wallet handle and free resources associated with it. * diff --git a/tart_module.c b/tart_module.c @@ -578,9 +578,9 @@ static JSValue js_talercrypto_eddsa_verify(JSContext *ctx, JSValue this_val, */ static int kdf(void *okm, size_t okm_len, - const void *ikm, size_t ikm_len, - const void *salt, size_t salt_len, - const void *info, size_t info_len) + const void *ikm, size_t ikm_len, + const void *salt, size_t salt_len, + const void *info, size_t info_len) { const mbedtls_md_info_t *md_extract; const mbedtls_md_info_t *md_expand; @@ -599,7 +599,7 @@ kdf(void *okm, size_t okm_len, ret = mbedtls_hkdf_extract(md_extract, salt, salt_len, ikm, ikm_len, prk); if (ret != 0) { - return -1; + return ret; } ret = mbedtls_hkdf_expand(md_expand, prk, mbedtls_md_get_size(md_extract), @@ -660,7 +660,8 @@ static JSValue js_talercrypto_kdf(JSContext *ctx, JSValue this_val, ret = kdf(okm, okm_len, ikm, ikm_len, salt, salt_len, info, info_len); if (ret != 0) { - return JS_EXCEPTION; + JS_ThrowInternalError(ctx, "kdf() call failed"); + goto exception; } ret_val = make_js_ta_copy(ctx, okm, okm_len); done: diff --git a/wallet-client-example.c b/wallet-client-example.c @@ -38,5 +38,7 @@ int main(int argc, char **argv) TALER_WALLET_run(wh); - TALER_WALLET_send_message(wh, "{}"); + //TALER_WALLET_send_message(wh, "{}"); + + TALER_WALLET_join(wh); }