diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | meson.build | 27 | ||||
-rw-r--r-- | qtart.c | 4 | ||||
-rw-r--r-- | quickjs/quickjs-libc.c | 139 | ||||
-rw-r--r-- | quickjs/quickjs-libc.h | 4 | ||||
-rw-r--r-- | quickjs/repl.js | 1 | ||||
-rw-r--r-- | taler_wallet_core_lib.c | 120 | ||||
-rw-r--r-- | taler_wallet_core_lib.h | 8 | ||||
-rw-r--r-- | tart_module.c | 1 | ||||
-rw-r--r-- | wallet-client-example.c | 42 |
10 files changed, 334 insertions, 14 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..719d735 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +tags +.vscode diff --git a/meson.build b/meson.build index 92be89a..afd5772 100644 --- a/meson.build +++ b/meson.build @@ -65,9 +65,10 @@ repl_c = custom_target('repl', repl = static_library('repl', repl_c) +tart = static_library('tart', 'tart_module.c') + qtart_exe = executable('qtart', [ 'qtart.c', - 'tart_module.c', ], link_with: [ libbf, @@ -77,6 +78,7 @@ qtart_exe = executable('qtart', [ quickjs_libc, quickjs, repl, + tart, ], dependencies: [ m_dep, @@ -85,3 +87,26 @@ qtart_exe = executable('qtart', [ mbedx509_dep, curl_dep, sodium_dep]) + +talerwalletcore_lib = shared_library('talerwalletcore', 'taler_wallet_core_lib.c', + link_with : [ + libbf, + libregexp, + libunicode , + cutils, + quickjs_libc, + quickjs, + tart, + ], + dependencies: [ + m_dep, + mbedcrypto_dep, + mbedtls_dep, + mbedx509_dep, + curl_dep, + sodium_dep + ]) + +wallet_client_example_exe = executable('wallet-client-example', + 'wallet-client-example.c', + link_with : [talerwalletcore_lib])
\ No newline at end of file @@ -39,10 +39,10 @@ #include <malloc.h> #endif -#include "tart_module.h" - #include "quickjs/cutils.h" +#include "tart_module.h" + extern const uint8_t qjsc_repl[]; extern const uint32_t qjsc_repl_size; diff --git a/quickjs/quickjs-libc.c b/quickjs/quickjs-libc.c index b4799b7..160308e 100644 --- a/quickjs/quickjs-libc.c +++ b/quickjs/quickjs-libc.c @@ -120,6 +120,18 @@ typedef struct { typedef struct { struct list_head link; + char *msg_data; +} JSHostMessage; + +typedef struct { + pthread_mutex_t mutex; + struct list_head msg_queue; /* list of JSHostMessage.link */ + int read_fd; + int write_fd; +} JSHostMessagePipe; + +typedef struct { + struct list_head link; JSWorkerMessagePipe *recv_pipe; JSValue on_message_func; } JSWorkerMessageHandler; @@ -130,8 +142,18 @@ typedef struct JSThreadState { struct list_head os_timers; /* list of JSOSTimer.link */ struct list_head port_list; /* list of JSWorkerMessageHandler.link */ int eval_script_recurse; /* only used in the main thread */ - /* not used in the main thread */ + JSWorkerMessagePipe *recv_pipe, *send_pipe; + + // send/receive message to/from the host in the main thread + JSHostMessagePipe *host_pipe; + + JSValue on_host_message_func; + + JSHostMessageHandlerFn host_message_handler_f; + void *host_message_handler_cls; + + int is_worker_thread; } JSThreadState; static uint64_t os_pending_signals; @@ -768,16 +790,16 @@ static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, str = JS_ToCStringLen(ctx, &len, argv[0]); if (!str) return JS_EXCEPTION; - if (!ts->recv_pipe && ++ts->eval_script_recurse == 1) { + if (!ts->is_worker_thread && ++ts->eval_script_recurse == 1) { /* install the interrupt handler */ JS_SetInterruptHandler(JS_GetRuntime(ctx), interrupt_handler, NULL); } - flags = JS_EVAL_TYPE_GLOBAL; + flags = JS_EVAL_TYPE_GLOBAL; if (backtrace_barrier) flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER; ret = JS_Eval(ctx, str, len, "<evalScript>", flags); JS_FreeCString(ctx, str); - if (!ts->recv_pipe && --ts->eval_script_recurse == 0) { + if (!ts->is_worker_thread && --ts->eval_script_recurse == 0) { /* remove the interrupt handler */ JS_SetInterruptHandler(JS_GetRuntime(ctx), NULL, NULL); os_pending_signals &= ~((uint64_t)1 << SIGINT); @@ -1825,7 +1847,7 @@ static JSValue js_os_rename(JSContext *ctx, JSValueConst this_val, static BOOL is_main_thread(JSRuntime *rt) { JSThreadState *ts = JS_GetRuntimeOpaque(rt); - return !ts->recv_pipe; + return !ts->is_worker_thread; } static JSOSRWHandler *find_rh(JSThreadState *ts, int fd) @@ -2489,7 +2511,7 @@ static int js_os_poll(JSContext *ctx) struct timeval tv, *tvp; /* only check signals in the main thread */ - if (!ts->recv_pipe && + if (!ts->is_worker_thread && unlikely(os_pending_signals != 0)) { JSOSSignalHandler *sh; uint64_t mask; @@ -2558,6 +2580,9 @@ static int js_os_poll(JSContext *ctx) } } + fd_max = max_int(fd_max, ts->host_pipe->read_fd); + FD_SET(ts->host_pipe->read_fd, &rfds); + ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp); if (ret > 0) { list_for_each(el, &ts->os_rw_handlers) { @@ -3536,6 +3561,7 @@ static void *worker_func(void *opaque) ts = JS_GetRuntimeOpaque(rt); ts->recv_pipe = args->recv_pipe; ts->send_pipe = args->send_pipe; + ts->is_worker_thread = TRUE; /* function pointer to avoid linking the whole JS_NewContext() if not needed */ @@ -3747,7 +3773,6 @@ static JSValue js_worker_postMessage(JSContext *ctx, JSValueConst this_val, js_free(ctx, data); js_free(ctx, sab_tab); return JS_EXCEPTION; - } static JSValue js_worker_set_onmessage(JSContext *ctx, JSValueConst this_val, @@ -3806,6 +3831,103 @@ static const JSCFunctionListEntry js_worker_proto_funcs[] = { #endif /* USE_WORKER */ +int +js_os_post_message_from_host(JSContext *ctx, const char *msg_str) +{ + JSThreadState *ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); + JSHostMessage *msg; + JSHostMessagePipe *hp; + uint8_t *data; + + msg = malloc(sizeof (*msg)); + if (!msg) { + goto fail; + } + msg->msg_data = strdup(msg_str); + if (!msg->msg_data) { + goto fail; + } + + hp = ts->host_pipe; + pthread_mutex_lock(&hp->mutex); + /* indicate that data is present */ + if (list_empty(&hp->msg_queue)) { + uint8_t ch = '\0'; + int ret; + for(;;) { + ret = write(hp->write_fd, &ch, 1); + if (ret == 1) + break; + if (ret < 0 && (errno != EAGAIN || errno != EINTR)) + break; + } + } + list_add_tail(&msg->link, &hp->msg_queue); + pthread_mutex_unlock(&hp->mutex); + return 0; + fail: + if (msg) { + free(msg->msg_data); + free(msg); + } + js_free(ctx, data); + return -1; +} + +void js_os_set_host_message_handler(JSContext *ctx, JSHostMessageHandlerFn f, void *cls) +{ + JSThreadState *ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); + ts->host_message_handler_f = f; + ts->host_message_handler_cls = f; +} + +static JSValue js_os_postHostMessage(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; + } + + 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; + } +} + +static JSValue js_os_set_onhostmessage(JSContext *ctx, JSValueConst this_val, + JSValueConst func) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + + if (JS_IsNull(func)) { + ts->on_host_message_func = JS_NULL; + } else { + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + JS_FreeValue(ctx, ts->on_host_message_func); + ts->on_host_message_func = JS_DupValue(ctx, func); + } + return JS_UNDEFINED; +} + void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)) { #ifdef USE_WORKER @@ -3906,6 +4028,8 @@ 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 ), }; static int js_os_init(JSContext *ctx, JSModuleDef *m) @@ -4024,6 +4148,7 @@ void js_std_init_handlers(JSRuntime *rt) ts = malloc(sizeof(*ts)); if (!ts) { +oom_fail: fprintf(stderr, "Could not allocate memory for the worker"); exit(1); } diff --git a/quickjs/quickjs-libc.h b/quickjs/quickjs-libc.h index fbbe5b0..4a7697e 100644 --- a/quickjs/quickjs-libc.h +++ b/quickjs/quickjs-libc.h @@ -33,6 +33,8 @@ extern "C" { #endif +typedef void (*JSHostMessageHandlerFn)(void *cls, const char *msg); + JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name); JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name); void js_std_add_helpers(JSContext *ctx, int argc, char **argv); @@ -51,6 +53,8 @@ void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void *opaque); void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)); +void js_os_set_host_message_handler(JSContext *ctx, JSHostMessageHandlerFn f, void *cls); +int js_os_post_message_from_host(JSContext *ctx, const char *msg_str); #ifdef __cplusplus } /* extern "C" { */ diff --git a/quickjs/repl.js b/quickjs/repl.js index 67a121e..2d2d19a 100644 --- a/quickjs/repl.js +++ b/quickjs/repl.js @@ -575,6 +575,7 @@ import * as os from "os"; std.exit(0); } else { std.puts("\n(Press Ctrl-C again to quit)\n"); + reset(); readline_print_prompt(); } } diff --git a/taler_wallet_core_lib.c b/taler_wallet_core_lib.c new file mode 100644 index 0000000..cd1f0e3 --- /dev/null +++ b/taler_wallet_core_lib.c @@ -0,0 +1,120 @@ +/* + This file is part of GNU Taler + Copyright (C) 2014-2022 Taler Systems SA + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +#include "taler_wallet_core_lib.h" + +#include "quickjs/quickjs.h" +#include "quickjs/quickjs-libc.h" +#include "tart_module.h" +#include <pthread.h> +#include "quickjs/list.h" +#include <unistd.h> + +struct HostMessage { + struct list_head link; + char *data; +}; + +struct TALER_WALLET_Handle { + JSRuntime *rt; + JSContext *ctx; + + TALER_WALLET_MessageHandlerFn *handler_f; + void *handler_cls; + + int hostmsg_send_pipe; + int hostmsg_recv_pipe; + pthread_mutex_t hostmsg_mutex; + struct list_head hostmg_queue; /* list of HostMessage.link */ +}; + +/* also used to initialize the worker context */ +static JSContext *JS_NewCustomContext(JSRuntime *rt) +{ + JSContext *ctx; + ctx = JS_NewContext(rt); + if (!ctx) + return NULL; + /* system modules */ + js_init_module_std(ctx, "std"); + js_init_module_os(ctx, "os"); + /* taler runtime */ + tart_init_module_talercrypto(ctx, "tart"); + return ctx; +} + +void +TALER_WALLET_set_handler(struct TALER_WALLET_Handle *h, + TALER_WALLET_MessageHandlerFn handler_f, + void *handler_p) +{ + +} + +int +TALER_WALLET_send_message (struct TALER_WALLET_Handle *h, + const char *msg) +{ + return js_os_post_message_from_host(h->ctx, msg); +} + +struct TALER_WALLET_Handle * +TALER_WALLET_create(void) +{ + struct TALER_WALLET_Handle *wh; + JSRuntime *rt; + JSContext *ctx; + + rt = JS_NewRuntime(); + js_std_init_handlers(rt); + ctx = JS_NewCustomContext(rt); + + if (!ctx) { + fprintf(stderr, "qjs: cannot allocate JS context\n"); + return NULL; + } + + JS_SetHostPromiseRejectionTracker(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; + } + + + + return wh; +} + +static void * +run(void *cls) +{ + struct TALER_WALLET_Handle *wh = cls; + + js_std_loop(wh->ctx); +} + +void +TALER_WALLET_run (struct TALER_WALLET_Handle *wh) +{ + pthread_t wallet_thread; + + pthread_create(&wallet_thread, NULL, run, wh); +} diff --git a/taler_wallet_core_lib.h b/taler_wallet_core_lib.h index 1e42cfb..78d6ad2 100644 --- a/taler_wallet_core_lib.h +++ b/taler_wallet_core_lib.h @@ -36,7 +36,7 @@ struct TALER_WALLET_Handle; * @param handler_p opaque closure for the message handler * @param message message from wallet-core as a JSON string */ -typedef void (*TALER_WALLET_MessageHandlerFn)(void *handler_p, char *message); +typedef void (*TALER_WALLET_MessageHandlerFn)(void *handler_p, const char *message); /** * Create a new wallet-core handle. @@ -52,7 +52,7 @@ TALER_WALLET_create(void); */ void TALER_WALLET_set_handler(struct TALER_WALLET_Handle *h, - TALER_WALLET_MessageHandlerFn *handler_f, + TALER_WALLET_MessageHandlerFn handler_f, void *handler_p); @@ -70,7 +70,7 @@ TALER_WALLET_set_jsfile(struct TALER_WALLET_Handle *h, * Responses will be sent asynchronously to the message handler * set with #TALER_WALLET_set_handler. */ -void +int TALER_WALLET_send_message (struct TALER_WALLET_Handle *h, const char *msg); @@ -80,7 +80,7 @@ TALER_WALLET_send_message (struct TALER_WALLET_Handle *h, * This function creates a new thread and returns immediately. */ void -TALER_WALLET_run (struct TALER_WLLET_Handle *h); +TALER_WALLET_run (struct TALER_WALLET_Handle *h); /** * Destroy the wallet handle and free resources associated with it. diff --git a/tart_module.c b/tart_module.c index 859f0aa..e244e6e 100644 --- a/tart_module.c +++ b/tart_module.c @@ -1302,6 +1302,7 @@ static JSValue js_talercrypto_hash_state_finish(JSContext *ctx, JSValue this_val return make_js_ta_copy(ctx, hashval, crypto_hash_sha512_BYTES); } + static const JSCFunctionListEntry tart_talercrypto_funcs[] = { JS_CFUNC_DEF("structuredClone", 1, js_structured_clone), JS_CFUNC_DEF("encodeUtf8", 1, js_encode_utf8), diff --git a/wallet-client-example.c b/wallet-client-example.c new file mode 100644 index 0000000..396a56a --- /dev/null +++ b/wallet-client-example.c @@ -0,0 +1,42 @@ +/* + This file is part of GNU Taler + Copyright (C) 2014-2022 Taler Systems SA + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + + +/** + * Sample client for a TALER_WALLET_Handle. + */ +#include <stdlib.h> +#include <stdio.h> +#include "taler_wallet_core_lib.h" + + +void +my_handler(void *cls, const char *message) +{ + printf("got message: %s\n", message); +} + + +int main(int argc, char **argv) +{ + struct TALER_WALLET_Handle *wh = TALER_WALLET_create(); + + TALER_WALLET_set_handler(wh, &my_handler, NULL); + + TALER_WALLET_run(wh); + + TALER_WALLET_send_message(wh, "{}"); +} |