tildefriends/src/util.js.c

591 lines
18 KiB
C

#include "util.js.h"
#include "bip39.h"
#include "log.h"
#include "mem.h"
#include "task.h"
#include "trace.h"
#include "backtrace.h"
#include "openssl/sha.h"
#include "picohttpparser.h"
#include "sodium/utils.h"
#include "uv.h"
#include <string.h>
#if defined(__ANDROID__)
#include <unwind.h>
#elif !defined(_WIN32) && !defined(__HAIKU__)
#include <execinfo.h>
#endif
static JSValue _util_utf8_encode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
size_t length = 0;
const char* value = JS_ToCStringLen(context, &length, argv[0]);
JSValue typed_array = tf_util_new_uint8_array(context, (const uint8_t*)value, length);
JS_FreeCString(context, value);
return typed_array;
}
static JSValue _util_utf8_decode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_NULL;
size_t length;
if (JS_IsString(argv[0]))
{
result = JS_DupValue(context, argv[0]);
}
else
{
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
if (array)
{
result = JS_NewStringLen(context, (const char*)array, length);
}
else
{
size_t offset;
size_t element_size;
JSValue buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
size_t size;
if (!JS_IsException(buffer))
{
array = tf_util_try_get_array_buffer(context, &size, buffer);
if (array)
{
result = JS_NewStringLen(context, (const char*)array, size);
}
}
JS_FreeValue(context, buffer);
}
}
return result;
}
JSValue tf_util_utf8_decode(JSContext* context, JSValue value)
{
return _util_utf8_decode(context, JS_NULL, 1, &value);
}
static JSValue _util_base64_encode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
JSValue buffer = JS_UNDEFINED;
size_t length = 0;
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
if (!array)
{
size_t offset;
size_t element_size;
buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
if (!JS_IsException(buffer))
{
array = tf_util_try_get_array_buffer(context, &length, buffer);
}
}
if (array)
{
char* encoded = tf_malloc(length * 4);
int r = tf_base64_encode(array, length, encoded, length * 4);
if (r >= 0)
{
result = JS_NewStringLen(context, encoded, r);
}
tf_free(encoded);
}
else
{
const char* value = JS_ToCStringLen(context, &length, argv[0]);
char* encoded = tf_malloc(length * 4);
int r = tf_base64_encode((const uint8_t*)value, length, encoded, length * 4);
if (r >= 0)
{
result = JS_NewStringLen(context, encoded, r);
}
tf_free(encoded);
JS_FreeCString(context, value);
}
JS_FreeValue(context, buffer);
return result;
}
static JSValue _util_base64_decode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
size_t length = 0;
const char* value = JS_ToCStringLen(context, &length, argv[0]);
uint8_t* decoded = tf_malloc(length);
int r = tf_base64_decode(value, length, decoded, length);
if (r >= 0)
{
result = tf_util_new_uint8_array(context, decoded, r);
}
tf_free(decoded);
JS_FreeCString(context, value);
return result;
}
static JSValue _util_bip39_words(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
JSValue buffer = JS_UNDEFINED;
size_t length;
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
if (!array)
{
size_t offset;
size_t element_size;
buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
if (!JS_IsException(buffer))
{
array = tf_util_try_get_array_buffer(context, &length, buffer);
}
}
char words[2048] = "";
if (array && tf_bip39_bytes_to_words(array, length, words, sizeof(words)))
{
result = JS_NewString(context, words);
}
JS_FreeValue(context, buffer);
return result;
}
static JSValue _util_bip39_bytes(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
const char* words = JS_ToCString(context, argv[0]);
uint8_t bytes[32] = { 0 };
bool success = tf_bip39_words_to_bytes(words, bytes, sizeof(bytes));
JS_FreeCString(context, words);
if (success)
{
return tf_util_new_uint8_array(context, bytes, sizeof(bytes));
}
return JS_UNDEFINED;
}
uint8_t* tf_util_try_get_array_buffer(JSContext* context, size_t* psize, JSValueConst obj)
{
uint8_t* result = JS_GetArrayBuffer(context, psize, obj);
JS_FreeValue(context, JS_GetException(context));
return result;
}
JSValue tf_util_try_get_typed_array_buffer(JSContext* context, JSValueConst obj, size_t* pbyte_offset, size_t* pbyte_length, size_t* pbytes_per_element)
{
JSValue result = JS_GetTypedArrayBuffer(context, obj, pbyte_offset, pbyte_length, pbytes_per_element);
JS_FreeValue(context, JS_GetException(context));
return result;
}
static JSValue _util_print(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_task_t* task = JS_GetContextOpaque(context);
if (task)
{
tf_printf("Task[%p:%s]>", task, tf_task_get_name(task));
tf_task_print(task, argc, argv);
}
for (int i = 0; i < argc; ++i)
{
if (JS_IsNull(argv[i]))
{
tf_printf(" null");
}
else
{
const char* value = JS_ToCString(context, argv[i]);
tf_printf(" %s", value);
JS_FreeCString(context, value);
}
}
tf_printf("\n");
return JS_NULL;
}
bool tf_util_report_error(JSContext* context, JSValue value)
{
bool is_error = false;
if (JS_IsError(context, value))
{
const char* string = JS_ToCString(context, value);
tf_printf("ERROR: %s\n", string);
JS_FreeCString(context, string);
JSValue stack = JS_GetPropertyStr(context, value, "stack");
if (!JS_IsUndefined(stack))
{
const char* stack_str = JS_ToCString(context, stack);
tf_printf("%s\n", stack_str);
JS_FreeCString(context, stack_str);
}
JS_FreeValue(context, stack);
tf_task_t* task = tf_task_get(context);
tf_task_send_error_to_parent(task, value);
is_error = true;
}
else if (JS_IsException(value))
{
tf_task_t* task = tf_task_get(context);
if (!tf_task_send_error_to_parent(task, value))
{
JSValue exception = JS_GetException(context);
tf_util_report_error(context, exception);
JS_FreeValue(context, exception);
}
is_error = true;
}
return is_error;
}
static JSValue _util_parseHttpRequest(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
const char* method = NULL;
size_t method_length = 0;
const char* path = NULL;
size_t path_length = 0;
int minor_version = 0;
struct phr_header headers[100];
size_t header_count = sizeof(headers) / sizeof(*headers);
int previous_length = 0;
JS_ToInt32(context, &previous_length, argv[1]);
JSValue buffer = JS_UNDEFINED;
size_t length;
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
if (!array)
{
size_t offset;
size_t element_size;
buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
if (!JS_IsException(buffer))
{
array = tf_util_try_get_array_buffer(context, &length, buffer);
}
}
if (array)
{
int parse_result = phr_parse_request((const char*)array, length, &method, &method_length, &path, &path_length, &minor_version, headers, &header_count, previous_length);
if (parse_result > 0)
{
result = JS_NewObject(context);
JS_SetPropertyStr(context, result, "bytes_parsed", JS_NewInt32(context, parse_result));
JS_SetPropertyStr(context, result, "minor_version", JS_NewInt32(context, minor_version));
JS_SetPropertyStr(context, result, "method", JS_NewStringLen(context, method, method_length));
JS_SetPropertyStr(context, result, "path", JS_NewStringLen(context, path, path_length));
JSValue header_object = JS_NewObject(context);
for (int i = 0; i < (int)header_count; i++)
{
char name[256];
snprintf(name, sizeof(name), "%.*s", (int)headers[i].name_len, headers[i].name);
JS_SetPropertyStr(context, header_object, name, JS_NewStringLen(context, headers[i].value, headers[i].value_len));
}
JS_SetPropertyStr(context, result, "headers", header_object);
}
else
{
result = JS_NewInt32(context, parse_result);
}
}
else
{
result = JS_ThrowTypeError(context, "Could not convert argument to array.");
}
JS_FreeValue(context, buffer);
return result;
}
static JSValue _util_parseHttpResponse(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
int status = 0;
int minor_version = 0;
const char* message = NULL;
size_t message_length = 0;
struct phr_header headers[100];
size_t header_count = sizeof(headers) / sizeof(*headers);
int previous_length = 0;
JS_ToInt32(context, &previous_length, argv[1]);
JSValue buffer = JS_UNDEFINED;
size_t length;
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
if (!array)
{
size_t offset;
size_t element_size;
buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
if (!JS_IsException(buffer))
{
array = tf_util_try_get_array_buffer(context, &length, buffer);
}
}
if (array)
{
int parse_result = phr_parse_response((const char*)array, length, &minor_version, &status, &message, &message_length, headers, &header_count, previous_length);
if (parse_result > 0)
{
result = JS_NewObject(context);
JS_SetPropertyStr(context, result, "bytes_parsed", JS_NewInt32(context, parse_result));
JS_SetPropertyStr(context, result, "minor_version", JS_NewInt32(context, minor_version));
JS_SetPropertyStr(context, result, "status", JS_NewInt32(context, status));
JS_SetPropertyStr(context, result, "message", JS_NewStringLen(context, message, message_length));
JSValue header_object = JS_NewObject(context);
for (int i = 0; i < (int)header_count; i++)
{
char name[256];
snprintf(name, sizeof(name), "%.*s", (int)headers[i].name_len, headers[i].name);
JS_SetPropertyStr(context, header_object, name, JS_NewStringLen(context, headers[i].value, headers[i].value_len));
}
JS_SetPropertyStr(context, result, "headers", header_object);
}
else
{
result = JS_NewInt32(context, parse_result);
}
}
else
{
result = JS_ThrowTypeError(context, "Could not convert argument to array.");
}
JS_FreeValue(context, buffer);
return result;
}
static JSValue _util_sha1_digest(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
size_t length = 0;
const char* value = JS_ToCStringLen(context, &length, argv[0]);
unsigned char digest[SHA_DIGEST_LENGTH] = { 0 };
SHA1((const unsigned char*)value, length, digest);
JS_FreeCString(context, value);
return JS_NewArrayBufferCopy(context, digest, sizeof(digest));
}
JSValue tf_util_new_uint8_array(JSContext* context, const uint8_t* data, size_t size)
{
JSValue array_buffer = JS_NewArrayBufferCopy(context, data, size);
JSValue global = JS_GetGlobalObject(context);
JSValue constructor = JS_GetPropertyStr(context, global, "Uint8Array");
JSValue result = JS_CallConstructor(context, constructor, 1, &array_buffer);
JS_FreeValue(context, constructor);
JS_FreeValue(context, global);
JS_FreeValue(context, array_buffer);
return result;
}
static JSValue _util_mask_bytes(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
uint32_t mask = 0;
JS_ToUint32(context, &mask, argv[1]);
uint64_t double_mask = ((uint64_t)mask << 32) | mask;
size_t offset = 0;
size_t length = 0;
size_t element_size = 0;
JSValue buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
if (!JS_IsException(buffer))
{
size_t size = 0;
const uint8_t* array = tf_util_try_get_array_buffer(context, &size, buffer);
if (array)
{
uint8_t* copy = tf_malloc(size);
size_t i = 0;
for (; i + sizeof(double_mask) < size; i += sizeof(double_mask))
{
((uint64_t*)copy)[i / sizeof(double_mask)] = ((const uint64_t*)array)[i / sizeof(double_mask)] ^ double_mask;
}
for (; i + sizeof(mask) < size; i += sizeof(mask))
{
((uint32_t*)copy)[i / sizeof(mask)] = ((const uint32_t*)array)[i / sizeof(mask)] ^ mask;
}
for (; i < size; i++)
{
copy[i] = array[i] ^ ((mask >> (8 * (i % 4))) & 0xff);
}
result = tf_util_new_uint8_array(context, copy, size);
tf_free(copy);
}
}
JS_FreeValue(context, buffer);
return result;
}
void tf_util_register(JSContext* context)
{
JSValue global = JS_GetGlobalObject(context);
JS_SetPropertyStr(context, global, "utf8Decode", JS_NewCFunction(context, _util_utf8_decode, "utf8Decode", 1));
JS_SetPropertyStr(context, global, "utf8Encode", JS_NewCFunction(context, _util_utf8_encode, "utf8Encode", 1));
JS_SetPropertyStr(context, global, "base64Decode", JS_NewCFunction(context, _util_base64_decode, "base64Decode", 1));
JS_SetPropertyStr(context, global, "base64Encode", JS_NewCFunction(context, _util_base64_encode, "base64Encode", 1));
JS_SetPropertyStr(context, global, "bip39Words", JS_NewCFunction(context, _util_bip39_words, "bip39Words", 1));
JS_SetPropertyStr(context, global, "bip39Bytes", JS_NewCFunction(context, _util_bip39_bytes, "bip39Bytes", 1));
JS_SetPropertyStr(context, global, "print", JS_NewCFunction(context, _util_print, "print", 1));
JS_SetPropertyStr(context, global, "parseHttpRequest", JS_NewCFunction(context, _util_parseHttpRequest, "parseHttpRequest", 2));
JS_SetPropertyStr(context, global, "parseHttpResponse", JS_NewCFunction(context, _util_parseHttpResponse, "parseHttpResponse", 2));
JS_SetPropertyStr(context, global, "sha1Digest", JS_NewCFunction(context, _util_sha1_digest, "sha1Digest", 1));
JS_SetPropertyStr(context, global, "maskBytes", JS_NewCFunction(context, _util_mask_bytes, "maskBytes", 2));
JS_FreeValue(context, global);
}
int tf_util_get_length(JSContext* context, JSValue value)
{
JSValue length = JS_GetPropertyStr(context, value, "length");
int result = 0;
JS_ToInt32(context, &result, length);
JS_FreeValue(context, length);
return result;
}
int tf_util_insert_index(const void* key, const void* base, size_t count, size_t size, int (*compare)(const void*, const void*))
{
int lower = 0;
int upper = count;
while (lower < upper && lower < (int)count)
{
int guess = (lower + upper) / 2;
int result = compare(key, ((char*)base) + size * guess);
if (result < 0)
{
upper = guess;
}
else if (result > 0)
{
lower = guess + 1;
}
else
{
return guess;
}
};
return lower;
}
size_t tf_base64_encode(const uint8_t* source, size_t source_length, char* out, size_t out_length)
{
sodium_bin2base64(out, out_length, source, source_length, sodium_base64_VARIANT_ORIGINAL);
return sodium_base64_ENCODED_LEN(source_length, sodium_base64_VARIANT_ORIGINAL) - 1;
}
size_t tf_base64_decode(const char* source, size_t source_length, uint8_t* out, size_t out_length)
{
size_t actual_length = 0;
return sodium_base642bin(out, out_length, source, source_length, NULL, &actual_length, NULL, sodium_base64_VARIANT_ORIGINAL) == 0 ? actual_length : 0;
}
static int _tf_util_backtrace_callback(void* data, uintptr_t pc, const char* filename, int line_number, const char* function)
{
char** stack = data;
char line[256];
int length = snprintf(line, sizeof(line), "%p %s:%d %s\n", (void*)pc, filename, line_number, function);
int current = *stack ? strlen(*stack) : 0;
*stack = tf_resize_vec(*stack, current + length + 1);
memcpy(*stack + current, line, length + 1);
return 0;
}
static void _tf_util_backtrace_error(void* data, const char* message, int error)
{
char** stack = data;
if (message)
{
int length = strlen(message);
int current = *stack ? strlen(*stack) : 0;
*stack = tf_resize_vec(*stack, current + length + 1);
memcpy(*stack + current, message, length + 1);
}
}
const char* tf_util_backtrace_to_string(void* const* buffer, int count)
{
extern struct backtrace_state* g_backtrace_state;
char* string = NULL;
for (int i = 0; i < count; i++)
{
backtrace_pcinfo(g_backtrace_state, (uintptr_t)buffer[i], _tf_util_backtrace_callback, _tf_util_backtrace_error, &string);
}
return string;
}
static int _tf_util_backtrace_single_callback(void* data, uintptr_t pc, const char* filename, int line_number, const char* function)
{
char** stack = data;
char line[256];
int length = snprintf(line, sizeof(line), "%s", function);
int current = *stack ? strlen(*stack) : 0;
*stack = tf_resize_vec(*stack, current + length + 1);
memcpy(*stack + current, line, length + 1);
return 0;
}
const char* tf_util_function_to_string(void* function)
{
extern struct backtrace_state* g_backtrace_state;
char* string = NULL;
backtrace_pcinfo(g_backtrace_state, (uintptr_t)function, _tf_util_backtrace_single_callback, _tf_util_backtrace_error, &string);
return string;
}
const char* tf_util_backtrace_string()
{
void* buffer[32];
int count = tf_util_backtrace(buffer, sizeof(buffer) / sizeof(*buffer));
return tf_util_backtrace_to_string(buffer, count);
}
#if defined(__ANDROID__)
typedef struct _android_backtrace_t
{
void** current;
void** end;
} android_backtrace_t;
static _Unwind_Reason_Code _android_unwind_callback(struct _Unwind_Context* context, void* arg)
{
android_backtrace_t* state = arg;
uintptr_t pc = _Unwind_GetIP(context);
if (pc)
{
if (state->current == state->end)
{
return _URC_END_OF_STACK;
}
else
{
*state->current++ = (void*)pc;
}
}
return _URC_NO_REASON;
}
#endif
int tf_util_backtrace(void** buffer, int count)
{
#ifdef _WIN32
return CaptureStackBackTrace(0, count, buffer, NULL);
#elif defined(__ANDROID__)
android_backtrace_t state = { .current = buffer, .end = buffer + count };
_Unwind_Backtrace(_android_unwind_callback, &state);
return state.current - buffer;
#elif defined(__HAIKU__)
return 0;
#else
return backtrace(buffer, count);
#endif
}