forked from cory/tildefriends
		
	
		
			
				
	
	
		
			434 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			434 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include "serialize.h"
 | |
| 
 | |
| #include "mem.h"
 | |
| #include "task.h"
 | |
| #include "taskstub.js.h"
 | |
| #include "trace.h"
 | |
| #include "util.js.h"
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| 
 | |
| #include <assert.h>
 | |
| 
 | |
| 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)
 | |
| {
 | |
| 	tf_trace_t* trace = tf_task_get_trace(task);
 | |
| 	tf_trace_begin(trace, "tf_serialize_store");
 | |
| 	buffer_t tmp = { 0 };
 | |
| 	_serialize_store(task, to, &tmp, value);
 | |
| 	tmp.data = tf_resize_vec(tmp.data, tmp.size);
 | |
| 	*out_buffer = tmp.data;
 | |
| 	*out_size = tmp.size;
 | |
| 	tf_trace_end(trace);
 | |
| }
 | |
| 
 | |
| JSValue tf_serialize_load(tf_task_t* task, tf_taskstub_t* from, const char* buffer, size_t size)
 | |
| {
 | |
| 	tf_trace_t* trace = tf_task_get_trace(task);
 | |
| 	tf_trace_begin(trace, "tf_serialize_load");
 | |
| 	JSValue result = _serialize_load(task, from, buffer, size);
 | |
| 	tf_trace_end(trace);
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| 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;
 | |
| }
 | |
| 
 | |
| static void _serialize_writeInt8(buffer_t* buffer, int8_t value)
 | |
| {
 | |
| 	_buffer_append(buffer, &value, sizeof(value));
 | |
| }
 | |
| 
 | |
| static void _serialize_writeInt32(buffer_t* buffer, int32_t value)
 | |
| {
 | |
| 	_buffer_append(buffer, &value, sizeof(value));
 | |
| }
 | |
| 
 | |
| static void _serialize_writeInt64(buffer_t* buffer, int64_t value)
 | |
| {
 | |
| 	_buffer_append(buffer, &value, sizeof(value));
 | |
| }
 | |
| 
 | |
| static 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;
 | |
| }
 | |
| 
 | |
| static int32_t _serialize_readInt32(const char** buffer, size_t* size)
 | |
