/*
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;
};
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);
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()", "");
printf("starting main loop\n");
js_std_loop(wh->ctx);
printf("done with main loop\n");
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;
}