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 }