#include "serialize.h" #include "mem.h" #include "task.h" #include "taskstub.js.h" #include "util.js.h" #include #include #include "quickjs-libc.h" #include typedef enum _serialize_type_t { kUndefined, kNull, kUninitialized, kBoolean, kInt32, kInt64, kNumber, kString, kArray, kArrayBuffer, kObject, kFunction, kError, kException, } serialize_type_t; typedef struct _buffer_t { char* data; size_t size; size_t capacity; } buffer_t; static bool _serialize_store(tf_task_t* task, tf_taskstub_t* to, buffer_t* buffer, JSValue value); static JSValue _serialize_load(tf_task_t* task, tf_taskstub_t* from, const char* buffer, size_t size); static bool _serialize_storeInternal(tf_task_t* task, tf_taskstub_t* to, buffer_t* buffer, JSValue value, int depth); static JSValue _serialize_loadInternal(tf_task_t* task, tf_taskstub_t* from, const char** buffer, size_t* size, int depth); static void _serialize_writeInt8(buffer_t* buffer, int8_t value); static void _serialize_writeInt32(buffer_t* buffer, int32_t value); static void _serialize_writeInt64(buffer_t* buffer, int64_t value); static void _serialize_writeDouble(buffer_t* buffer, double value); static int8_t _serialize_readInt8(const char** buffer, size_t* size); static int32_t _serialize_readInt32(const char** buffer, size_t* size); static int64_t _serialize_readInt64(const char** buffer, size_t* size); static double _serialize_readDouble(const char** buffer, size_t* size); void tf_serialize_store(tf_task_t* task, tf_taskstub_t* to, void** out_buffer, size_t* out_size, JSValue value) { buffer_t tmp = { 0 }; _serialize_store(task, to, &tmp, value); tmp.data = tf_realloc(tmp.data, tmp.size); *out_buffer = tmp.data; *out_size = tmp.size; } JSValue tf_serialize_load(tf_task_t* task, tf_taskstub_t* from, const char* buffer, size_t size) { return _serialize_load(task, from, buffer, size); } static void _buffer_append(buffer_t* buffer, const void* data, size_t size) { if (buffer->capacity < buffer->size + size) { size_t new_capacity = (size + buffer->capacity) * 2; buffer->data = tf_realloc(buffer->data, new_capacity); buffer->capacity = new_capacity; } memcpy((char*)buffer->data + buffer->size, data, size); buffer->size += size; } void _serialize_writeInt8(buffer_t* buffer, int8_t value) { _buffer_append(buffer, &value, sizeof(value)); } void _serialize_writeInt32(buffer_t* buffer, int32_t value) { _buffer_append(buffer, &value, sizeof(value)); } void _serialize_writeInt64(buffer_t* buffer, int64_t value) { _buffer_append(buffer, &value, sizeof(value)); } void _serialize_writeDouble(buffer_t* buffer, double value) { _buffer_append(buffer, &value, sizeof(value)); } static void _serialize_read(const char** buffer, size_t* size, void* target, size_t target_size) { assert(*size >= target_size); memcpy(target, *buffer, target_size); *buffer += target_size; *size -= target_size; } static int8_t _serialize_readInt8(const char** buffer, size_t* size) { int8_t result; _serialize_read(buffer, size, &result, sizeof(result)); return result; } int32_t _serialize_readInt32(const char** buffer, size_t* size) { int32_t result; _serialize_read(buffer, size, &result, sizeof(result)); return result; } int64_t _serialize_readInt64(const char** buffer, size_t* size) { int64_t result; _serialize_read(buffer, size, &result, sizeof(result)); return result; } double _serialize_readDouble(const char** buffer, size_t* size) { double result; _serialize_read(buffer, size, &result, sizeof(result)); return result; } static bool _serialize_store(tf_task_t* task, tf_taskstub_t* to, buffer_t* buffer, JSValue value) { return _serialize_storeInternal(task, to, buffer, value, 0); } static bool _serialize_storeInternal(tf_task_t* task, tf_taskstub_t* to, buffer_t* buffer, JSValue value, int depth) { JSContext* context = tf_task_get_context(task); size_t size; size_t offset; size_t element_size; JSValue typed; uint8_t* bytes; if (JS_IsUndefined(value)) { _serialize_writeInt32(buffer, kUndefined); } else if (JS_IsUninitialized(value)) { _serialize_writeInt32(buffer, kUninitialized); } else if (JS_IsNull(value)) { _serialize_writeInt32(buffer, kNull); } else if (JS_IsBool(value)) { _serialize_writeInt32(buffer, kBoolean); _serialize_writeInt8(buffer, JS_ToBool(tf_task_get_context(task), value) ? 1 : 0); } else if (JS_IsNumber(value)) { int64_t result = 0; if (JS_ToInt64(tf_task_get_context(task), &result, value) == 0) { _serialize_writeInt32(buffer, kInt64); _serialize_writeInt64(buffer, result); } else { fprintf(stderr, "Unable to store integer.\n"); } } else if (JS_IsNumber(value)) { double result = 0.0; if (JS_ToFloat64(tf_task_get_context(task), &result, value) == 0) { _serialize_writeInt32(buffer, kNumber); _serialize_writeDouble(buffer, result); } else { fprintf(stderr, "Unable to store number.\n"); } } else if (JS_IsException(value)) { JSValue exception = JS_GetException(context); const char* message = JS_ToCString(context, exception); JSValue error = JS_NewObject(context); JS_SetPropertyStr(context, error, "message", JS_NewString(context, message ? message : "[exception]")); if (JS_IsError(context, exception)) { JSValue stack = JS_GetPropertyStr(context, exception, "stack"); if (!JS_IsUndefined(stack) && !JS_IsException(stack)) { JS_SetPropertyStr(context, error, "stack", JS_DupValue(context, stack)); } JS_FreeValue(context, stack); } _serialize_writeInt32(buffer, kException); _serialize_storeInternal(task, to, buffer, error, depth + 1); JS_FreeCString(context, message); JS_FreeValue(context, error); JS_FreeValue(context, exception); } else if (JS_IsString(value)) { size_t len = 0; const char* result = JS_ToCStringLen(tf_task_get_context(task), &len, value); _serialize_writeInt32(buffer, kString); _serialize_writeInt32(buffer, (int32_t)len); _buffer_append(buffer, result, len); JS_FreeCString(tf_task_get_context(task), result); } else if ((bytes = tf_util_try_get_array_buffer(tf_task_get_context(task), &size, value)) != 0) { _serialize_writeInt32(buffer, kArrayBuffer); _serialize_writeInt32(buffer, (int32_t)size); _buffer_append(buffer, bytes, size); } else if (!JS_IsException((typed = tf_util_try_get_typed_array_buffer(tf_task_get_context(task), value, &offset, &size, &element_size)))) { size_t total_size; uint8_t* bytes = tf_util_try_get_array_buffer(tf_task_get_context(task), &total_size, typed); _serialize_writeInt32(buffer, kArrayBuffer); _serialize_writeInt32(buffer, (int32_t)size); _buffer_append(buffer, bytes, size); JS_FreeValue(tf_task_get_context(task), typed); } else if (JS_IsArray(tf_task_get_context(task), value)) { _serialize_writeInt32(buffer, kArray); JSValue length_val = JS_GetPropertyStr(tf_task_get_context(task), value, "length"); int length; if (JS_ToInt32(tf_task_get_context(task), &length, length_val) == 0) { _serialize_writeInt32(buffer, length); for (int i = 0; i < length; ++i) { JSValue element = JS_GetPropertyUint32(tf_task_get_context(task), value, i); _serialize_storeInternal(task, to, buffer, element, depth + 1); JS_FreeValue(context, element); } } else { _serialize_writeInt32(buffer, 0); } JS_FreeValue(tf_task_get_context(task), length_val); } else if (JS_IsFunction(tf_task_get_context(task), value)) { _serialize_writeInt32(buffer, kFunction); exportid_t exportId = tf_task_export_function(task, to, value); _serialize_writeInt32(buffer, exportId); } else if (JS_IsError(tf_task_get_context(task), value)) { _serialize_writeInt32(buffer, kError); JSPropertyEnum* ptab; uint32_t plen; JS_GetOwnPropertyNames(tf_task_get_context(task), &ptab, &plen, value, JS_GPN_STRING_MASK); _serialize_writeInt32(buffer, plen); for (uint32_t i = 0; i < plen; ++i) { JSValue key = JS_AtomToString(tf_task_get_context(task), ptab[i].atom); JSPropertyDescriptor desc; JSValue key_value = JS_NULL; if (JS_GetOwnProperty(tf_task_get_context(task), &desc, value, ptab[i].atom) == 1) { key_value = desc.value; JS_FreeValue(tf_task_get_context(task), desc.setter); JS_FreeValue(tf_task_get_context(task), desc.getter); } _serialize_storeInternal(task, to, buffer, key, depth + 1); _serialize_storeInternal(task, to, buffer, key_value, depth + 1); JS_FreeValue(tf_task_get_context(task), key); JS_FreeValue(tf_task_get_context(task), key_value); } for (uint32_t i = 0; i < plen; ++i) { JS_FreeAtom(tf_task_get_context(task), ptab[i].atom); } js_free(tf_task_get_context(task), ptab); } else if (JS_IsObject(value)) { _serialize_writeInt32(buffer, kObject); JSPropertyEnum* ptab; uint32_t plen; JS_GetOwnPropertyNames(tf_task_get_context(task), &ptab, &plen, value, JS_GPN_STRING_MASK); _serialize_writeInt32(buffer, plen); for (uint32_t i = 0; i < plen; ++i) { JSValue key = JS_AtomToString(tf_task_get_context(task), ptab[i].atom); JSPropertyDescriptor desc; JSValue key_value = JS_NULL; if (JS_GetOwnProperty(tf_task_get_context(task), &desc, value, ptab[i].atom) == 1) { key_value = desc.value; JS_FreeValue(tf_task_get_context(task), desc.setter); JS_FreeValue(tf_task_get_context(task), desc.getter); } _serialize_storeInternal(task, to, buffer, key, depth + 1); _serialize_storeInternal(task, to, buffer, key_value, depth + 1); JS_FreeValue(tf_task_get_context(task), key); JS_FreeValue(tf_task_get_context(task), key_value); } for (uint32_t i = 0; i < plen; ++i) { JS_FreeAtom(tf_task_get_context(task), ptab[i].atom); } js_free(tf_task_get_context(task), ptab); } else { fprintf(stderr, "Unknown JSValue type: %d.\n", JS_VALUE_GET_TAG(value)); abort(); } return true; } static JSValue _serialize_load(tf_task_t* task, tf_taskstub_t* from, const char* buffer, size_t size) { return _serialize_loadInternal(task, from, &buffer, &size, 0); } static JSValue _serialize_loadInternal(tf_task_t* task, tf_taskstub_t* from, const char** buffer, size_t* size, int depth) { if (*size < sizeof(int32_t)) { return JS_UNDEFINED; } else { int32_t type = _serialize_readInt32(buffer, size); JSValue result = JS_UNDEFINED; switch (type) { case kUndefined: result = JS_UNDEFINED; break; case kNull: result = JS_NULL; break; case kUninitialized: result = JS_UNINITIALIZED; break; case kBoolean: result = JS_NewBool(tf_task_get_context(task), _serialize_readInt8(buffer, size) != 0); break; case kInt32: result = JS_NewInt32(tf_task_get_context(task), _serialize_readInt32(buffer, size)); break; case kInt64: result = JS_NewInt64(tf_task_get_context(task), _serialize_readInt64(buffer, size)); break; case kNumber: result = JS_NewFloat64(tf_task_get_context(task), _serialize_readDouble(buffer, size)); break; case kString: { int32_t length = _serialize_readInt32(buffer, size); result = JS_NewStringLen(tf_task_get_context(task), *buffer, length); *buffer += length; *size -= length; } break; case kArrayBuffer: { int32_t length = _serialize_readInt32(buffer, size); result = JS_NewArrayBufferCopy(tf_task_get_context(task), (const uint8_t*)*buffer, length); *buffer += length; *size -= length; } break; case kArray: { int32_t length = _serialize_readInt32(buffer, size); result = JS_NewArray(tf_task_get_context(task)); for (int i = 0; i < length; ++i) { JS_SetPropertyUint32(tf_task_get_context(task), result, i, _serialize_loadInternal(task, from, buffer, size, depth + 1)); } } break; case kFunction: { exportid_t exportId = _serialize_readInt32(buffer, size); result = tf_task_add_import(task, tf_taskstub_get_id(from), exportId); } break; case kException: { _serialize_readInt32(buffer, size); JSValue error = JS_NewError(tf_task_get_context(task)); int32_t length = _serialize_readInt32(buffer, size); for (int i = 0; i < length; ++i) { JSValue key = _serialize_loadInternal(task, from, buffer, size, depth + 1); JSValue value = _serialize_loadInternal(task, from, buffer, size, depth + 1); const char* key_str = JS_ToCString(tf_task_get_context(task), key); JS_SetPropertyStr(tf_task_get_context(task), error, key_str, value); JS_FreeCString(tf_task_get_context(task), key_str); JS_FreeValue(tf_task_get_context(task), key); } result = error; } break; case kError: case kObject: { int32_t length = _serialize_readInt32(buffer, size); result = JS_NewObject(tf_task_get_context(task)); for (int i = 0; i < length; ++i) { JSValue key = _serialize_loadInternal(task, from, buffer, size, depth + 1); JSValue value = _serialize_loadInternal(task, from, buffer, size, depth + 1); const char* key_str = JS_ToCString(tf_task_get_context(task), key); JS_SetPropertyStr(tf_task_get_context(task), result, key_str, value); JS_FreeCString(tf_task_get_context(task), key_str); JS_FreeValue(tf_task_get_context(task), key); } } break; default: abort(); break; } return result; } }