quickjs-tart

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

taler_wallet_core_lib.c (9267B)


      1 /*
      2  This file is part of GNU Taler
      3  Copyright (C) 2014-2022 Taler Systems SA
      4 
      5  GNU Taler is free software; you can redistribute it and/or modify it under the
      6  terms of the GNU Affero General Public License as published by the Free Software
      7  Foundation; either version 3, or (at your option) any later version.
      8 
      9  GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13  You should have received a copy of the GNU Affero General Public License along with
     14  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 #include "taler_wallet_core_lib.h"
     18 
     19 #include "quickjs/quickjs.h"
     20 #include "quickjs/quickjs-libc.h"
     21 #include "tart_module.h"
     22 #include <pthread.h>
     23 #include "quickjs/list.h"
     24 #include <signal.h>
     25 #include <unistd.h>
     26 #include <string.h>
     27 
     28 extern const uint8_t qjsc_prelude[];
     29 extern const uint32_t qjsc_prelude_size;
     30 
     31 extern const uint8_t qjsc_wallet_core[];
     32 extern const uint32_t qjsc_wallet_core_size;
     33 
     34 static JSClassID js_wallet_instance_handle_id;
     35 
     36 struct HostMessage {
     37     struct list_head link;
     38     char *data;
     39 };
     40 
     41 struct TALER_WALLET_Instance
     42 {
     43     // Mutex that is locked while initializing the handle
     44     pthread_mutex_t handle_mutex;
     45 
     46     JSRuntime *rt;
     47     JSContext *ctx;
     48 
     49     pthread_t wallet_thread;
     50 
     51     TALER_WALLET_MessageHandlerFn handler_f;
     52     void *handler_cls;
     53 
     54     TALER_WALLET_LogHandlerFn log_handler_f;
     55     void *log_handler_cls;
     56 
     57     struct JSHttpClientImplementation *http_impl;
     58 };
     59 
     60 
     61 static int eval_buf(JSContext *ctx, const char *codestr, const char *filename)
     62 {
     63     JSValue val;
     64     int ret;
     65     val = JS_Eval(ctx, codestr, strlen(codestr), filename, 0);
     66     if (JS_IsException(val)) {
     67         js_std_dump_error(ctx);
     68         ret = -1;
     69     } else {
     70         ret = 0;
     71     }
     72     JS_FreeValue(ctx, val);
     73     return ret;
     74 }
     75 
     76 
     77 /* also used to initialize the worker context */
     78 static JSContext *JS_NewCustomContext(JSRuntime *rt)
     79 {
     80     JSContext *ctx;
     81     ctx = JS_NewContext(rt);
     82     if (!ctx)
     83         return NULL;
     84     /* system modules */
     85     js_init_module_std(ctx, "std");
     86     js_init_module_os(ctx, "os");
     87     /* taler runtime */
     88     tart_init_module_talercrypto(ctx, "tart");
     89     return ctx;
     90 }
     91 
     92 
     93 void
     94 TALER_WALLET_set_message_handler(struct TALER_WALLET_Instance *twi,
     95                                  TALER_WALLET_MessageHandlerFn handler_f,
     96                                  void *handler_cls)
     97 {
     98     twi->handler_cls = handler_cls;
     99     twi->handler_f = handler_f;
    100 }
    101 
    102 
    103 void
    104 TALER_WALLET_set_log_handler(struct TALER_WALLET_Instance *twi,
    105                              TALER_WALLET_LogHandlerFn handler_f,
    106                              void *handler_cls)
    107 {
    108     twi->log_handler_cls = handler_cls;
    109     twi->log_handler_f = handler_f;
    110 }
    111 
    112 
    113 int
    114 TALER_WALLET_send_request (struct TALER_WALLET_Instance *twi,
    115                            const char *msg)
    116 {
    117     int ret;
    118     pthread_mutex_lock(&twi->handle_mutex);
    119     ret = js_os_post_message_from_host(twi->ctx, msg);
    120     pthread_mutex_unlock(&twi->handle_mutex);
    121     return ret;
    122 }
    123 
    124 
    125 static void
    126 wallet_host_message_handler(void *cls, const char *msg)
    127 {
    128     struct TALER_WALLET_Instance *wh = cls;
    129 
    130     if (wh->handler_f) {
    131         wh->handler_f(wh->handler_cls, msg);
    132     }
    133 }
    134 
    135 
    136 struct TALER_WALLET_Instance *
    137 TALER_WALLET_create(void)
    138 {
    139     struct TALER_WALLET_Instance *wh;
    140     wh = malloc(sizeof (*wh));
    141     memset(wh, 0, sizeof *wh);
    142     if (0 != pthread_mutex_init(&wh->handle_mutex, NULL)) {
    143         abort();
    144     }
    145 
    146     return wh;
    147 }
    148 
    149 static JSValue js_native_log(JSContext *ctx,
    150                              JSValueConst this_obj,
    151                              int argc, JSValueConst *argv,
    152                              int magic, JSValue *func_data)
    153 {
    154     struct TALER_WALLET_Instance *wh;
    155     const char *tag = NULL;
    156     const char *msg = NULL;
    157     uint32_t level = 0;
    158     wh = JS_GetOpaque(func_data[0], js_wallet_instance_handle_id);
    159     if (NULL != wh->log_handler_f) {
    160         JS_ToUint32(ctx, &level, argv[0]);
    161         tag = JS_ToCString(ctx, argv[1]);
    162         msg = JS_ToCString(ctx, argv[2]);
    163         wh->log_handler_f(wh->log_handler_cls,
    164                           level,
    165                           tag,
    166                           msg);
    167     }
    168     JS_FreeCString(ctx, tag);
    169     JS_FreeCString(ctx, msg);
    170     return JS_UNDEFINED;
    171 }
    172 
    173 
    174 static void *
    175 run(void *cls)
    176 {
    177     struct TALER_WALLET_Instance *wh = cls;
    178 
    179     wh->rt = JS_NewRuntime();
    180 
    181     js_std_init_handlers(wh->rt);
    182 
    183     if (wh->http_impl) {
    184         js_os_set_http_impl(wh->rt, wh->http_impl);
    185     } else {
    186         fprintf(stderr, "warning: no HTTP client implementation provided for wallet-core\n");
    187     }
    188 
    189     wh->ctx = JS_NewCustomContext(wh->rt);
    190 
    191     if (!wh->ctx) {
    192         fprintf(stderr, "qjs: cannot allocate JS context\n");
    193         pthread_mutex_unlock(&wh->handle_mutex);
    194         return NULL;
    195     }
    196 
    197     JS_NewClassID(&js_wallet_instance_handle_id);
    198 
    199 
    200     JS_SetHostPromiseRejectionTracker(wh->rt, js_std_promise_rejection_tracker,
    201                                       NULL);
    202 
    203     js_std_add_helpers(wh->ctx, 0, NULL);
    204 
    205     // install native log handler
    206     if (NULL != wh->log_handler_f) {
    207         JSValue global_obj;
    208         JSValue data;
    209 
    210         data = JS_NewObjectClass(wh->ctx, js_wallet_instance_handle_id);
    211         JS_SetOpaque(data, wh);
    212         global_obj = JS_GetGlobalObject(wh->ctx);
    213         // We could also try to add this to "os" or "tart", but we are lazy.
    214         JS_SetPropertyStr(wh->ctx, global_obj, "__nativeLog",
    215                           JS_NewCFunctionData(wh->ctx, js_native_log, 3, 0, 1, &data));
    216     }
    217 
    218 //    fprintf(stderr, "qtart: loading JS code\n");
    219     js_std_eval_binary(wh->ctx, qjsc_prelude, qjsc_prelude_size, 0);
    220     js_std_eval_binary(wh->ctx, qjsc_wallet_core, qjsc_wallet_core_size, 0);
    221 //    fprintf(stderr, "qtart: loaded JS code\n");
    222 
    223     js_os_set_host_message_handler(wh->ctx, wallet_host_message_handler, wh);
    224 
    225 
    226     pthread_mutex_unlock(&wh->handle_mutex);
    227 
    228     eval_buf(wh->ctx, "installNativeWalletListener()", "<talerwalletcore>");
    229 
    230     js_std_loop(wh->ctx);
    231 
    232     return NULL;
    233 }
    234 
    235 #define WALLET_THREAD_STACK_SIZE (1024 * 512)
    236 
    237 int
    238 TALER_WALLET_run (struct TALER_WALLET_Instance *wh)
    239 {
    240     pthread_t wallet_thread;
    241     pthread_attr_t tattr;
    242 
    243     pthread_mutex_lock(&wh->handle_mutex);
    244 
    245     if (0 != pthread_attr_init(&tattr)) {
    246         pthread_mutex_unlock(&wh->handle_mutex);
    247         fprintf(stderr, "could not initialize pthread attr\n");
    248         return -1;
    249     }
    250 
    251     if (0 != pthread_attr_setstacksize(&tattr, WALLET_THREAD_STACK_SIZE)) {
    252         pthread_mutex_unlock(&wh->handle_mutex);
    253         fprintf(stderr, "could not set stack size\n");
    254         return -1;
    255     }
    256 
    257     if (0 != pthread_create(&wallet_thread, &tattr, run, wh)) {
    258         pthread_mutex_unlock(&wh->handle_mutex);
    259         fprintf(stderr, "could not create wallet thread\n");
    260         return -1;
    261     }
    262 
    263     wh->wallet_thread = wallet_thread;
    264 
    265     return 0;
    266 }
    267 
    268 
    269 void TALER_WALLET_join(struct TALER_WALLET_Instance *wh)
    270 {
    271     pthread_join(wh->wallet_thread, NULL);
    272 }
    273 
    274 
    275 void
    276 TALER_WALLET_destroy(struct TALER_WALLET_Instance *twi)
    277 {
    278     pthread_kill(twi->wallet_thread, SIGKILL);
    279 }
    280 
    281 
    282 struct LogRedirectContext {
    283     int active;
    284     TALER_LogFn logfn;
    285     void *cls;
    286 };
    287 
    288 static struct LogRedirectContext redir_ctx;
    289 static int pfd[2];
    290 static pthread_t log_thr;
    291 
    292 static void *thread_func(void *) {
    293     ssize_t rdsz;
    294     char buf[1024];
    295     while ((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) {
    296         if (buf[rdsz - 1] == '\n') --rdsz;
    297         buf[rdsz] = 0;  /* add null-terminator */
    298         redir_ctx.logfn(redir_ctx.cls, 0, buf);
    299     }
    300     return 0;
    301 }
    302 
    303 
    304 int
    305 TALER_start_redirect_std(TALER_LogFn logfn, void *cls)
    306 {
    307     if (redir_ctx.active) {
    308         return -2;
    309     }
    310     /* make stdout line-buffered and stderr unbuffered */
    311     setvbuf(stdout, 0, _IOLBF, 0);
    312     setvbuf(stderr, 0, _IONBF, 0);
    313 
    314     /* create the pipe and redirect stdout and stderr */
    315     pipe(pfd);
    316     dup2(pfd[1], 1);
    317     dup2(pfd[1], 2);
    318 
    319     redir_ctx.cls = cls;
    320     redir_ctx.logfn = logfn;
    321     redir_ctx.active = 1;
    322 
    323     /* spawn the logging thread */
    324     if (pthread_create(&log_thr, 0, thread_func, 0) == -1) {
    325         return -1;
    326     }
    327     pthread_detach(log_thr);
    328     return 0;
    329 }
    330 
    331 void
    332 TALER_set_http_client_implementation(struct TALER_WALLET_Instance *twi,
    333                                      struct JSHttpClientImplementation *impl)
    334 {
    335   twi->http_impl = impl;
    336 }
    337 
    338 void
    339 TALER_set_curl_http_client(struct TALER_WALLET_Instance *twi)
    340 {
    341     struct JSHttpClientImplementation *client = js_curl_http_client_create();
    342     TALER_set_http_client_implementation(twi, client);
    343 }
    344 
    345 #pragma mark -
    346 struct JSHttpClientImplementation *
    347 TALER_pack_http_client_implementation(JSHttpReqCreateFn req_create,
    348                                       JSHttpReqCancelFn req_cancel,
    349                                       void *handler_p)
    350 {
    351     // will (and should!) crash if this tiny malloc fails
    352     struct JSHttpClientImplementation *impl = malloc(sizeof *impl);
    353 
    354     if (!impl) {
    355         return NULL;
    356     }
    357     impl->cls = handler_p;
    358     impl->req_create = req_create;
    359     impl->req_cancel = req_cancel;
    360     return impl;
    361 }