/* 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 */ #include "taler_wallet_core_lib.h" #include "quickjs/quickjs.h" #include "quickjs/quickjs-libc.h" #include "tart_module.h" #include #include "quickjs/list.h" #include #include 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 JSClassID js_wallet_instance_handle_id; struct HostMessage { struct list_head link; char *data; }; struct TALER_WALLET_Instance { // Mutex that is locked while initializing the handle pthread_mutex_t handle_mutex; JSRuntime *rt; JSContext *ctx; pthread_t wallet_thread; TALER_WALLET_MessageHandlerFn handler_f; void *handler_cls; TALER_WALLET_LogHandlerFn log_handler_f; void *log_handler_cls; struct JSHttpClientImplementation *http_impl; }; 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) { 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_message_handler(struct TALER_WALLET_Instance *twi, TALER_WALLET_MessageHandlerFn handler_f, void *handler_cls) { twi->handler_cls = handler_cls; twi->handler_f = handler_f; } void TALER_WALLET_set_log_handler(struct TALER_WALLET_Instance *twi, TALER_WALLET_LogHandlerFn handler_f, void *handler_cls) { twi->log_handler_cls = handler_cls; twi->log_handler_f = handler_f; } int TALER_WALLET_send_request (struct TALER_WALLET_Instance *twi, const char *msg) { int ret; pthread_mutex_lock(&twi->handle_mutex); ret = js_os_post_message_from_host(twi->ctx, msg); pthread_mutex_unlock(&twi->handle_mutex); return ret; } static void wallet_host_message_handler(void *cls, const char *msg) { struct TALER_WALLET_Instance *wh = cls; if (wh->handler_f) { wh->handler_f(wh->handler_cls, msg); } } struct TALER_WALLET_Instance * TALER_WALLET_create(void) { struct TALER_WALLET_Instance *wh; wh = malloc(sizeof (*wh)); memset(wh, 0, sizeof *wh); if (0 != pthread_mutex_init(&wh->handle_mutex, NULL)) { abort(); } return wh; } static JSValue js_native_log(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv, int magic, JSValue *func_data) { struct TALER_WALLET_Instance *wh; const char *tag = NULL; const char *msg = NULL; uint32_t level = 0; wh = JS_GetOpaque(func_data[0], js_wallet_instance_handle_id); if (NULL != wh->log_handler_f) { JS_ToUint32(ctx, &level, argv[0]); tag = JS_ToCString(ctx, argv[1]); msg = JS_ToCString(ctx, argv[2]); wh->log_handler_f(wh->log_handler_cls, level, tag, msg); } JS_FreeCString(ctx, tag); JS_FreeCString(ctx, msg); return JS_UNDEFINED; } static void * run(void *cls) { struct TALER_WALLET_Instance *wh = cls; wh->rt = JS_NewRuntime(); js_std_init_handlers(wh->rt); if (wh->http_impl) { js_os_set_http_impl(wh->rt, wh->http_impl); } else { fprintf(stderr, "warning: no HTTP client implementation provided for wallet-core\n"); } wh->ctx = JS_NewCustomContext(wh->rt); if (!wh->ctx) { fprintf(stderr, "qjs: cannot allocate JS context\n"); pthread_mutex_unlock(&wh->handle_mutex); return NULL; } JS_NewClassID(&js_wallet_instance_handle_id); JS_SetHostPromiseRejectionTracker(wh->rt, js_std_promise_rejection_tracker, NULL); js_std_add_helpers(wh->ctx, 0, NULL); // install native log handler if (NULL != wh->log_handler_f) { JSValue global_obj; JSValue data; data = JS_NewObjectClass(wh->ctx, js_wallet_instance_handle_id); JS_SetOpaque(data, wh); global_obj = JS_GetGlobalObject(wh->ctx); // We could also try to add this to "os" or "tart", but we are lazy. JS_SetPropertyStr(wh->ctx, global_obj, "__nativeLog", JS_NewCFunctionData(wh->ctx, js_native_log, 3, 0, 1, &data)); } fprintf(stderr, "qtart: loading JS code\n"); 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); fprintf(stderr, "qtart: done loading JS code\n"); js_os_set_host_message_handler(wh->ctx, wallet_host_message_handler, wh); pthread_mutex_unlock(&wh->handle_mutex); eval_buf(wh->ctx, "installNativeWalletListener()", ""); js_std_loop(wh->ctx); return NULL; } #define WALLET_THREAD_STACK_SIZE (1024 * 512) int TALER_WALLET_run (struct TALER_WALLET_Instance *wh) { pthread_t wallet_thread; pthread_attr_t tattr; pthread_mutex_lock(&wh->handle_mutex); if (0 != pthread_attr_init(&tattr)) { pthread_mutex_unlock(&wh->handle_mutex); fprintf(stderr, "could not initialize pthread attr\n"); return -1; } if (0 != pthread_attr_setstacksize(&tattr, WALLET_THREAD_STACK_SIZE)) { pthread_mutex_unlock(&wh->handle_mutex); fprintf(stderr, "could not set stack size\n"); return -1; } if (0 != pthread_create(&wallet_thread, &tattr, run, wh)) { pthread_mutex_unlock(&wh->handle_mutex); fprintf(stderr, "could not create wallet thread\n"); return -1; } wh->wallet_thread = wallet_thread; return 0; } void TALER_WALLET_join(struct TALER_WALLET_Instance *wh) { pthread_join(wh->wallet_thread, NULL); } void TALER_WALLET_destroy(struct TALER_WALLET_Instance *twi) { // FIXME: Implement! } struct LogRedirectContext { int active; TALER_LogFn logfn; void *cls; }; static struct LogRedirectContext redir_ctx; static int pfd[2]; static pthread_t log_thr; static void *thread_func(void *) { ssize_t rdsz; char buf[1024]; while ((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) { if (buf[rdsz - 1] == '\n') --rdsz; buf[rdsz] = 0; /* add null-terminator */ redir_ctx.logfn(redir_ctx.cls, 0, buf); } return 0; } int TALER_start_redirect_std(TALER_LogFn logfn, void *cls) { if (redir_ctx.active) { return -2; } /* make stdout line-buffered and stderr unbuffered */ setvbuf(stdout, 0, _IOLBF, 0); setvbuf(stderr, 0, _IONBF, 0); /* create the pipe and redirect stdout and stderr */ pipe(pfd); dup2(pfd[1], 1); dup2(pfd[1], 2); redir_ctx.cls = cls; redir_ctx.logfn = logfn; redir_ctx.active = 1; /* spawn the logging thread */ if (pthread_create(&log_thr, 0, thread_func, 0) == -1) { return -1; } pthread_detach(log_thr); return 0; } void TALER_set_http_client_implementation(struct TALER_WALLET_Instance *twi, struct JSHttpClientImplementation *impl) { twi->http_impl = impl; } void TALER_set_curl_http_client(struct TALER_WALLET_Instance *twi) { struct JSHttpClientImplementation *client = js_curl_http_client_create(); TALER_set_http_client_implementation(twi, client); } #pragma mark - struct JSHttpClientImplementation * TALER_pack_http_client_implementation(JSHttpReqCreateFn req_create, JSHttpReqCancelFn req_cancel, void *handler_p) { // will (and should!) crash if this tiny malloc fails struct JSHttpClientImplementation *impl = malloc(sizeof *impl); impl->cls = handler_p; impl->req_create = req_create; impl->req_cancel = req_cancel; return impl; }