mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2024-12-27 07:11:45 +03:00
409 lines
12 KiB
C
409 lines
12 KiB
C
#include <common/cs_dbg.h>
|
|
#include <toolbox/stream/file_stream.h>
|
|
#include <loader/firmware_api/firmware_api.h>
|
|
#include <flipper_application/api_hashtable/api_hashtable.h>
|
|
#include <flipper_application/plugins/composite_resolver.h>
|
|
#include <furi_hal.h>
|
|
#include "plugin_api/app_api_interface.h"
|
|
#include "js_thread.h"
|
|
#include "js_thread_i.h"
|
|
#include "js_modules.h"
|
|
|
|
#define TAG "JS"
|
|
|
|
struct JsThread {
|
|
FuriThread* thread;
|
|
FuriString* path;
|
|
CompositeApiResolver* resolver;
|
|
JsThreadCallback app_callback;
|
|
void* context;
|
|
JsModules* modules;
|
|
};
|
|
|
|
static void js_str_print(FuriString* msg_str, struct mjs* mjs) {
|
|
size_t num_args = mjs_nargs(mjs);
|
|
for(size_t i = 0; i < num_args; i++) {
|
|
char* name = NULL;
|
|
size_t name_len = 0;
|
|
int need_free = 0;
|
|
mjs_val_t arg = mjs_arg(mjs, i);
|
|
mjs_err_t err = mjs_to_string(mjs, &arg, &name, &name_len, &need_free);
|
|
if(err != MJS_OK) {
|
|
furi_string_cat_printf(msg_str, "err %s ", mjs_strerror(mjs, err));
|
|
} else {
|
|
furi_string_cat_printf(msg_str, "%s ", name);
|
|
}
|
|
if(need_free) {
|
|
free(name);
|
|
name = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void js_print(struct mjs* mjs) {
|
|
FuriString* msg_str = furi_string_alloc();
|
|
js_str_print(msg_str, mjs);
|
|
|
|
JsThread* worker = mjs_get_context(mjs);
|
|
furi_assert(worker);
|
|
if(worker->app_callback) {
|
|
worker->app_callback(JsThreadEventPrint, furi_string_get_cstr(msg_str), worker->context);
|
|
} else {
|
|
FURI_LOG_D(TAG, "%s\r\n", furi_string_get_cstr(msg_str));
|
|
}
|
|
|
|
furi_string_free(msg_str);
|
|
|
|
mjs_return(mjs, MJS_UNDEFINED);
|
|
}
|
|
|
|
static void js_console_log(struct mjs* mjs) {
|
|
FuriString* msg_str = furi_string_alloc();
|
|
js_str_print(msg_str, mjs);
|
|
FURI_LOG_I(TAG, "%s", furi_string_get_cstr(msg_str));
|
|
furi_string_free(msg_str);
|
|
mjs_return(mjs, MJS_UNDEFINED);
|
|
}
|
|
|
|
static void js_console_warn(struct mjs* mjs) {
|
|
FuriString* msg_str = furi_string_alloc();
|
|
js_str_print(msg_str, mjs);
|
|
FURI_LOG_W(TAG, "%s", furi_string_get_cstr(msg_str));
|
|
furi_string_free(msg_str);
|
|
mjs_return(mjs, MJS_UNDEFINED);
|
|
}
|
|
|
|
static void js_console_error(struct mjs* mjs) {
|
|
FuriString* msg_str = furi_string_alloc();
|
|
js_str_print(msg_str, mjs);
|
|
FURI_LOG_E(TAG, "%s", furi_string_get_cstr(msg_str));
|
|
furi_string_free(msg_str);
|
|
mjs_return(mjs, MJS_UNDEFINED);
|
|
}
|
|
|
|
static void js_console_debug(struct mjs* mjs) {
|
|
FuriString* msg_str = furi_string_alloc();
|
|
js_str_print(msg_str, mjs);
|
|
FURI_LOG_D(TAG, "%s", furi_string_get_cstr(msg_str));
|
|
furi_string_free(msg_str);
|
|
mjs_return(mjs, MJS_UNDEFINED);
|
|
}
|
|
|
|
static void js_exit_flag_poll(struct mjs* mjs) {
|
|
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, 0);
|
|
if(flags & FuriFlagError) {
|
|
return;
|
|
}
|
|
if(flags & ThreadEventStop) {
|
|
mjs_exit(mjs);
|
|
}
|
|
}
|
|
|
|
bool js_delay_with_flags(struct mjs* mjs, uint32_t time) {
|
|
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, time);
|
|
if(flags & FuriFlagError) {
|
|
return false;
|
|
}
|
|
if(flags & ThreadEventStop) {
|
|
mjs_exit(mjs);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void js_flags_set(struct mjs* mjs, uint32_t flags) {
|
|
JsThread* worker = mjs_get_context(mjs);
|
|
furi_assert(worker);
|
|
furi_thread_flags_set(furi_thread_get_id(worker->thread), flags);
|
|
}
|
|
|
|
uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags_mask, uint32_t timeout) {
|
|
flags_mask |= ThreadEventStop;
|
|
uint32_t flags = furi_thread_flags_get();
|
|
furi_check((flags & FuriFlagError) == 0);
|
|
if(flags == 0) {
|
|
flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout);
|
|
} else {
|
|
uint32_t state = furi_thread_flags_clear(flags & flags_mask);
|
|
furi_check((state & FuriFlagError) == 0);
|
|
}
|
|
|
|
if(flags & FuriFlagError) {
|
|
return 0;
|
|
}
|
|
if(flags & ThreadEventStop) {
|
|
mjs_exit(mjs);
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
static void js_delay(struct mjs* mjs) {
|
|
bool args_correct = false;
|
|
int ms = 0;
|
|
|
|
if(mjs_nargs(mjs) == 1) {
|
|
mjs_val_t arg = mjs_arg(mjs, 0);
|
|
if(mjs_is_number(arg)) {
|
|
ms = mjs_get_int(mjs, arg);
|
|
args_correct = true;
|
|
}
|
|
}
|
|
if(!args_correct) {
|
|
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
|
mjs_return(mjs, MJS_UNDEFINED);
|
|
return;
|
|
}
|
|
js_delay_with_flags(mjs, ms);
|
|
mjs_return(mjs, MJS_UNDEFINED);
|
|
}
|
|
|
|
static void* js_dlsym(void* handle, const char* name) {
|
|
CompositeApiResolver* resolver = handle;
|
|
Elf32_Addr addr = 0;
|
|
uint32_t hash = elf_symbolname_hash(name);
|
|
const ElfApiInterface* api = composite_api_resolver_get(resolver);
|
|
|
|
if(!api->resolver_callback(api, hash, &addr)) {
|
|
FURI_LOG_E(TAG, "FFI: cannot find \"%s\"", name);
|
|
return NULL;
|
|
}
|
|
|
|
return (void*)addr;
|
|
}
|
|
|
|
static void js_ffi_address(struct mjs* mjs) {
|
|
mjs_val_t name_v = mjs_arg(mjs, 0);
|
|
size_t len;
|
|
const char* name = mjs_get_string(mjs, &name_v, &len);
|
|
void* addr = mjs_ffi_resolve(mjs, name);
|
|
mjs_return(mjs, mjs_mk_foreign(mjs, addr));
|
|
}
|
|
|
|
static void js_require(struct mjs* mjs) {
|
|
mjs_val_t name_v = mjs_arg(mjs, 0);
|
|
size_t len;
|
|
const char* name = mjs_get_string(mjs, &name_v, &len);
|
|
mjs_val_t req_object = MJS_UNDEFINED;
|
|
if((len == 0) || (name == NULL)) {
|
|
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "String argument is expected");
|
|
} else {
|
|
JsThread* worker = mjs_get_context(mjs);
|
|
furi_assert(worker);
|
|
req_object = js_module_require(worker->modules, name, len);
|
|
}
|
|
mjs_return(mjs, req_object);
|
|
}
|
|
|
|
static void js_global_to_string(struct mjs* mjs) {
|
|
double num = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
|
char tmp_str[] = "-2147483648";
|
|
itoa(num, tmp_str, 10);
|
|
mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true);
|
|
mjs_return(mjs, ret);
|
|
}
|
|
|
|
static void js_global_to_hex_string(struct mjs* mjs) {
|
|
double num = mjs_get_int(mjs, mjs_arg(mjs, 0));
|
|
char tmp_str[] = "-FFFFFFFF";
|
|
itoa(num, tmp_str, 16);
|
|
mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true);
|
|
mjs_return(mjs, ret);
|
|
}
|
|
|
|
static void js_parse_int(struct mjs* mjs) {
|
|
mjs_val_t arg = mjs_arg(mjs, 0);
|
|
if(!mjs_is_string(arg)) {
|
|
mjs_return(mjs, mjs_mk_number(mjs, 0));
|
|
return;
|
|
}
|
|
size_t str_len = 0;
|
|
const char* str = mjs_get_string(mjs, &arg, &str_len);
|
|
if((str_len == 0) || (str == NULL)) {
|
|
mjs_return(mjs, mjs_mk_number(mjs, 0));
|
|
return;
|
|
}
|
|
|
|
int32_t num = 0;
|
|
int32_t sign = 1;
|
|
size_t i = 0;
|
|
|
|
if(str[0] == '-') {
|
|
sign = -1;
|
|
i = 1;
|
|
} else if(str[0] == '+') {
|
|
i = 1;
|
|
}
|
|
|
|
for(; i < str_len; i++) {
|
|
if(str[i] >= '0' && str[i] <= '9') {
|
|
num = num * 10 + (str[i] - '0');
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
num *= sign;
|
|
|
|
mjs_return(mjs, mjs_mk_number(mjs, num));
|
|
}
|
|
|
|
static void js_to_upper_case(struct mjs* mjs) {
|
|
mjs_val_t arg0 = mjs_arg(mjs, 0);
|
|
|
|
size_t str_len;
|
|
const char* str = NULL;
|
|
if(mjs_is_string(arg0)) {
|
|
str = mjs_get_string(mjs, &arg0, &str_len);
|
|
}
|
|
if(!str) {
|
|
mjs_return(mjs, MJS_UNDEFINED);
|
|
return;
|
|
}
|
|
|
|
char* upperStr = strdup(str);
|
|
for(size_t i = 0; i < str_len; i++) {
|
|
upperStr[i] = toupper(upperStr[i]);
|
|
}
|
|
|
|
mjs_val_t resultStr = mjs_mk_string(mjs, upperStr, ~0, true);
|
|
free(upperStr);
|
|
mjs_return(mjs, resultStr);
|
|
}
|
|
|
|
static void js_to_lower_case(struct mjs* mjs) {
|
|
mjs_val_t arg0 = mjs_arg(mjs, 0);
|
|
|
|
size_t str_len;
|
|
const char* str = NULL;
|
|
if(mjs_is_string(arg0)) {
|
|
str = mjs_get_string(mjs, &arg0, &str_len);
|
|
}
|
|
if(!str) {
|
|
mjs_return(mjs, MJS_UNDEFINED);
|
|
return;
|
|
}
|
|
|
|
char* lowerStr = strdup(str);
|
|
for(size_t i = 0; i < str_len; i++) {
|
|
lowerStr[i] = tolower(lowerStr[i]);
|
|
}
|
|
|
|
mjs_val_t resultStr = mjs_mk_string(mjs, lowerStr, ~0, true);
|
|
free(lowerStr);
|
|
mjs_return(mjs, resultStr);
|
|
}
|
|
|
|
#ifdef JS_DEBUG
|
|
static void js_dump_write_callback(void* ctx, const char* format, ...) {
|
|
File* file = ctx;
|
|
furi_assert(ctx);
|
|
|
|
FuriString* str = furi_string_alloc();
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
furi_string_vprintf(str, format, args);
|
|
furi_string_cat(str, "\n");
|
|
va_end(args);
|
|
|
|
storage_file_write(file, furi_string_get_cstr(str), furi_string_size(str));
|
|
furi_string_free(str);
|
|
}
|
|
#endif
|
|
|
|
static int32_t js_thread(void* arg) {
|
|
JsThread* worker = arg;
|
|
worker->resolver = composite_api_resolver_alloc();
|
|
composite_api_resolver_add(worker->resolver, firmware_api_interface);
|
|
composite_api_resolver_add(worker->resolver, application_api_interface);
|
|
|
|
struct mjs* mjs = mjs_create(worker);
|
|
worker->modules = js_modules_create(mjs, worker->resolver);
|
|
mjs_val_t global = mjs_get_global(mjs);
|
|
mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print));
|
|
mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay));
|
|
mjs_set(mjs, global, "to_string", ~0, MJS_MK_FN(js_global_to_string));
|
|
mjs_set(mjs, global, "to_hex_string", ~0, MJS_MK_FN(js_global_to_hex_string));
|
|
mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address));
|
|
mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require));
|
|
mjs_set(mjs, global, "parse_int", ~0, MJS_MK_FN(js_parse_int));
|
|
mjs_set(mjs, global, "to_upper_case", ~0, MJS_MK_FN(js_to_upper_case));
|
|
mjs_set(mjs, global, "to_lower_case", ~0, MJS_MK_FN(js_to_lower_case));
|
|
|
|
mjs_val_t console_obj = mjs_mk_object(mjs);
|
|
mjs_set(mjs, console_obj, "log", ~0, MJS_MK_FN(js_console_log));
|
|
mjs_set(mjs, console_obj, "warn", ~0, MJS_MK_FN(js_console_warn));
|
|
mjs_set(mjs, console_obj, "error", ~0, MJS_MK_FN(js_console_error));
|
|
mjs_set(mjs, console_obj, "debug", ~0, MJS_MK_FN(js_console_debug));
|
|
mjs_set(mjs, global, "console", ~0, console_obj);
|
|
|
|
mjs_set_ffi_resolver(mjs, js_dlsym, worker->resolver);
|
|
|
|
mjs_set_exec_flags_poller(mjs, js_exit_flag_poll);
|
|
|
|
mjs_err_t err = mjs_exec_file(mjs, furi_string_get_cstr(worker->path), NULL);
|
|
|
|
#ifdef JS_DEBUG
|
|
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
|
FuriString* dump_path = furi_string_alloc_set(worker->path);
|
|
furi_string_cat(dump_path, ".lst");
|
|
|
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
|
File* file = storage_file_alloc(storage);
|
|
|
|
if(storage_file_open(
|
|
file, furi_string_get_cstr(dump_path), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
|
mjs_disasm_all(mjs, js_dump_write_callback, file);
|
|
}
|
|
|
|
storage_file_close(file);
|
|
storage_file_free(file);
|
|
furi_record_close(RECORD_STORAGE);
|
|
|
|
furi_string_free(dump_path);
|
|
}
|
|
#endif
|
|
|
|
if(err != MJS_OK) {
|
|
FURI_LOG_E(TAG, "Exec error: %s", mjs_strerror(mjs, err));
|
|
if(worker->app_callback) {
|
|
worker->app_callback(JsThreadEventError, mjs_strerror(mjs, err), worker->context);
|
|
}
|
|
const char* stack_trace = mjs_get_stack_trace(mjs);
|
|
if(stack_trace != NULL) {
|
|
FURI_LOG_E(TAG, "Stack trace:\n%s", stack_trace);
|
|
if(worker->app_callback) {
|
|
worker->app_callback(JsThreadEventErrorTrace, stack_trace, worker->context);
|
|
}
|
|
}
|
|
} else {
|
|
if(worker->app_callback) {
|
|
worker->app_callback(JsThreadEventDone, NULL, worker->context);
|
|
}
|
|
}
|
|
|
|
js_modules_destroy(worker->modules);
|
|
mjs_destroy(mjs);
|
|
|
|
composite_api_resolver_free(worker->resolver);
|
|
|
|
return 0;
|
|
}
|
|
|
|
JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context) {
|
|
JsThread* worker = malloc(sizeof(JsThread)); //-V799
|
|
worker->path = furi_string_alloc_set(script_path);
|
|
worker->thread = furi_thread_alloc_ex("JsThread", 8 * 1024, js_thread, worker);
|
|
worker->app_callback = callback;
|
|
worker->context = context;
|
|
furi_thread_start(worker->thread);
|
|
return worker;
|
|
}
|
|
|
|
void js_thread_stop(JsThread* worker) {
|
|
furi_thread_flags_set(furi_thread_get_id(worker->thread), ThreadEventStop);
|
|
furi_thread_join(worker->thread);
|
|
furi_thread_free(worker->thread);
|
|
furi_string_free(worker->path);
|
|
free(worker);
|
|
}
|