| {
 | |
| 	int32_t result = 0;
 | |
| 	_serialize_read(buffer, size, &result, sizeof(result));
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| static int64_t _serialize_readInt64(const char** buffer, size_t* size)
 | |
| {
 | |
| 	int64_t result = 0;
 | |
| 	_serialize_read(buffer, size, &result, sizeof(result));
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| static double _serialize_readDouble(const char** buffer, size_t* size)
 | |
| {
 | |
| 	double result = 0;
 | |
| 	_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(context, value) ? 1 : 0);
 | |
| 	}
 | |
| 	else if (JS_IsNumber(value))
 | |
| 	{
 | |
| 		int64_t result = 0;
 | |
| 		double float_result = 0.0;
 | |
| 		if (JS_VALUE_GET_TAG(value) == JS_TAG_INT && JS_ToInt64(context, &result, value) == 0)
 | |
| 		{
 | |
| 			_serialize_writeInt32(buffer, kInt64);
 | |
| 			_serialize_writeInt64(buffer, result);
 | |
| 		}
 | |
| 		else if (JS_ToFloat64(context, &float_result, value) == 0)
 | |
| 		{
 | |
| 			_serialize_writeInt32(buffer, kNumber);
 | |
| 			_serialize_writeDouble(buffer, float_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(context, &len, value);
 | |
| 		_serialize_writeInt32(buffer, kString);
 | |
| 		_serialize_writeInt32(buffer, (int32_t)len);
 | |
| 		_buffer_append(buffer, result, len);
 | |
| 		JS_FreeCString(context, result);
 | |
| 	}
 | |
| 	else if ((bytes = tf_util_try_get_array_buffer(context, &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(context, value, &offset, &size, &element_size))))
 | |
| 	{
 | |
| 		size_t total_size = 0;
 | |
| 		uint8_t* bytes = tf_util_try_get_array_buffer(context, &total_size, typed);
 | |
| 		_serialize_writeInt32(buffer, kArrayBuffer);
 | |
| 		_serialize_writeInt32(buffer, (int32_t)size);
 | |
| 		_buffer_append(buffer, bytes, size);
 | |
| 		JS_FreeValue(context, typed);
 | |
| 	}
 | |
| 	else if (JS_IsArray(context, value))
 | |
| 	{
 | |
| 		_serialize_writeInt32(buffer, kArray);
 | |
| 		int length = tf_util_get_length(context, value);
 | |
| 		_serialize_writeInt32(buffer, length);
 | |
| 		for (int i = 0; i < length; ++i)
 | |
| 		{
 | |
| 			JSValue element = JS_GetPropertyUint32(context, value, i);
 | |
| 			_serialize_storeInternal(task, to, buffer, element, depth + 1);
 | |
| 			JS_FreeValue(context, element);
 | |
| 		}
 | |
| 	}
 | |
| 	else if (JS_IsFunction(context, value))
 | |
| 	{
 | |
| 		_serialize_writeInt32(buffer, kFunction);
 | |
| 		exportid_t exportId = tf_task_export_function(task, to, value);
 | |
| 		_serialize_writeInt32(buffer, exportId);
 | |
| 	}
 | |
| 	else if (JS_IsError(context, value))
 | |
| 	{
 | |
| 		_serialize_writeInt32(buffer, kError);
 | |
| 		JSPropertyEnum* ptab;
 | |
| 		uint32_t plen;
 | |
| 		JS_GetOwnPropertyNames(context, &ptab, &plen, value, JS_GPN_STRING_MASK);
 | |
| 		_serialize_writeInt32(buffer, plen);
 | |
| 		for (uint32_t i = 0; i < plen; ++i)
 | |
| 		{
 | |
| 			JSValue key = JS_AtomToString(context, ptab[i].atom);
 | |
| 			JSPropertyDescriptor desc;
 | |
| 			JSValue key_value = JS_NULL;
 | |
| 			if (JS_GetOwnProperty(context, &desc, value, ptab[i].atom) == 1)
 | |
| 			{
 | |
| 				key_value = desc.value;
 | |
| 				JS_FreeValue(context, desc.setter);
 | |
| 				JS_FreeValue(context, desc.getter);
 | |
| 			}
 | |
| 			_serialize_storeInternal(task, to, buffer, key, depth + 1);
 | |
| 			_serialize_storeInternal(task, to, buffer, key_value, depth + 1);
 | |
| 			JS_FreeValue(context, key);
 | |
| 			JS_FreeValue(context, key_value);
 | |
| 		}
 | |
| 		for (uint32_t i = 0; i < plen; ++i)
 | |
| 		{
 | |
| 			JS_FreeAtom(context, ptab[i].atom);
 | |
| 		}
 | |
| 		js_free(context, ptab);
 | |
| 	}
 | |
| 	else if (JS_IsObject(value))
 | |
| 	{
 | |
| 		_serialize_writeInt32(buffer, kObject);
 | |
| 		JSPropertyEnum* ptab;
 | |
| 		uint32_t plen;
 | |
| 		JS_GetOwnPropertyNames(context, &ptab, &plen, value, JS_GPN_STRING_MASK);
 | |
| 		_serialize_writeInt32(buffer, plen);
 | |
| 		for (uint32_t i = 0; i < plen; ++i)
 | |
| 		{
 | |
| 			JSValue key = JS_AtomToString(context, ptab[i].atom);
 | |
| 			JSPropertyDescriptor desc;
 | |
| 			JSValue key_value = JS_NULL;
 | |
| 			if (JS_GetOwnProperty(context, &desc, value, ptab[i].atom) == 1)
 | |
| 			{
 | |
| 				key_value = desc.value;
 | |
| 				JS_FreeValue(context, desc.setter);
 | |
| 				JS_FreeValue(context, desc.getter);
 | |
| 			}
 | |
| 			_serialize_storeInternal(task, to, buffer, key, depth + 1);
 | |
| 			_serialize_storeInternal(task, to, buffer, key_value, depth + 1);
 | |
| 			JS_FreeValue(context, key);
 | |
| 			JS_FreeValue(context, key_value);
 | |
| 		}
 | |
| 		for (uint32_t i = 0; i < plen; ++i)
 | |
| 		{
 | |
| 			JS_FreeAtom(context, ptab[i].atom);
 | |
| 		}
 | |
| 		js_free(context, 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;
 | |
| 	}
 | |
| 
 | |
| 	JSContext* context = tf_task_get_context(task);
 | |
| 	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(context, _serialize_readInt8(buffer, size) != 0);
 | |
| 		break;
 | |
| 	case kInt32:
 | |
| 		result = JS_NewInt32(context, _serialize_readInt32(buffer, size));
 | |
| 		break;
 | |
| 	case kInt64:
 | |
| 		result = JS_NewInt64(context, _serialize_readInt64(buffer, size));
 | |
| 		break;
 | |
| 	case kNumber:
 | |
| 		result = JS_NewFloat64(context, _serialize_readDouble(buffer, size));
 | |
| 		break;
 | |
| 	case kString:
 | |
| 		{
 | |
| 			int32_t length = _serialize_readInt32(buffer, size);
 | |
| 			result = JS_NewStringLen(context, *buffer, length);
 | |
| 			*buffer += length;
 | |
| 			*size -= length;
 | |
| 		}
 | |
| 		break;
 | |
| 	case kArrayBuffer:
 | |
| 		{
 | |
| 			int32_t length = _serialize_readInt32(buffer, size);
 | |
| 			result = JS_NewArrayBufferCopy(context, (const uint8_t*)*buffer, length);
 | |
| 			*buffer += length;
 | |
| 			*size -= length;
 | |
| 		}
 | |
| 		break;
 | |
| 	case kArray:
 | |
| 		{
 | |
| 			int32_t length = _serialize_readInt32(buffer, size);
 | |
| 			result = JS_NewArray(context);
 | |
| 			for (int i = 0; i < length; ++i)
 | |
| 			{
 | |
| 				JS_SetPropertyUint32(context, 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:
 | |
| 		{
 | |
| 			int32_t length = _serialize_readInt32(buffer, size);
 | |
| 			result = JS_NewError(context);
 | |
| 			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(context, key);
 | |
| 				JS_SetPropertyStr(context, result, key_str, value);
 | |
| 				JS_FreeCString(context, key_str);
 | |
| 				JS_FreeValue(context, key);
 | |
| 			}
 | |
| 		}
 | |
| 		break;
 | |
| 	case kError:
 | |
| 	case kObject:
 | |
| 		{
 | |
| 			int32_t length = _serialize_readInt32(buffer, size);
 | |
| 			result = JS_NewObject(context);
 | |
| 			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(context, key);
 | |
| 				JS_SetPropertyStr(context, result, key_str, value);
 | |
| 				JS_FreeCString(context, key_str);
 | |
| 				JS_FreeValue(context, key);
 | |
| 			}
 | |
| 		}
 | |
| 		break;
 | |
| 	default:
 | |
| 		abort();
 | |
| 		break;
 | |
| 	}
 | |
| 	return result;
 | |
| }
 |