quickjs-tart

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

taler_wallet_core_lib.c (15209B)


      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 static pthread_once_t wallet_class_id_once = PTHREAD_ONCE_INIT;
     36 
     37 static JSClassDef js_wallet_instance_handle_class = {
     38     .class_name = "TalerWalletInstanceHandle",
     39 };
     40 
     41 static void init_wallet_class_id(void)
     42 {
     43     JS_NewClassID(&js_wallet_instance_handle_id);
     44 }
     45 
     46 struct HostMessage {
     47     struct list_head link;
     48     char *data;
     49 };
     50 
     51 struct TALER_WALLET_Instance
     52 {
     53     // Mutex that is locked while initializing the handle
     54     pthread_mutex_t handle_mutex;
     55     pthread_cond_t state_changed;
     56 
     57     JSRuntime *rt;
     58     JSContext *ctx;
     59 
     60     pthread_t wallet_thread;
     61     int wallet_thread_valid;
     62 
     63     enum {
     64         WALLET_CREATED,
     65         WALLET_STARTING,
     66         WALLET_RUNNING,
     67         WALLET_STOPPING,
     68         WALLET_STOPPED,
     69         WALLET_FAILED,
     70     } state;
     71 
     72     TALER_WALLET_MessageHandlerFn handler_f;
     73     void *handler_cls;
     74 
     75     TALER_WALLET_LogHandlerFn log_handler_f;
     76     void *log_handler_cls;
     77 
     78     struct JSHttpClientImplementation *http_impl;
     79     int owns_http_impl;
     80 };
     81 
     82 
     83 static int eval_buf(JSContext *ctx, const char *codestr, const char *filename)
     84 {
     85     JSValue val;
     86     int ret;
     87     val = JS_Eval(ctx, codestr, strlen(codestr), filename, 0);
     88     if (JS_IsException(val)) {
     89         js_std_dump_error(ctx);
     90         ret = -1;
     91     } else {
     92         ret = 0;
     93     }
     94     JS_FreeValue(ctx, val);
     95     return ret;
     96 }
     97 
     98 
     99 /* also used to initialize the worker context */
    100 static JSContext *JS_NewCustomContext(JSRuntime *rt)
    101 {
    102     JSContext *ctx;
    103     ctx = JS_NewContext(rt);
    104     if (!ctx)
    105         return NULL;
    106     /* system modules */
    107     js_init_module_std(ctx, "std");
    108     js_init_module_os(ctx, "os");
    109     /* taler runtime */
    110     tart_init_module_talercrypto(ctx, "tart");
    111     return ctx;
    112 }
    113 
    114 
    115 void
    116 TALER_WALLET_set_message_handler(struct TALER_WALLET_Instance *twi,
    117                                  TALER_WALLET_MessageHandlerFn handler_f,
    118                                  void *handler_cls)
    119 {
    120     twi->handler_cls = handler_cls;
    121     twi->handler_f = handler_f;
    122 }
    123 
    124 
    125 void
    126 TALER_WALLET_set_log_handler(struct TALER_WALLET_Instance *twi,
    127                              TALER_WALLET_LogHandlerFn handler_f,
    128                              void *handler_cls)
    129 {
    130     twi->log_handler_cls = handler_cls;
    131     twi->log_handler_f = handler_f;
    132 }
    133 
    134 
    135 int
    136 TALER_WALLET_send_request (struct TALER_WALLET_Instance *twi,
    137                            const char *msg)
    138 {
    139     int ret;
    140     pthread_mutex_lock(&twi->handle_mutex);
    141     while (twi->state == WALLET_STARTING) {
    142         pthread_cond_wait(&twi->state_changed, &twi->handle_mutex);
    143     }
    144     if (twi->state != WALLET_RUNNING || !twi->ctx) {
    145         pthread_mutex_unlock(&twi->handle_mutex);
    146         return -1;
    147     }
    148     ret = js_os_post_message_from_host(twi->ctx, msg);
    149     pthread_mutex_unlock(&twi->handle_mutex);
    150     return ret;
    151 }
    152 
    153 
    154 static void
    155 wallet_host_message_handler(void *cls, const char *msg)
    156 {
    157     struct TALER_WALLET_Instance *wh = cls;
    158 
    159     if (wh->handler_f) {
    160         wh->handler_f(wh->handler_cls, msg);
    161     }
    162 }
    163 
    164 
    165 struct TALER_WALLET_Instance *
    166 TALER_WALLET_create(void)
    167 {
    168     struct TALER_WALLET_Instance *wh;
    169     wh = calloc(1, sizeof (*wh));
    170     if (!wh) {
    171         return NULL;
    172     }
    173     if (0 != pthread_mutex_init(&wh->handle_mutex, NULL)) {
    174         free(wh);
    175         return NULL;
    176     }
    177     if (0 != pthread_cond_init(&wh->state_changed, NULL)) {
    178         pthread_mutex_destroy(&wh->handle_mutex);
    179         free(wh);
    180         return NULL;
    181     }
    182     wh->state = WALLET_CREATED;
    183 
    184     return wh;
    185 }
    186 
    187 static JSValue js_native_log(JSContext *ctx,
    188                              JSValueConst this_obj,
    189                              int argc, JSValueConst *argv,
    190                              int magic, JSValue *func_data)
    191 {
    192     struct TALER_WALLET_Instance *wh;
    193     const char *tag = NULL;
    194     const char *msg = NULL;
    195     uint32_t level = 0;
    196     wh = JS_GetOpaque(func_data[0], js_wallet_instance_handle_id);
    197     if (!wh) {
    198         return JS_ThrowInternalError(ctx, "invalid wallet log context");
    199     }
    200     if (NULL != wh->log_handler_f) {
    201         JS_ToUint32(ctx, &level, argv[0]);
    202         tag = JS_ToCString(ctx, argv[1]);
    203         msg = JS_ToCString(ctx, argv[2]);
    204         wh->log_handler_f(wh->log_handler_cls,
    205                           level,
    206                           tag,
    207                           msg);
    208     }
    209     JS_FreeCString(ctx, tag);
    210     JS_FreeCString(ctx, msg);
    211     return JS_UNDEFINED;
    212 }
    213 
    214 
    215 static void *
    216 run(void *cls)
    217 {
    218     struct TALER_WALLET_Instance *wh = cls;
    219     int handlers_initialized = 0;
    220 
    221     wh->rt = JS_NewRuntime();
    222     if (!wh->rt) {
    223         goto fail;
    224     }
    225 
    226     js_std_init_handlers(wh->rt);
    227     handlers_initialized = 1;
    228 
    229     if (wh->http_impl) {
    230         js_os_set_http_impl(wh->rt, wh->http_impl);
    231     } else {
    232         fprintf(stderr, "warning: no HTTP client implementation provided for wallet-core\n");
    233     }
    234 
    235     wh->ctx = JS_NewCustomContext(wh->rt);
    236 
    237     if (!wh->ctx) {
    238         fprintf(stderr, "qjs: cannot allocate JS context\n");
    239         goto fail;
    240     }
    241 
    242     if (0 != pthread_once(&wallet_class_id_once, init_wallet_class_id) ||
    243         0 != JS_NewClass(wh->rt, js_wallet_instance_handle_id,
    244                          &js_wallet_instance_handle_class)) {
    245         goto fail;
    246     }
    247 
    248 
    249     JS_SetHostPromiseRejectionTracker(wh->rt, js_std_promise_rejection_tracker,
    250                                       NULL);
    251 
    252     js_std_add_helpers(wh->ctx, 0, NULL);
    253 
    254     // install native log handler
    255     if (NULL != wh->log_handler_f) {
    256         JSValue global_obj;
    257         JSValue data;
    258 
    259         data = JS_NewObjectClass(wh->ctx, js_wallet_instance_handle_id);
    260         if (JS_IsException(data)) {
    261             goto fail;
    262         }
    263         JS_SetOpaque(data, wh);
    264         global_obj = JS_GetGlobalObject(wh->ctx);
    265         // We could also try to add this to "os" or "tart", but we are lazy.
    266         JS_SetPropertyStr(wh->ctx, global_obj, "__nativeLog",
    267                           JS_NewCFunctionData(wh->ctx, js_native_log, 3, 0, 1, &data));
    268         JS_FreeValue(wh->ctx, global_obj);
    269         JS_FreeValue(wh->ctx, data);
    270     }
    271 
    272 //    fprintf(stderr, "qtart: loading JS code\n");
    273     js_std_eval_binary(wh->ctx, qjsc_prelude, qjsc_prelude_size, 0);
    274     js_std_eval_binary(wh->ctx, qjsc_wallet_core, qjsc_wallet_core_size, 0);
    275 //    fprintf(stderr, "qtart: loaded JS code\n");
    276 
    277     js_os_set_host_message_handler(wh->ctx, wallet_host_message_handler, wh);
    278 
    279 
    280     if (0 != eval_buf(wh->ctx, "installNativeWalletListener()",
    281                       "<talerwalletcore>")) {
    282         goto fail;
    283     }
    284 
    285     pthread_mutex_lock(&wh->handle_mutex);
    286     wh->state = WALLET_RUNNING;
    287     pthread_cond_broadcast(&wh->state_changed);
    288     pthread_mutex_unlock(&wh->handle_mutex);
    289 
    290     js_std_loop(wh->ctx);
    291 
    292     pthread_mutex_lock(&wh->handle_mutex);
    293     wh->state = WALLET_STOPPING;
    294     pthread_cond_broadcast(&wh->state_changed);
    295     pthread_mutex_unlock(&wh->handle_mutex);
    296     js_std_free_handlers(wh->rt);
    297     handlers_initialized = 0;
    298     JS_FreeContext(wh->ctx);
    299     JS_FreeRuntime(wh->rt);
    300     pthread_mutex_lock(&wh->handle_mutex);
    301     wh->ctx = NULL;
    302     wh->rt = NULL;
    303     wh->state = WALLET_STOPPED;
    304     pthread_cond_broadcast(&wh->state_changed);
    305     pthread_mutex_unlock(&wh->handle_mutex);
    306     return NULL;
    307 
    308 fail:
    309     if (handlers_initialized) {
    310         js_std_free_handlers(wh->rt);
    311     }
    312     if (wh->ctx) {
    313         JS_FreeContext(wh->ctx);
    314     }
    315     if (wh->rt) {
    316         JS_FreeRuntime(wh->rt);
    317     }
    318     pthread_mutex_lock(&wh->handle_mutex);
    319     wh->ctx = NULL;
    320     wh->rt = NULL;
    321     wh->state = WALLET_FAILED;
    322     pthread_cond_broadcast(&wh->state_changed);
    323     pthread_mutex_unlock(&wh->handle_mutex);
    324     return NULL;
    325 }
    326 
    327 #define WALLET_THREAD_STACK_SIZE (1024 * 512)
    328 
    329 int
    330 TALER_WALLET_run (struct TALER_WALLET_Instance *wh)
    331 {
    332     pthread_t wallet_thread;
    333     pthread_attr_t tattr;
    334 
    335     if (!wh) {
    336         return -1;
    337     }
    338     pthread_mutex_lock(&wh->handle_mutex);
    339     if (wh->state != WALLET_CREATED) {
    340         pthread_mutex_unlock(&wh->handle_mutex);
    341         return -1;
    342     }
    343     wh->state = WALLET_STARTING;
    344 
    345     if (0 != pthread_attr_init(&tattr)) {
    346         wh->state = WALLET_CREATED;
    347         pthread_mutex_unlock(&wh->handle_mutex);
    348         fprintf(stderr, "could not initialize pthread attr\n");
    349         return -1;
    350     }
    351 
    352     if (0 != pthread_attr_setstacksize(&tattr, WALLET_THREAD_STACK_SIZE)) {
    353         pthread_attr_destroy(&tattr);
    354         wh->state = WALLET_CREATED;
    355         pthread_mutex_unlock(&wh->handle_mutex);
    356         fprintf(stderr, "could not set stack size\n");
    357         return -1;
    358     }
    359 
    360     if (0 != pthread_create(&wallet_thread, &tattr, run, wh)) {
    361         pthread_attr_destroy(&tattr);
    362         wh->state = WALLET_CREATED;
    363         pthread_mutex_unlock(&wh->handle_mutex);
    364         fprintf(stderr, "could not create wallet thread\n");
    365         return -1;
    366     }
    367 
    368     wh->wallet_thread = wallet_thread;
    369     wh->wallet_thread_valid = 1;
    370 
    371     pthread_attr_destroy(&tattr);
    372     pthread_mutex_unlock(&wh->handle_mutex);
    373     return 0;
    374 }
    375 
    376 
    377 void TALER_WALLET_join(struct TALER_WALLET_Instance *wh)
    378 {
    379     pthread_t thread;
    380 
    381     if (!wh) {
    382         return;
    383     }
    384     pthread_mutex_lock(&wh->handle_mutex);
    385     if (!wh->wallet_thread_valid) {
    386         pthread_mutex_unlock(&wh->handle_mutex);
    387         return;
    388     }
    389     thread = wh->wallet_thread;
    390     pthread_mutex_unlock(&wh->handle_mutex);
    391     if (!pthread_equal(pthread_self(), thread)) {
    392         pthread_join(thread, NULL);
    393         pthread_mutex_lock(&wh->handle_mutex);
    394         wh->wallet_thread_valid = 0;
    395         pthread_mutex_unlock(&wh->handle_mutex);
    396     }
    397 }
    398 
    399 
    400 void
    401 TALER_WALLET_destroy(struct TALER_WALLET_Instance *twi)
    402 {
    403     pthread_t thread;
    404     int have_thread;
    405 
    406     if (!twi) {
    407         return;
    408     }
    409     pthread_mutex_lock(&twi->handle_mutex);
    410     while (twi->state == WALLET_STARTING) {
    411         pthread_cond_wait(&twi->state_changed, &twi->handle_mutex);
    412     }
    413     have_thread = twi->wallet_thread_valid;
    414     thread = twi->wallet_thread;
    415     if (twi->state == WALLET_RUNNING && twi->ctx) {
    416         js_std_request_stop(twi->ctx);
    417     }
    418     pthread_mutex_unlock(&twi->handle_mutex);
    419 
    420     if (have_thread && pthread_equal(pthread_self(), thread)) {
    421         return;
    422     }
    423     if (have_thread) {
    424         pthread_join(thread, NULL);
    425     }
    426 #ifndef NO_CURL
    427     if (twi->owns_http_impl) {
    428         js_curl_http_client_destroy(twi->http_impl);
    429     }
    430 #endif
    431     pthread_cond_destroy(&twi->state_changed);
    432     pthread_mutex_destroy(&twi->handle_mutex);
    433     free(twi);
    434 }
    435 
    436 
    437 struct LogRedirectContext {
    438     int active;
    439     TALER_LogFn logfn;
    440     void *cls;
    441 };
    442 
    443 static struct LogRedirectContext redir_ctx;
    444 static int pfd[2];
    445 static pthread_t log_thr;
    446 
    447 static void *thread_func(void *cls) {
    448     ssize_t rdsz;
    449     char buf[1024];
    450 
    451     (void) cls;
    452     while ((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) {
    453         if (buf[rdsz - 1] == '\n') --rdsz;
    454         buf[rdsz] = 0;  /* add null-terminator */
    455         redir_ctx.logfn(redir_ctx.cls, 0, buf);
    456     }
    457     return 0;
    458 }
    459 
    460 
    461 int
    462 TALER_start_redirect_std(TALER_LogFn logfn, void *cls)
    463 {
    464     int saved_stdout;
    465     int saved_stderr;
    466 
    467     if (!logfn) {
    468         return -1;
    469     }
    470     if (redir_ctx.active) {
    471         return -2;
    472     }
    473     /* make stdout line-buffered and stderr unbuffered */
    474     setvbuf(stdout, 0, _IOLBF, 0);
    475     setvbuf(stderr, 0, _IONBF, 0);
    476 
    477     /* create the pipe and redirect stdout and stderr */
    478     if (pipe(pfd) != 0) {
    479         return -1;
    480     }
    481     saved_stdout = dup(STDOUT_FILENO);
    482     saved_stderr = dup(STDERR_FILENO);
    483     if (saved_stdout < 0 || saved_stderr < 0 ||
    484         dup2(pfd[1], STDOUT_FILENO) < 0 ||
    485         dup2(pfd[1], STDERR_FILENO) < 0) {
    486         if (saved_stdout >= 0) {
    487             (void) dup2(saved_stdout, STDOUT_FILENO);
    488         }
    489         if (saved_stderr >= 0) {
    490             (void) dup2(saved_stderr, STDERR_FILENO);
    491         }
    492         if (saved_stdout >= 0) {
    493             close(saved_stdout);
    494         }
    495         if (saved_stderr >= 0) {
    496             close(saved_stderr);
    497         }
    498         close(pfd[0]);
    499         close(pfd[1]);
    500         return -1;
    501     }
    502     redir_ctx.cls = cls;
    503     redir_ctx.logfn = logfn;
    504     redir_ctx.active = 1;
    505 
    506     /* spawn the logging thread */
    507     if (pthread_create(&log_thr, 0, thread_func, 0) != 0) {
    508         (void) dup2(saved_stdout, STDOUT_FILENO);
    509         (void) dup2(saved_stderr, STDERR_FILENO);
    510         close(saved_stdout);
    511         close(saved_stderr);
    512         close(pfd[0]);
    513         close(pfd[1]);
    514         redir_ctx.active = 0;
    515         return -1;
    516     }
    517     close(saved_stdout);
    518     close(saved_stderr);
    519     close(pfd[1]);
    520     pthread_detach(log_thr);
    521     return 0;
    522 }
    523 
    524 void
    525 TALER_set_http_client_implementation(struct TALER_WALLET_Instance *twi,
    526                                      struct JSHttpClientImplementation *impl)
    527 {
    528   pthread_mutex_lock(&twi->handle_mutex);
    529   if (twi->state != WALLET_CREATED) {
    530       pthread_mutex_unlock(&twi->handle_mutex);
    531       return;
    532   }
    533 #ifndef NO_CURL
    534   if (twi->owns_http_impl) {
    535       js_curl_http_client_destroy(twi->http_impl);
    536   }
    537 #endif
    538   twi->http_impl = impl;
    539   twi->owns_http_impl = 0;
    540   pthread_mutex_unlock(&twi->handle_mutex);
    541 }
    542 
    543 #ifndef NO_CURL
    544 void
    545 TALER_set_curl_http_client(struct TALER_WALLET_Instance *twi)
    546 {
    547     struct JSHttpClientImplementation *client = js_curl_http_client_create();
    548     TALER_set_http_client_implementation(twi, client);
    549     pthread_mutex_lock(&twi->handle_mutex);
    550     if (twi->http_impl == client) {
    551         twi->owns_http_impl = client != NULL;
    552     } else {
    553         js_curl_http_client_destroy(client);
    554     }
    555     pthread_mutex_unlock(&twi->handle_mutex);
    556 }
    557 #endif
    558 
    559 #ifdef __APPLE__
    560 #pragma mark -
    561 #endif
    562 struct JSHttpClientImplementation *
    563 TALER_pack_http_client_implementation(JSHttpReqCreateFn req_create,
    564                                       JSHttpReqCancelFn req_cancel,
    565                                       void *handler_p)
    566 {
    567     // will (and should!) crash if this tiny malloc fails
    568     struct JSHttpClientImplementation *impl = malloc(sizeof *impl);
    569 
    570     if (!impl) {
    571         return NULL;
    572     }
    573     impl->cls = handler_p;
    574     impl->req_create = req_create;
    575     impl->req_cancel = req_cancel;
    576     return impl;
    577 }