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 }