From b3f9b25d53b1bd1fc83e8a291ae40b84d02a4478 Mon Sep 17 00:00:00 2001
From: Daniel Friesel <derf@finalrewind.org>
Date: Tue, 13 Nov 2018 15:33:56 +0100
Subject: [PATCH] prototest: add ubjson

---
 include/lib/ubjson/ubj.h          | 231 +++++++++++
 include/lib/ubjson/ubj_internal.h | 163 ++++++++
 src/app/prototest/Makefile.inc    |   2 +-
 src/app/prototest/main.cc         |  30 ++
 src/lib/ubjson/ubjr.c             | 516 +++++++++++++++++++++++++
 src/lib/ubjson/ubjrw.c            | 169 ++++++++
 src/lib/ubjson/ubjw.c             | 618 ++++++++++++++++++++++++++++++
 7 files changed, 1728 insertions(+), 1 deletion(-)
 create mode 100644 include/lib/ubjson/ubj.h
 create mode 100644 include/lib/ubjson/ubj_internal.h
 create mode 100644 src/lib/ubjson/ubjr.c
 create mode 100644 src/lib/ubjson/ubjrw.c
 create mode 100644 src/lib/ubjson/ubjw.c

diff --git a/include/lib/ubjson/ubj.h b/include/lib/ubjson/ubj.h
new file mode 100644
index 0000000..f3d5ac8
--- /dev/null
+++ b/include/lib/ubjson/ubj.h
@@ -0,0 +1,231 @@
+#ifndef UBJ_H
+#define UBJ_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include<inttypes.h>
+#include<stdio.h>
+
+typedef enum
+{
+	UBJ_MIXED=0,			//NOT a type...or the type is mixed
+
+	UBJ_NULLTYPE,
+	UBJ_NOOP,
+	UBJ_BOOL_TRUE,
+	UBJ_BOOL_FALSE,
+	
+	UBJ_CHAR,
+	UBJ_STRING,
+	UBJ_HIGH_PRECISION,
+
+	UBJ_INT8,
+	UBJ_UINT8 ,
+	UBJ_INT16,
+	UBJ_INT32,
+	UBJ_INT64,
+	UBJ_FLOAT32 ,
+	UBJ_FLOAT64,
+
+	UBJ_ARRAY,
+	UBJ_OBJECT,
+
+	UBJ_NUM_TYPES				//this is the size of how many types there are (chris' trick)
+} UBJ_TYPE;
+
+
+
+//////////here is the declarations for the writer API////////////////////////////////////
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct ubjw_context_t_s;
+typedef struct ubjw_context_t_s ubjw_context_t;
+
+ubjw_context_t* ubjw_open_callback(void* userdata,
+	size_t(*write_cb)(const void* data, size_t size, size_t count, void* userdata),
+	int(*close_cb)(void* userdata),
+	void(*error_cb)(const char* error_msg)
+	);
+ubjw_context_t* ubjw_open_file(FILE*);
+ubjw_context_t* ubjw_open_memory(uint8_t* dst_b, uint8_t* dst_e);
+
+size_t ubjw_close_context(ubjw_context_t* ctx);
+
+void ubjw_write_string(ubjw_context_t* dst, const char* out);
+void ubjw_write_char(ubjw_context_t* dst, char out);
+
+void ubjw_write_uint8(ubjw_context_t* dst, uint8_t out);
+void ubjw_write_int8(ubjw_context_t* dst, int8_t out);
+void ubjw_write_int16(ubjw_context_t* dst, int16_t out);
+void ubjw_write_int32(ubjw_context_t* dst, int32_t out);
+void ubjw_write_int64(ubjw_context_t* dst, int64_t out);
+void ubjw_write_high_precision(ubjw_context_t* dst, const char* hp);
+
+void ubjw_write_integer(ubjw_context_t* dst, int64_t out);
+UBJ_TYPE ubjw_min_integer_type(int64_t in);
+
+void ubjw_write_float32(ubjw_context_t* dst, float out);
+void ubjw_write_float64(ubjw_context_t* dst, double out);
+
+void ubjw_write_floating_point(ubjw_context_t* dst, double out);
+
+void ubjw_write_noop(ubjw_context_t* dst);
+void ubjw_write_null(ubjw_context_t* dst);
+void ubjw_write_bool(ubjw_context_t* dst, uint8_t out);
+
+void ubjw_begin_array(ubjw_context_t* dst, UBJ_TYPE type, size_t count);
+
+void ubjw_begin_object(ubjw_context_t* dst, UBJ_TYPE type, size_t count);
+void ubjw_write_key(ubjw_context_t* dst, const char* key);
+void ubjw_end(ubjw_context_t* dst);
+
+//output an efficient buffer of types
+void ubjw_write_buffer(ubjw_context_t* dst, const uint8_t* data, UBJ_TYPE type, size_t count);
+
+//Proposal for N-D arrays
+void ubjw_begin_ndarray(ubjw_context_t* dst, UBJ_TYPE type, const size_t* dims, uint8_t ndims);
+void ubjw_write_ndbuffer(ubjw_context_t* dst,const uint8_t* data, UBJ_TYPE type, const size_t* dims, uint8_t ndims);
+
+
+//////////here is the declarations for the reader API////////////////////////////////////
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct ubjr_context_t_s;
+typedef struct ubjr_context_t_s ubjr_context_t;
+
+//Open up a reader context for reading using a custom calllback
+ubjr_context_t* ubjr_open_callback(void* userdata,
+	size_t(*read_cb)(void* data, size_t size, size_t count, void* userdata),
+	int(*peek_cb)(void* userdata),
+	int(*close_cb)(void* userdata),
+	void(*error_cb)(const char* error_msg)
+	);
+
+//Open a context initialized to a UBJ file
+ubjr_context_t* ubjr_open_file(FILE*);
+
+//Open up a context initialized to a memory dump of a UBJ file (or a segment of a UBJ file)
+ubjr_context_t* ubjr_open_memory(const uint8_t* dst_b, const uint8_t* dst_e);
+
+//Close a reader context 
+size_t ubjr_close_context(ubjr_context_t* ctx);
+
+typedef char* ubjr_string_t;
+
+//An array that you read from the stream
+typedef struct ubjr_array_t_s
+{
+	uint8_t originally_sized;
+	UBJ_TYPE type;	
+	size_t size;	//total number of elements
+	void* values;
+	uint8_t num_dims;
+	size_t* dims;	//this could be faster if it was constant size, but would also make the size of the dynamic object a LOT bigger
+
+} ubjr_array_t;
+
+//a map that you read from the stream
+typedef struct ubjr_object_t_s
+{
+	uint8_t originally_sized;
+	UBJ_TYPE type;
+	size_t size;
+	void* values;
+	ubjr_string_t* keys;
+	void* metatable;		//don't use this..only useful for computing object_lookup
+} ubjr_object_t;
+
+//a dynamic type that you parsed.
+typedef struct ubjr_dynamic_t_s
+{
+	UBJ_TYPE type;
+	union
+	{
+		uint8_t boolean;
+		double real;
+		int64_t integer;
+		ubjr_string_t string;
+		ubjr_array_t container_array;
+		ubjr_object_t container_object;
+	};
+} ubjr_dynamic_t;
+
+//Parse a dynamic object from the stream
+ubjr_dynamic_t ubjr_read_dynamic(ubjr_context_t* ctx);
+void ubjr_cleanup_dynamic(ubjr_dynamic_t* dyn);
+
+ubjr_dynamic_t ubjr_object_lookup(ubjr_object_t* obj, const char* key);
+size_t ubjr_local_type_size(UBJ_TYPE typ);//should be equivalent to sizeof()
+size_t ubjr_ndarray_index(const ubjr_array_t* arr, const size_t* indices);
+
+
+//output an efficient buffer of types
+///void ubjr_read_buffer(struct ubjr_context_t* dst, const uint8_t* data, UBJ_TYPE type, size_t count);
+
+void ubjr_cleanup_dynamic(ubjr_dynamic_t* dyn);
+void ubjr_cleanup_array(ubjr_array_t* arr);
+void ubjr_cleanup_object(ubjr_object_t* obj);
+
+
+
+///////UBJ_RW api
+
+void ubjrw_write_dynamic(ubjw_context_t* ctx, ubjr_dynamic_t dobj,uint8_t optimize);
+//ubjrw_append_object(ubjw_context_t* ctx, ubjr_dynamic_t dobj);
+//ubjrw_append_array(ubjw_context_t* ctx, ubjr_dynamic_t dobj);
+
+#ifdef __cplusplus
+}
+
+/*
+#include<iostream>
+
+static size_t write_os(const void* data, size_t size, size_t count, void* userdata)
+{
+	size_t n = size*count;
+	reinterpret_cast<std::ostream*>(userdata)->write(data, n);
+	return n;
+}
+static void close_os(void* userdata)
+{
+	reinterpret_cast<std::ostream*>(userdata)->close();
+}
+
+static size_t read_is(void* data, size_t size, size_t count, void* userdata)
+{
+	size_t n = size*count;
+	reinterpret_cast<std::istream*>(userdata)->read(data, n);
+	return n;
+}
+static int peek_is(void* userdata)
+{
+	return reinterpret_cast<std::istream*>(userdata)->peek();
+}
+static void close_is(void* userdata)
+{
+	reinterpret_cast<std::istream*>(userdata)->close();
+}
+
+static ubjw_context_t* ubjw_open_stream(std::ostream& outstream)
+{
+	return ubjw_open_callback((void*)&outstream, write_os, close_os, NULL);
+}
+
+static ubjr_context_t* ubjr_open_stream(std::istream& instream)
+{
+	return ubjr_open_callback((void*)&instream, read_is, peek_is, close_is, NULL);
+}
+*/
+
+
+#endif
+
+#endif
diff --git a/include/lib/ubjson/ubj_internal.h b/include/lib/ubjson/ubj_internal.h
new file mode 100644
index 0000000..fc61697
--- /dev/null
+++ b/include/lib/ubjson/ubj_internal.h
@@ -0,0 +1,163 @@
+#ifndef UBJ_INTERNAL_H
+#define UBJ_INTERNAL_H
+
+#include "ubj.h"
+#include <stdlib.h>
+#include <string.h>
+
+#if _MSC_VER
+#define inline __inline
+#endif
+
+
+static const uint8_t UBJI_TYPEC_convert[UBJ_NUM_TYPES] = "\x00ZNTFCSHiUIlLdD[{";
+
+static const int UBJI_TYPE_size[UBJ_NUM_TYPES] =
+	{ -1,	 //MIXED
+	0,	 //NULLTYPE
+	0,	 //NOOP
+	0,   //BOOL_TRUE
+	0,   //BOOL_FALSE
+	1,   //CHAR
+	sizeof(const char*), //STRING
+	sizeof(const char*), //high-precision
+	1,					//INT8
+	1,					//UINT8
+	2,					//int16
+	4,					//int32
+	8,					//int64
+	4,					//float32
+	8,					//float64
+	-1,					//array
+	-1					//object
+	};
+
+static const size_t UBJR_TYPE_localsize[UBJ_NUM_TYPES] =
+{
+	sizeof(ubjr_dynamic_t),	 //MIXED
+	0,	 //NULLTYPE
+	0,	 //NOOP
+	0,   //BOOL_TRUE
+	0,   //BOOL_FALSE
+	sizeof(ubjr_string_t),   //CHAR
+	sizeof(ubjr_string_t), //STRING
+	sizeof(ubjr_string_t), //high-precision
+	sizeof(int8_t),					//INT8
+	sizeof(uint8_t),					//UINT8
+	sizeof(int16_t),					//int16
+	sizeof(int32_t),					//int32
+	sizeof(int64_t),					//int64
+	sizeof(float),					//float32
+	sizeof(double),					//float64
+	sizeof(ubjr_array_t),					//array
+	sizeof(ubjr_object_t)					//object
+};
+
+static inline void _to_bigendian16(uint8_t* outbuffer, uint16_t input)
+{
+	*outbuffer++ = (input >> 8); // Get top order byte (guaranteed endian-independent since machine registers)
+	*outbuffer++ = input & 0xFF; // Get bottom order byte
+}
+static inline void _to_bigendian32(uint8_t* outbuffer, uint32_t input)
+{
+	_to_bigendian16(outbuffer, (uint16_t)(input >> 16)); // Get top order 2 bytes
+	_to_bigendian16(outbuffer + 2, (uint16_t)(input & 0xFFFF)); // Get bottom order 2 bytes
+}
+static inline void _to_bigendian64(uint8_t* outbuffer, uint64_t input)
+{
+	_to_bigendian32(outbuffer, (uint32_t)(input >> 32));
+	_to_bigendian32(outbuffer + 4, (uint32_t)(input & 0xFFFFFFFF));
+}
+
+static inline uint8_t _is_bigendian()
+{
+	int i = 1;
+	char *low = (char*)&i;
+	return *low ? 0 : 1;
+}
+
+#define BUF_BIG_ENDIAN_SWAP(type,func,ptr,num)  \
+	{											\
+		size_t i;type* d = (type*)ptr; 					\
+		for (i = 0; i < num; i++)				\
+		{										\
+			func((uint8_t*)&d[i], d[i]);		\
+		}										\
+	}											\
+
+static inline void buf_endian_swap(uint8_t* buf, size_t sz, size_t n)
+{
+	if (!_is_bigendian())
+	{
+		switch (sz)
+		{
+		case 1:
+		case 0:
+			break;
+		case 2:
+			BUF_BIG_ENDIAN_SWAP(uint16_t, _to_bigendian16,buf,n);
+			break;
+		case 4:
+			BUF_BIG_ENDIAN_SWAP(uint32_t, _to_bigendian32,buf,n);
+			break;
+		case 8:
+			BUF_BIG_ENDIAN_SWAP(uint64_t, _to_bigendian64,buf,n);
+			break;
+		};
+	}
+}
+
+//warning...null-terminated strings are assumed...when this is not necessarily valid. FIXED: we don't use null-terminated strings in the reader (NOT FIXED...string type is awkward)
+static inline ubjr_dynamic_t priv_ubjr_pointer_to_dynamic(UBJ_TYPE typ, const void* dat)
+{
+	ubjr_dynamic_t outdyn;
+	outdyn.type = typ;
+	size_t n = 1;
+	switch (typ)
+	{
+	case UBJ_NULLTYPE:
+	case UBJ_NOOP:
+		break;
+	case UBJ_BOOL_TRUE:
+	case UBJ_BOOL_FALSE:
+		outdyn.boolean = (typ == UBJ_BOOL_TRUE ? 1 : 0);
+		break;
+	case UBJ_HIGH_PRECISION:
+	case UBJ_STRING:
+	case UBJ_CHAR://possibly if char allocate, otherwise don't
+		outdyn.string = *(const ubjr_string_t*)dat;
+		break;
+	case UBJ_INT8:
+		outdyn.integer = *(const int8_t*)dat;
+		break;
+	case UBJ_UINT8:
+		outdyn.integer = *(const uint8_t*)dat;
+		break;
+	case UBJ_INT16:
+		outdyn.integer = *(const int16_t*)dat;
+		break;
+	case UBJ_INT32:
+		outdyn.integer = *(const int32_t*)dat;
+		break;
+	case UBJ_INT64:
+		outdyn.integer = *(const int64_t*)dat;
+		break;
+	case UBJ_FLOAT32:
+		outdyn.real = *(const float*)dat;
+		break;
+	case UBJ_FLOAT64:
+		outdyn.real = *(const double*)dat;
+		break;
+	case UBJ_ARRAY:
+		outdyn.container_array = *(const ubjr_array_t*)dat;
+		break;
+	case UBJ_OBJECT:
+		outdyn.container_object = *(const ubjr_object_t*)dat;
+		break;
+	case UBJ_MIXED:
+		outdyn = *(const ubjr_dynamic_t*)dat;
+	};
+	return outdyn;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/app/prototest/Makefile.inc b/src/app/prototest/Makefile.inc
index d3dad26..36cfb49 100644
--- a/src/app/prototest/Makefile.inc
+++ b/src/app/prototest/Makefile.inc
@@ -23,7 +23,7 @@ endif
 
 ifeq (${prototest_ubjson}, 1)
 	COMMON_FLAGS += -DPROTOTEST_UBJSON
-	CXX_TARGETS += src/lib/ubjson/ubjr.cc src/lib/ubjson/ubjw.cc
+	C_TARGETS += src/lib/ubjson/ubjr.c src/lib/ubjson/ubjw.c
 	INCLUDES += -Iinclude/lib/ubjson
 endif
 
diff --git a/src/app/prototest/main.cc b/src/app/prototest/main.cc
index aebfaea..c1d8359 100644
--- a/src/app/prototest/main.cc
+++ b/src/app/prototest/main.cc
@@ -17,6 +17,9 @@
 #include <pb_encode.h>
 #include <pb_decode.h>
 #endif
+#ifdef PROTOTEST_UBJSON
+#include "ubj.h"
+#endif
 #ifdef PROTOTEST_XDR
 #include "object/stdbuf.h"
 #include "object/xdrstream.h"
@@ -146,6 +149,33 @@ void loop(void)
 	}
 	kout << endl;
 
+#endif
+
+	/*
+	 * UBJSON
+	 */
+
+#ifdef PROTOTEST_UBJSON
+
+	uint8_t buf[128];
+	for (unsigned int i = 0; i < 128; i++) {
+		buf[i] = 0;
+	}
+
+	ubjw_context_t* ctx = ubjw_open_memory(buf, buf + sizeof(buf));
+	ubjw_begin_array(ctx, UBJ_MIXED, 0);
+	ubjw_write_int16(ctx, ts);
+	ubjw_write_string(ctx, "Noot Noot");
+	ubjw_end(ctx);
+
+	kout << "ubjr_close_context: " << ubjw_close_context(ctx) << endl;
+
+	kout << "ubjr is " << hex;
+	for (unsigned int i = 0; i < 128; i++) {
+		kout << (uint8_t)buf[i];
+	}
+	kout << endl;
+
 #endif
 
 	/*
diff --git a/src/lib/ubjson/ubjr.c b/src/lib/ubjson/ubjr.c
new file mode 100644
index 0000000..4a5ba97
--- /dev/null
+++ b/src/lib/ubjson/ubjr.c
@@ -0,0 +1,516 @@
+#include "ubj.h"
+#include "ubj_internal.h"
+#include <stdlib.h>
+#include <string.h>
+
+#if _MSC_VER
+#define inline __inline
+#endif
+
+typedef struct ubjr_context_t_s
+{
+	size_t (*read_cb )(void* data, size_t size, size_t count, void* userdata);
+	int (*peek_cb )(void* userdata);
+	int    (*close_cb)(void* userdata);
+	void   (*error_cb)(const char* error_msg);
+
+	void* userdata;
+
+//	struct _ubjr_container_t container_stack[CONTAINER_STACK_MAX];
+//	struct _ubjr_container_t* head;
+
+	uint8_t ignore_container_flags;
+
+	uint16_t last_error_code;
+
+	size_t total_read;
+} ubjr_context_t;
+
+ubjr_context_t* ubjr_open_callback(void* userdata,
+	size_t(*read_cb)(void* data, size_t size, size_t count, void* userdata),
+	int(*peek_cb)(void* userdata),
+	int(*close_cb)(void* userdata),
+	void(*error_cb)(const char* error_msg)
+	)
+{
+	ubjr_context_t* ctx = (ubjr_context_t*)malloc(sizeof(ubjr_context_t));
+	ctx->userdata = userdata;
+	ctx->read_cb = read_cb;
+	ctx->peek_cb = peek_cb;
+	ctx->close_cb = close_cb;
+	ctx->error_cb = error_cb;
+
+
+/*	ctx->head = ctx->container_stack;
+	ctx->head->flags = 0;
+	ctx->head->type = UBJ_MIXED;
+	ctx->head->elements_remaining = 0;
+
+	ctx->ignore_container_flags = 0;*/
+
+	ctx->last_error_code = 0;
+
+	ctx->total_read = 0;
+	return ctx;
+}
+
+size_t ubjr_close_context(ubjr_context_t* ctx)
+{
+	size_t n = ctx->total_read;
+	free(ctx);
+	return n;
+}
+
+static inline uint8_t priv_ubjr_context_getc(ubjr_context_t* ctx)
+{
+	uint8_t a;
+	ctx->total_read += 1;
+	ctx->read_cb(&a, 1, 1, ctx->userdata);
+	return a;
+}
+
+static int fpeek(void* fp)
+{
+    int c;
+    c = fgetc(fp);
+    ungetc(c, fp);
+
+    return c;
+}
+
+ubjr_context_t* ubjr_open_file(FILE* fd)
+{
+	return ubjr_open_callback(fd, (void*)fread,(void*)fpeek,(void*)fclose, NULL);
+}
+
+struct mem_r_fd
+{
+	const uint8_t *begin, *current, *end;
+};
+static int memclose(void* mfd)
+{
+	//free(mfd);
+	return 0;
+}
+static size_t memread(void* data, size_t size, size_t count, struct mem_r_fd* fp)
+{
+	size_t n = size*count;
+	size_t lim = fp->end - fp->current;
+	if (lim < n)
+	{
+		n = lim;
+	}
+	memcpy(data, fp->current, n);
+	fp->current += n;
+	return n;
+}
+static int mempeek(struct mem_r_fd* mfd)
+{
+	return *mfd->current;
+}
+
+ubjr_context_t* ubjr_open_memory(const uint8_t* be, const uint8_t* en)
+{
+	struct mem_r_fd* mfd = (struct mem_r_fd*)malloc(sizeof(struct mem_r_fd));
+	mfd->current = be;
+	mfd->begin = be;
+	mfd->end = en;
+	return ubjr_open_callback(mfd, (void*)memread, (void*)mempeek,(void*)memclose, NULL);
+}
+
+static inline int priv_ubjr_context_peek(ubjr_context_t* ctx)
+{
+	return ctx->peek_cb(ctx->userdata);
+}
+static inline size_t priv_ubjr_context_read(ubjr_context_t* ctx,uint8_t* dst,size_t n)
+{
+	size_t nr=ctx->read_cb(dst,n,1,ctx->userdata);
+	ctx->total_read+=nr;
+	return nr;
+}
+
+typedef struct priv_ubjr_sorted_key_t_s
+{
+	ubjr_string_t key;
+	const uint8_t* value;
+
+} priv_ubjr_sorted_key_t;
+
+static int _obj_key_cmp(const void* av, const void* bv)
+{
+	const priv_ubjr_sorted_key_t *a, *b;
+	a = (const priv_ubjr_sorted_key_t*)av;
+	b = (const priv_ubjr_sorted_key_t*)bv;
+	return strcmp(a->key,b->key);
+}
+
+static inline UBJ_TYPE priv_ubjr_type_from_char(uint8_t c)
+{
+	int i = 0;								//TODO: Benchmark this and see if it should be a switch statement where the compiler implements fastest switch e.g. binary search (17 cases might be binary search fast)
+	for (i = 0; i < UBJ_NUM_TYPES && UBJI_TYPEC_convert[i] != c; i++);
+	return (UBJ_TYPE)i;
+}
+
+size_t ubjr_local_type_size(UBJ_TYPE typ)
+{
+	return UBJR_TYPE_localsize[typ];
+}
+
+
+static inline priv_ubjr_sorted_key_t* priv_ubjr_object_build_sorted_keys(ubjr_object_t* obj)
+{
+	priv_ubjr_sorted_key_t* sorted_keysmem = malloc(obj->size*sizeof(priv_ubjr_sorted_key_t));
+	size_t i;
+	for (i = 0; i < obj->size; i++)
+	{
+		sorted_keysmem[i].key = obj->keys[i];
+		sorted_keysmem[i].value = (const uint8_t*)obj->values + i*UBJR_TYPE_localsize[obj->type];
+	}
+	qsort(sorted_keysmem, obj->size, sizeof(priv_ubjr_sorted_key_t), _obj_key_cmp);
+	return sorted_keysmem;
+}
+
+static inline uint8_t priv_ubjr_read_1b(ubjr_context_t* ctx)
+{
+	return priv_ubjr_context_getc(ctx);
+}
+static inline uint16_t priv_ubjr_read_2b(ubjr_context_t* ctx)
+{
+	return (uint16_t)priv_ubjr_read_1b(ctx) << 8 | (uint16_t)priv_ubjr_read_1b(ctx);
+}
+static inline uint32_t priv_ubjr_read_4b(ubjr_context_t* ctx)
+{
+	return (uint32_t)priv_ubjr_read_2b(ctx) << 16 | (uint32_t)priv_ubjr_read_2b(ctx);
+}
+static inline uint64_t priv_ubjr_read_8b(ubjr_context_t* ctx)
+{
+	return (uint64_t)priv_ubjr_read_4b(ctx) << 32 | (uint64_t)priv_ubjr_read_4b(ctx);
+}
+
+static inline int64_t priv_ubjw_read_integer(ubjr_context_t* ctx)
+{
+	ubjr_dynamic_t d = ubjr_read_dynamic(ctx);
+	if (d.type >= UBJ_INT8 && d.type <= UBJ_INT64)
+		return d.integer;
+	return 0;//error
+}
+
+static inline ubjr_object_t priv_ubjr_read_raw_object(ubjr_context_t* ctx);
+static inline ubjr_array_t priv_ubjr_read_raw_array(ubjr_context_t* ctx);
+static inline void priv_ubjr_read_to_ptr(ubjr_context_t* ctx, uint8_t* dst, UBJ_TYPE typ)
+{
+	int64_t n = 1;
+	char *tstr;
+	switch (typ)
+	{
+	case UBJ_MIXED:
+	{
+		*(ubjr_dynamic_t*)dst = ubjr_read_dynamic(ctx);
+		break;
+	}
+	case UBJ_STRING:
+	case UBJ_HIGH_PRECISION:
+	{
+		n = priv_ubjw_read_integer(ctx);
+	}
+	case UBJ_CHAR:
+	{
+		tstr = malloc(n + 1);
+		priv_ubjr_context_read(ctx, tstr, n);
+		tstr[n] = 0;
+		*(ubjr_string_t*)dst = tstr;
+		break;
+	}
+	case UBJ_INT8:
+	case UBJ_UINT8:
+	{
+		*dst = priv_ubjr_read_1b(ctx);
+		break;
+	}
+	case UBJ_INT16:
+	{
+		*(uint16_t*)dst = priv_ubjr_read_2b(ctx);
+		break;
+	}
+	case UBJ_INT32:
+	case UBJ_FLOAT32:
+	{
+		*(uint32_t*)dst = priv_ubjr_read_4b(ctx);
+		break;
+	}
+	case UBJ_INT64:
+	case UBJ_FLOAT64:
+	{
+		*(uint64_t*)dst = priv_ubjr_read_8b(ctx);
+		break;
+	}
+	case UBJ_ARRAY:
+	{
+		*(ubjr_array_t*)dst = priv_ubjr_read_raw_array(ctx);
+		break;
+	}
+	case UBJ_OBJECT:
+	{
+		*(ubjr_object_t*)dst = priv_ubjr_read_raw_object(ctx);
+		break;
+	}
+	};
+}
+
+ubjr_dynamic_t ubjr_object_lookup(ubjr_object_t* obj, const char* key)
+{
+	if (obj->metatable == NULL)
+	{
+		//memcpy(obj->sorted_keys,obj->keys)
+		obj->metatable = priv_ubjr_object_build_sorted_keys(obj);
+	}
+	void* result=bsearch(key, obj->metatable,obj->size, sizeof(priv_ubjr_sorted_key_t),_obj_key_cmp);
+	if (result == NULL)
+	{
+		ubjr_dynamic_t nulldyn;
+		nulldyn.type = UBJ_NULLTYPE;
+		return nulldyn;
+	}
+	const priv_ubjr_sorted_key_t* result_key = (const priv_ubjr_sorted_key_t*)result;
+	return priv_ubjr_pointer_to_dynamic(obj->type,result_key->value);
+}
+
+size_t ubjr_ndarray_index(const ubjr_array_t* arr, const size_t* indices)
+{
+	//multi-dimensional array to linear array lookup
+	size_t cstride = 1;
+	size_t cdex = 0;
+	uint8_t i;
+	uint8_t nd = arr->num_dims;
+	const size_t* dims = arr->dims;
+	for (i = 0; i<nd; i++)
+	{
+		cdex += cstride*indices[i];
+		cstride *= dims[i];
+	}
+	return cdex;
+}
+
+
+
+ubjr_dynamic_t ubjr_read_dynamic(ubjr_context_t* ctx)
+{
+	ubjr_dynamic_t scratch; //scratch memory
+	UBJ_TYPE newtyp = priv_ubjr_type_from_char(priv_ubjr_context_getc(ctx));
+	priv_ubjr_read_to_ptr(ctx, (uint8_t*)&scratch, newtyp);
+	return priv_ubjr_pointer_to_dynamic(newtyp, &scratch);
+}
+
+static inline void priv_read_container_params(ubjr_context_t* ctx, UBJ_TYPE* typout, size_t* sizeout)
+{
+	int nextchar = priv_ubjr_context_peek(ctx);
+	if (nextchar == '$')
+	{
+		priv_ubjr_context_getc(ctx);
+		*typout = priv_ubjr_type_from_char(priv_ubjr_context_getc(ctx));
+		nextchar = priv_ubjr_context_peek(ctx);
+	}
+	else
+	{
+		*typout = UBJ_MIXED;
+	}
+
+	if (nextchar == '#')
+	{
+		priv_ubjr_context_getc(ctx);
+		*sizeout = priv_ubjw_read_integer(ctx);
+	}
+	else
+	{
+		*sizeout = 0;
+	}
+}
+//TODO: This can be reused for object
+
+static inline ubjr_array_t priv_ubjr_read_raw_array(ubjr_context_t* ctx)
+{
+	ubjr_array_t myarray;
+	priv_read_container_params(ctx,&myarray.type,&myarray.size);
+	myarray.num_dims = 1;
+	myarray.dims = NULL;
+	if (myarray.type != UBJ_MIXED && myarray.size==0) //params detected this is a typed array but no size was detected..possibly an N-D array?
+	{
+		if (priv_ubjr_context_peek(ctx) == '@')
+		{
+			uint8_t dselect;
+			priv_ubjr_context_getc(ctx);//skip over the '@' marker
+			myarray.num_dims = priv_ubjr_context_getc(ctx);//since max is 8, no type indicator needed...always a int7 type
+			myarray.dims = malloc(sizeof(size_t)*myarray.num_dims);
+			myarray.size = 1;
+			for (dselect = 0; dselect < myarray.num_dims; dselect++)
+			{
+				size_t d = priv_ubjw_read_integer(ctx);
+				myarray.dims[dselect] = d;
+				myarray.size *= d;
+			}
+		}
+	}
+
+	size_t ls = UBJR_TYPE_localsize[myarray.type];
+	if (myarray.size == 0)
+	{
+		myarray.originally_sized = 0;
+		size_t arrpot = 0;
+		myarray.values=malloc(1*ls+1); //the +1 is for memory for the 0-size elements
+		for (myarray.size = 0; priv_ubjr_context_peek(ctx) != ']'; myarray.size++)
+		{
+			if (myarray.size >= (1ULL << arrpot))
+			{
+				arrpot ++;
+				myarray.values = realloc(myarray.values, (1ULL << arrpot)*ls+1);
+			}
+			priv_ubjr_read_to_ptr(ctx,(uint8_t*)myarray.values + ls*myarray.size,myarray.type);
+		}
+		priv_ubjr_context_getc(ctx); // read the closing ']'
+	}
+	else
+	{
+		myarray.originally_sized = 1;
+		size_t i;
+		myarray.values = malloc(ls*myarray.size+1);
+		size_t sz = UBJI_TYPE_size[myarray.type];
+
+		if (sz >= 0 && myarray.type != UBJ_STRING && myarray.type != UBJ_HIGH_PRECISION && myarray.type != UBJ_CHAR && myarray.type != UBJ_MIXED) //constant size,fastread
+		{
+			priv_ubjr_context_read(ctx, myarray.values, sz*myarray.size);
+			buf_endian_swap(myarray.values, sz, myarray.size); //do nothing for 0-sized buffers
+		}
+		else
+		{
+			for (i = 0; i < myarray.size; i++)
+			{
+				priv_ubjr_read_to_ptr(ctx, (uint8_t*)myarray.values + ls*i, myarray.type);
+			}
+		}
+	}
+	if (myarray.dims == NULL)
+	{
+		myarray.dims = malloc(sizeof(size_t));
+		myarray.dims[0] = myarray.size;
+	}
+	return myarray;
+}
+
+static inline ubjr_object_t priv_ubjr_read_raw_object(ubjr_context_t* ctx)
+{
+	ubjr_object_t myobject;
+	myobject.metatable = NULL;
+	priv_read_container_params(ctx, &myobject.type, &myobject.size);
+
+	size_t ls = UBJR_TYPE_localsize[myobject.type];
+	if (myobject.size == 0)
+	{
+		myobject.originally_sized = 0;
+		size_t arrpot = 0;
+		myobject.values = malloc(1 * ls + 1); //the +1 is for memory for the 0-size elements
+		myobject.keys = malloc(1 * sizeof(ubjr_string_t));
+		for (myobject.size = 0; priv_ubjr_context_peek(ctx) != '}'; myobject.size++)
+		{
+			if (myobject.size >= (1ULL << arrpot))
+			{
+				arrpot++;
+				myobject.values = realloc(myobject.values, (1ULL << arrpot)*ls + 1);
+				myobject.keys = realloc((uint8_t*)myobject.keys, (1ULL << arrpot)*sizeof(ubjr_string_t));
+			}
+			priv_ubjr_read_to_ptr(ctx, (uint8_t*)(myobject.keys + myobject.size), UBJ_STRING);
+			priv_ubjr_read_to_ptr(ctx, (uint8_t*)myobject.values + ls*myobject.size, myobject.type);
+		}
+		priv_ubjr_context_getc(ctx); // read the closing '}'
+	}
+	else
+	{
+		size_t i;
+		myobject.originally_sized = 1;
+		myobject.values = malloc(ls*myobject.size + 1);
+		myobject.keys = malloc(myobject.size * sizeof(ubjr_string_t));
+
+		for (i = 0; i < myobject.size; i++)
+		{
+			priv_ubjr_read_to_ptr(ctx, (uint8_t*)(myobject.keys + i), UBJ_STRING);
+			priv_ubjr_read_to_ptr(ctx, (uint8_t*)myobject.values + ls*i, myobject.type);
+		}
+	}
+	return myobject;
+}
+static inline void priv_ubjr_cleanup_pointer(UBJ_TYPE typ,void* value);
+static inline priv_ubjr_cleanup_container(UBJ_TYPE type,size_t size,void* values)
+{
+	if(type == UBJ_MIXED || type == UBJ_ARRAY || type == UBJ_OBJECT || type == UBJ_STRING)
+	{
+		size_t ls=UBJR_TYPE_localsize[type];
+		uint8_t *viter,*vend;
+		viter=values;
+		vend=viter+ls*size;
+		for(;viter != vend;viter+=ls)
+		{
+			priv_ubjr_cleanup_pointer(type,(void*)viter);
+		}
+	}
+	free(values);
+}
+static inline void priv_ubjr_cleanup_pointer(UBJ_TYPE typ,void* value)
+{
+	switch(typ)
+	{
+		case UBJ_MIXED:
+		{
+			ubjr_dynamic_t* dyn=(ubjr_dynamic_t*)value;
+			switch(dyn->type)
+			{
+			case UBJ_STRING:
+				priv_ubjr_cleanup_pointer(UBJ_STRING,&dyn->string);
+				break;
+			case UBJ_ARRAY:
+				priv_ubjr_cleanup_pointer(UBJ_ARRAY,&dyn->container_array);
+				break;
+			case UBJ_OBJECT:
+				priv_ubjr_cleanup_pointer(UBJ_OBJECT,&dyn->container_object);
+				break;
+			};
+			break;
+		}
+		case UBJ_STRING:
+		{
+			ubjr_string_t* st=(ubjr_string_t*)value;
+			free((void*)*st);
+			break;
+		}
+		case UBJ_ARRAY:
+		{
+			ubjr_array_t* arr=(ubjr_array_t*)value;
+			priv_ubjr_cleanup_container(arr->type,arr->size,arr->values);
+			free(arr->dims);
+			break;
+		}
+		case UBJ_OBJECT:
+		{
+			ubjr_object_t* obj=(ubjr_object_t*)value;
+			priv_ubjr_cleanup_container(obj->type,obj->size,obj->values);
+			priv_ubjr_cleanup_container(UBJ_STRING,obj->size,obj->keys);
+			if(obj->metatable)
+			{
+				free(obj->metatable);
+			}
+			break;
+		}
+	};
+}
+
+void ubjr_cleanup_dynamic(ubjr_dynamic_t* dyn)
+{
+	priv_ubjr_cleanup_pointer(UBJ_MIXED,dyn);
+}
+void ubjr_cleanup_array(ubjr_array_t* arr)
+{
+	priv_ubjr_cleanup_pointer(UBJ_ARRAY,arr);
+}
+void ubjr_cleanup_object(ubjr_object_t* obj)
+{
+	priv_ubjr_cleanup_pointer(UBJ_OBJECT,obj);
+}
+
diff --git a/src/lib/ubjson/ubjrw.c b/src/lib/ubjson/ubjrw.c
new file mode 100644
index 0000000..ee43d70
--- /dev/null
+++ b/src/lib/ubjson/ubjrw.c
@@ -0,0 +1,169 @@
+#include "ubj_internal.h"
+
+static uint32_t compute_typemask(ubjr_dynamic_t* vals, size_t sz)
+{
+	uint32_t typemask = 0;
+	size_t i;
+	for (i = 0; i < sz; i++)
+	{
+		typemask |= 1UL << vals[i].type;
+	}
+	return typemask;
+}
+
+static inline UBJ_TYPE typemask2type(uint32_t v)
+{
+	unsigned int r = 0; // r will be lg(v)
+
+	while (v >>= 1) // unroll for more speed...
+	{
+		r++;
+	}
+	return (UBJ_TYPE)r;
+}
+static UBJ_TYPE compute_best_integer_type(ubjr_dynamic_t* vals, size_t sz)
+{
+	uint32_t typemask = 0;
+	size_t i;
+	for (i = 0; i < sz; i++)
+	{
+		typemask |= 1UL << ubjw_min_integer_type(vals[i].integer);
+	}
+	return typemask2type(typemask);
+}
+static uint32_t compute_best_string_type(ubjr_dynamic_t* vals, size_t sz)
+{
+	size_t i;
+	for (i = 0; i < sz; i++)
+	{
+		if (strlen(vals[i].string) > 1)
+		{
+			return UBJ_STRING;
+		}
+	}
+	return UBJ_CHAR;
+}
+static UBJ_TYPE optimize_type(UBJ_TYPE typein,ubjr_dynamic_t* vals, size_t sz)
+{
+	static const uint32_t intmask = (1 << UBJ_INT8) | (1 << UBJ_UINT8) | (1 << UBJ_INT16) | (1 << UBJ_INT32) | (1 << UBJ_INT64);
+	static const uint32_t stringmask = (1 << UBJ_STRING) | (1 << UBJ_CHAR);
+	if (typein != UBJ_MIXED)
+		return typein;
+	//integer optimization can be done here...
+	uint32_t tm = compute_typemask(vals, sz);
+	if ((tm & intmask) == tm) //if all values are integers
+	{
+		return compute_best_integer_type(vals,sz);	//calculate the optimum type given the data
+	}
+	else if ((tm & stringmask) == tm)
+	{
+		return compute_best_string_type(vals,sz);
+	}
+	else if(tm && !(tm & (tm- 1)))  //if only one bit is set in typemask
+	{
+		return typemask2type(tm); //figure out which bit is set.
+	}
+	else
+	{
+		return UBJ_MIXED;
+	}
+}
+
+void ubjrw_write_dynamic(ubjw_context_t* ctx, ubjr_dynamic_t dobj,uint8_t optimize)
+{
+	UBJ_TYPE ctyp,otyp;
+	size_t csize;
+	uint8_t* cvalues;
+	switch (dobj.type)
+	{
+	case UBJ_MIXED:
+		return;///error, can't be mixed
+	case UBJ_NULLTYPE:
+		ubjw_write_null(ctx);
+		return;
+	case UBJ_NOOP:
+		ubjw_write_noop(ctx);
+		return;
+	case UBJ_BOOL_FALSE:
+		ubjw_write_bool(ctx, 0);
+		return;
+	case UBJ_BOOL_TRUE:
+		ubjw_write_bool(ctx, 1);
+		return;
+	case UBJ_CHAR:
+		ubjw_write_char(ctx, *dobj.string);//first character of string
+		return;
+	case UBJ_STRING:
+		ubjw_write_string(ctx, dobj.string);
+		return;
+	case UBJ_HIGH_PRECISION:
+		ubjw_write_high_precision(ctx, dobj.string);
+		return;
+	case UBJ_INT8:
+		ubjw_write_int8(ctx, (int8_t)dobj.integer);
+		return;
+	case UBJ_UINT8:
+		ubjw_write_uint8(ctx, (uint8_t)dobj.integer);
+		return;
+	case UBJ_INT16:
+		ubjw_write_int16(ctx, (int16_t)dobj.integer);
+		return;
+	case UBJ_INT32:
+		ubjw_write_int32(ctx, (int32_t)dobj.integer);
+		return;
+	case UBJ_INT64:
+		ubjw_write_int64(ctx, dobj.integer);
+		return;
+	case UBJ_FLOAT32:
+		ubjw_write_float32(ctx, (float)dobj.real);
+		return;
+	case UBJ_FLOAT64:
+		ubjw_write_float64(ctx, dobj.real);
+		return;
+	case UBJ_ARRAY:
+		if ((dobj.container_array.originally_sized || optimize) //if we optimize an unsized array to a sized one or the original is sized
+			&& dobj.container_array.type != UBJ_MIXED 
+			&& dobj.container_array.type != UBJ_OBJECT 
+			&& dobj.container_array.type != UBJ_ARRAY)
+		{
+			ubjw_write_buffer(ctx, dobj.container_array.values, dobj.container_array.type, dobj.container_array.size);
+			return;
+		}
+		else
+		{
+			ctyp = dobj.container_array.type;
+			csize = dobj.container_array.size;
+			cvalues = dobj.container_array.values;
+			otyp = optimize ? optimize_type(ctyp,(ubjr_dynamic_t*)cvalues,csize) : ctyp;
+			ubjw_begin_array(ctx, otyp, (dobj.container_array.originally_sized || optimize) ? csize : 0);
+			break;
+		}
+	case UBJ_OBJECT:
+		{
+			ctyp = dobj.container_object.type;
+			csize = dobj.container_object.size;
+			cvalues = dobj.container_object.values;
+			otyp = optimize ? optimize_type(ctyp, (ubjr_dynamic_t*)cvalues, csize) : ctyp;
+			ubjw_begin_object(ctx, otyp, (dobj.container_object.originally_sized || optimize) ? csize : 0);
+			break;
+		}
+	};
+	{
+		size_t i;
+		ubjr_dynamic_t scratch;
+		size_t ls = UBJR_TYPE_localsize[ctyp];
+
+		for (i = 0; i < csize; i++)
+		{
+			if (dobj.type == UBJ_OBJECT)
+			{
+				ubjw_write_key(ctx, dobj.container_object.keys[i]);
+			}
+			scratch = priv_ubjr_pointer_to_dynamic(ctyp, cvalues + ls*i);
+			scratch.type = (otyp == UBJ_MIXED ? scratch.type : otyp);
+			ubjrw_write_dynamic(ctx, scratch,optimize);
+		}
+		ubjw_end(ctx);
+	}
+
+}
diff --git a/src/lib/ubjson/ubjw.c b/src/lib/ubjson/ubjw.c
new file mode 100644
index 0000000..5cd060a
--- /dev/null
+++ b/src/lib/ubjson/ubjw.c
@@ -0,0 +1,618 @@
+#include "ubj.h"
+#include "ubj_internal.h"
+
+#define CONTAINER_IS_SIZED		0x1
+#define CONTAINER_IS_TYPED		0x2
+#define CONTAINER_IS_UBJ_ARRAY		0x4
+#define CONTAINER_IS_UBJ_OBJECT		0x8
+
+#define CONTAINER_EXPECTS_KEY	0x10
+
+#define CONTAINER_STACK_MAX		64
+#define BUFFER_OUT_SIZE			1024
+
+#define MAX_DIMS	8
+
+
+struct priv_ubjw_container_t
+{
+	uint8_t flags;
+	UBJ_TYPE type;
+	size_t elements_remaining;
+};
+
+struct ubjw_context_t_s
+{
+	size_t(*write_cb)(const void* data, size_t size, size_t count, void* userdata);
+	int(*close_cb)(void* userdata);
+	void (*error_cb)(const char* error_msg);
+	
+	void* userdata;
+
+	struct priv_ubjw_container_t container_stack[CONTAINER_STACK_MAX];
+	struct priv_ubjw_container_t* head;
+
+	uint8_t ignore_container_flags;
+
+	uint16_t last_error_code;
+
+	size_t total_written;
+};
+
+
+
+ubjw_context_t* ubjw_open_callback(void* userdata,
+	size_t(*write_cb)(const void* data, size_t size, size_t count, void* userdata),
+	int(*close_cb)(void* userdata),
+	void (*error_cb)(const char* error_msg)
+ 					)
+{
+	ubjw_context_t* ctx = (ubjw_context_t*)malloc(sizeof(ubjw_context_t));
+	ctx->userdata = userdata;
+	ctx->write_cb = write_cb;
+	ctx->close_cb = close_cb;
+	ctx->error_cb = error_cb;
+	
+	ctx->head = ctx->container_stack;
+	ctx->head->flags = 0;
+	ctx->head->type = UBJ_MIXED;
+	ctx->head->elements_remaining = 0;
+	//ctx->head->num_dims=1;
+
+	ctx->ignore_container_flags = 0;
+
+	ctx->last_error_code = 0;
+
+	ctx->total_written = 0;
+	return ctx;
+}
+ubjw_context_t* ubjw_open_file(FILE* fd)
+{
+	return ubjw_open_callback(fd, (void*)fwrite,(void*)fclose,NULL);
+}
+
+struct mem_w_fd
+{
+	uint8_t *begin,*current, *end;
+};
+
+static int memclose(void* mfd)
+{
+	free(mfd);
+	return 0;
+}
+static size_t memwrite(const void* data, size_t size, size_t count, struct mem_w_fd* fp)
+{
+	size_t n = size*count;
+	size_t lim = fp->end - fp->current;
+	if (lim < n)
+	{
+		n = lim;
+	}
+	memcpy(fp->current, data, n);
+	fp->current += n;
+	return n;
+}
+
+ubjw_context_t* ubjw_open_memory(uint8_t* be, uint8_t* en)
+{
+	struct mem_w_fd* mfd = (struct mem_w_fd*)malloc(sizeof(struct mem_w_fd));
+	mfd->current = be;
+	mfd->begin = be;
+	mfd->end = en;
+	return ubjw_open_callback(mfd, (void*)memwrite, (void*)memclose,NULL);
+}
+
+static inline void priv_ubjw_context_append(ubjw_context_t* ctx, uint8_t a)
+{
+	ctx->total_written += 1;
+	ctx->write_cb(&a, 1, 1, ctx->userdata);
+}
+
+static inline void priv_disassembly_begin(ubjw_context_t* ctx)
+{
+#ifdef UBJW_DISASSEMBLY_MODE
+	priv_ubjw_context_append(ctx, (uint8_t)'[');
+#endif
+}
+static inline void priv_disassembly_end(ubjw_context_t* ctx)
+{
+#ifdef UBJW_DISASSEMBLY_MODE
+	priv_ubjw_context_append(ctx, (uint8_t)']');
+#endif
+}
+static inline void priv_disassembly_indent(ubjw_context_t* ctx)
+{
+#ifdef UBJW_DISASSEMBLY_MODE
+	int n = ctx->head - ctx->container_stack;
+	int i;
+	priv_ubjw_context_append(ctx, (uint8_t)'\n');
+	for (i = 0; i < n; i++)
+	{
+		priv_ubjw_context_append(ctx, (uint8_t)'\t');
+	}
+#endif
+}
+
+static inline void priv_ubjw_context_finish_container(ubjw_context_t* ctx, struct priv_ubjw_container_t* head)
+{
+	if (head->flags & CONTAINER_IS_SIZED)
+	{
+		if (head->elements_remaining > 0)
+		{
+			//error not all elements written
+		}
+	}
+	else
+	{
+		priv_disassembly_begin(ctx);
+		if (head->flags & CONTAINER_IS_UBJ_ARRAY)
+		{
+			priv_ubjw_context_append(ctx, (uint8_t)']');
+		}
+		else if (head->flags & CONTAINER_IS_UBJ_OBJECT)
+		{
+			priv_ubjw_context_append(ctx, (uint8_t)'}');
+		}
+		priv_disassembly_end(ctx);
+	}
+}
+
+static inline priv_ubjw_container_stack_push(ubjw_context_t* ctx, const struct priv_ubjw_container_t* cnt)
+{
+	size_t height = ctx->head-ctx->container_stack+1;
+	if(height < CONTAINER_STACK_MAX)
+	{
+		*(++(ctx->head))=*cnt;
+	}
+	else
+	{
+		//todo::error
+	}
+}
+static inline struct priv_ubjw_container_t priv_ubjw_container_stack_pop(ubjw_context_t* ctx)
+{
+	return *ctx->head--;
+}
+
+size_t ubjw_close_context(ubjw_context_t* ctx)
+{
+	while (ctx->head > ctx->container_stack)
+	{
+		struct priv_ubjw_container_t cnt = priv_ubjw_container_stack_pop(ctx);
+		priv_ubjw_context_finish_container(ctx, &cnt);
+	};
+	size_t n = ctx->total_written;
+	if (ctx->close_cb)
+		ctx->close_cb(ctx->userdata);
+	free(ctx);
+	return n;
+}
+
+
+static inline size_t priv_ubjw_context_write(ubjw_context_t* ctx, const uint8_t* data, size_t sz)
+{
+	ctx->total_written += sz;
+	return ctx->write_cb(data, 1, sz, ctx->userdata);
+}
+
+static inline void priv_ubjw_tag_public(ubjw_context_t* ctx, UBJ_TYPE tid)
+{
+	struct priv_ubjw_container_t* ch = ctx->head;
+	if (!ctx->ignore_container_flags)
+	{
+
+		/*if (
+			(!(ch->flags & (CONTAINER_IS_UBJ_ARRAY | CONTAINER_IS_UBJ_OBJECT))) &&
+			(tid != UBJ_ARRAY && tid !=UBJ_OBJECT))
+		{
+			//error, only array and object can be first written
+		}*/
+
+		if (ch->flags & CONTAINER_IS_UBJ_OBJECT)
+		{
+			if (ch->flags & CONTAINER_EXPECTS_KEY)
+			{
+				//error,a key expected
+				return;
+			}
+			ch->flags |= CONTAINER_EXPECTS_KEY; //set key expected next time in this context
+		}
+		else
+		{
+			priv_disassembly_indent(ctx);
+		}
+
+		if (ch->flags & CONTAINER_IS_SIZED)
+		{
+			ch->elements_remaining--; //todo: error if elements remaining is 0;
+		}
+
+		if ((ch->flags & CONTAINER_IS_TYPED) && ch->type == tid)
+		{
+			return;
+		}
+	}
+	priv_disassembly_begin(ctx);
+	priv_ubjw_context_append(ctx, UBJI_TYPEC_convert[tid]);
+	priv_disassembly_end(ctx);
+}
+
+static inline void priv_ubjw_write_raw_string(ubjw_context_t* ctx, const char* out)//TODO: possibly use a safe string
+{
+	size_t n = strlen(out);
+	ctx->ignore_container_flags = 1; 
+	ubjw_write_integer(ctx, (int64_t)n);
+	ctx->ignore_container_flags = 0;
+	priv_disassembly_begin(ctx);
+	priv_ubjw_context_write(ctx, (const uint8_t*)out, n);
+	priv_disassembly_end(ctx);
+}
+void ubjw_write_string(ubjw_context_t* ctx, const char* out)
+{
+	priv_ubjw_tag_public(ctx,UBJ_STRING);
+	priv_ubjw_write_raw_string(ctx, out);
+}
+
+static inline void priv_ubjw_write_raw_char(ubjw_context_t* ctx, char out)
+{
+	priv_disassembly_begin(ctx);
+	priv_ubjw_context_append(ctx, (uint8_t)out);
+	priv_disassembly_end(ctx);
+}
+void ubjw_write_char(ubjw_context_t* ctx, char out)
+{
+	priv_ubjw_tag_public(ctx,UBJ_CHAR);
+	priv_ubjw_write_raw_char(ctx, out);
+}
+
+#ifndef min
+static inline size_t min(size_t x,size_t y)
+{
+	return x < y ? x : y;
+}
+#endif
+
+#ifdef UBJW_DISASSEMBLY_MODE
+#include <stdarg.h>  
+#define DISASSEMBLY_PRINT_BUFFER_SIZE 1024
+
+static inline priv_disassembly_print(ubjw_context_t* ctx, const char* format,...)
+{
+	char buffer[DISASSEMBLY_PRINT_BUFFER_SIZE];
+	va_list args; 
+	va_start(args, format);
+	int n=vsnprintf(buffer, DISASSEMBLY_PRINT_BUFFER_SIZE, format, args);
+	n = min(n, DISASSEMBLY_PRINT_BUFFER_SIZE);
+	priv_ubjw_context_write(ctx, buffer,n);
+	va_end(args);
+}
+#endif
+
+static inline void priv_ubjw_write_raw_uint8(ubjw_context_t* ctx, uint8_t out)
+{
+	priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+	priv_ubjw_context_append(ctx, out);
+#else
+	priv_disassembly_print(ctx, "%hhu", out);
+#endif
+	priv_disassembly_end(ctx);
+}
+void ubjw_write_uint8(ubjw_context_t* ctx, uint8_t out)
+{
+	priv_ubjw_tag_public(ctx,UBJ_UINT8);
+	priv_ubjw_write_raw_uint8(ctx, out);
+}
+
+static inline void priv_ubjw_write_raw_int8(ubjw_context_t* ctx, int8_t out)
+{
+	priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+	priv_ubjw_context_append(ctx, *(uint8_t*)&out);
+#else
+	priv_disassembly_print(ctx, "%hhd", out);
+#endif
+	priv_disassembly_end(ctx);
+}
+void ubjw_write_int8(ubjw_context_t* ctx, int8_t out)
+{
+	priv_ubjw_tag_public(ctx,UBJ_INT8);
+	priv_ubjw_write_raw_int8(ctx, out);
+}
+
+static inline void priv_ubjw_write_raw_int16(ubjw_context_t* ctx, int16_t out)
+{
+	priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+	uint8_t buf[2];
+	_to_bigendian16(buf, *(uint16_t*)&out);
+	priv_ubjw_context_write(ctx, buf, 2);
+#else
+	priv_disassembly_print(ctx, "%hd", out);
+#endif
+	priv_disassembly_end(ctx);
+}
+void ubjw_write_int16(ubjw_context_t* ctx, int16_t out)
+{
+	priv_ubjw_tag_public(ctx,UBJ_INT16);
+	priv_ubjw_write_raw_int16(ctx, out);
+}
+static inline void priv_ubjw_write_raw_int32(ubjw_context_t* ctx, int32_t out)
+{
+	priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+	uint8_t buf[4];
+	_to_bigendian32(buf, *(uint32_t*)&out);
+	priv_ubjw_context_write(ctx, buf, 4);
+#else
+	priv_disassembly_print(ctx, "%ld", out);
+#endif
+	priv_disassembly_end(ctx);
+}
+void ubjw_write_int32(ubjw_context_t* ctx, int32_t out)
+{
+	priv_ubjw_tag_public(ctx,UBJ_INT32);
+	priv_ubjw_write_raw_int32(ctx, out);
+}
+static inline void priv_ubjw_write_raw_int64(ubjw_context_t* ctx, int64_t out)
+{
+	priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+	uint8_t buf[8];
+	_to_bigendian64(buf, *(uint64_t*)&out);
+	priv_ubjw_context_write(ctx, buf, 8);
+#else
+	priv_disassembly_print(ctx, "%lld", out);
+#endif
+	priv_disassembly_end(ctx);
+}
+void ubjw_write_int64(ubjw_context_t* ctx, int64_t out)
+{
+	priv_ubjw_tag_public(ctx,UBJ_INT64);
+	priv_ubjw_write_raw_int64(ctx, out);
+}
+
+void ubjw_write_high_precision(ubjw_context_t* ctx, const char* hp)
+{
+	priv_ubjw_tag_public(ctx,UBJ_HIGH_PRECISION);
+	priv_ubjw_write_raw_string(ctx, hp);
+}
+UBJ_TYPE ubjw_min_integer_type(int64_t in)
+{
+	uint64_t mc = llabs(in);
+	if (mc < 0x80)
+	{
+		return UBJ_INT8;
+	}
+	else if (in > 0 && mc < 0x100)
+	{
+		return UBJ_UINT8;
+	}
+	else if (mc < 0x8000)
+	{
+		return UBJ_INT16;
+	}
+	else if (mc < 0x80000000)
+	{
+		return UBJ_INT32;
+	}
+	else
+	{
+		return UBJ_INT64;
+	}
+}
+
+void ubjw_write_integer(ubjw_context_t* ctx, int64_t out)
+{
+	switch (ubjw_min_integer_type(out))
+	{
+	case UBJ_INT8:
+		ubjw_write_int8(ctx, (int8_t)out);
+		break;
+	case UBJ_UINT8:
+		ubjw_write_uint8(ctx, (uint8_t)out);
+		break;
+	case UBJ_INT16:
+		ubjw_write_int16(ctx, (int16_t)out);
+		break;
+	case UBJ_INT32:
+		ubjw_write_int32(ctx, (int32_t)out);
+		break;
+	default:
+		ubjw_write_int64(ctx, out);
+		break;
+	};
+}
+
+static inline void priv_ubjw_write_raw_float32(ubjw_context_t* ctx, float out)
+{
+	priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+	uint32_t fout = *(uint32_t*)&out;
+	uint8_t outbuf[4];
+	_to_bigendian32(outbuf, fout);
+	priv_ubjw_context_write(ctx, outbuf, 4);
+#else
+	priv_disassembly_print(ctx, "%g", out);
+#endif
+	priv_disassembly_end(ctx);
+
+}
+void ubjw_write_float32(ubjw_context_t* ctx, float out)
+{
+	priv_ubjw_tag_public(ctx,UBJ_FLOAT32);
+	priv_ubjw_write_raw_float32(ctx, out);
+}
+static inline void priv_ubjw_write_raw_float64(ubjw_context_t* ctx, double out)
+{
+	priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+	uint64_t fout = *(uint64_t*)&out;
+	uint8_t outbuf[8];
+	_to_bigendian64(outbuf, fout);
+	priv_ubjw_context_write(ctx, outbuf, 8);
+#else
+	priv_disassembly_print(ctx, "%g", out);
+#endif
+	priv_disassembly_end(ctx);
+}
+void ubjw_write_float64(ubjw_context_t* ctx, double out)
+{
+	priv_ubjw_tag_public(ctx,UBJ_FLOAT64);
+	priv_ubjw_write_raw_float64(ctx, out);
+}
+
+void ubjw_write_floating_point(ubjw_context_t* ctx, double out)
+{
+	//this may not be possible to implement correctly...for now we just write it as a float64'
+	ubjw_write_float64(ctx,out);
+}
+
+void ubjw_write_noop(ubjw_context_t* ctx)
+{
+	priv_ubjw_tag_public(ctx,UBJ_NOOP);
+}
+void ubjw_write_null(ubjw_context_t* ctx)
+{
+	priv_ubjw_tag_public(ctx,UBJ_NULLTYPE);
+}
+void ubjw_write_bool(ubjw_context_t* ctx, uint8_t out)
+{
+	priv_ubjw_tag_public(ctx,(out ? UBJ_BOOL_TRUE : UBJ_BOOL_FALSE));
+}
+
+void priv_ubjw_begin_container(struct priv_ubjw_container_t* cnt, ubjw_context_t* ctx, UBJ_TYPE typ, size_t count)
+{
+	cnt->flags=0;
+	cnt->elements_remaining = count;
+	cnt->type = typ;
+
+	if (typ != UBJ_MIXED)
+	{
+		if (count == 0)
+		{
+			//error and return;
+		}
+		priv_disassembly_begin(ctx);
+		priv_ubjw_context_append(ctx, '$');
+		priv_disassembly_end(ctx);
+
+		priv_disassembly_begin(ctx);
+		priv_ubjw_context_append(ctx, UBJI_TYPEC_convert[typ]);
+		priv_disassembly_end(ctx);
+
+		cnt->flags |= CONTAINER_IS_TYPED;
+	}
+	if (count != 0)
+	{
+		priv_disassembly_begin(ctx);
+		priv_ubjw_context_append(ctx, '#');
+		priv_disassembly_end(ctx);
+
+		ctx->ignore_container_flags = 1;
+		ubjw_write_integer(ctx, (int64_t)count);
+		ctx->ignore_container_flags = 0;
+		
+		cnt->flags |= CONTAINER_IS_SIZED;
+		cnt->elements_remaining = count;
+	}
+}
+void ubjw_begin_array(ubjw_context_t* ctx, UBJ_TYPE type, size_t count)
+{
+	priv_ubjw_tag_public(ctx, UBJ_ARRAY); //todo: should this happen before any erro potential?
+	struct priv_ubjw_container_t ch;
+	priv_ubjw_begin_container(&ch, ctx, type, count);
+	ch.flags |= CONTAINER_IS_UBJ_ARRAY;
+	priv_ubjw_container_stack_push(ctx, &ch);
+}
+void ubjw_begin_ndarray(ubjw_context_t* dst, UBJ_TYPE type, const size_t* dims, uint8_t ndims);
+void ubjw_write_ndbuffer(ubjw_context_t* dst, const uint8_t* data, UBJ_TYPE type, const size_t* dims, uint8_t ndims);
+
+void ubjw_begin_object(ubjw_context_t* ctx, UBJ_TYPE type, size_t count)
+{
+	priv_ubjw_tag_public(ctx, UBJ_OBJECT);
+	struct priv_ubjw_container_t ch;
+	priv_ubjw_begin_container(&ch, ctx, type, count);
+	ch.flags |= CONTAINER_EXPECTS_KEY | CONTAINER_IS_UBJ_OBJECT;
+	priv_ubjw_container_stack_push(ctx, &ch);
+}
+void ubjw_write_key(ubjw_context_t* ctx, const char* key)
+{
+	if (ctx->head->flags & CONTAINER_EXPECTS_KEY && ctx->head->flags & CONTAINER_IS_UBJ_OBJECT)
+	{
+		priv_disassembly_indent(ctx);
+		priv_ubjw_write_raw_string(ctx, key);
+		ctx->head->flags ^= CONTAINER_EXPECTS_KEY; //turn off container 
+	}
+	else
+	{
+		//error unexpected key
+	}
+}
+void ubjw_end(ubjw_context_t* ctx)
+{
+	struct priv_ubjw_container_t ch = priv_ubjw_container_stack_pop(ctx);
+	if ((ch.flags & CONTAINER_IS_UBJ_OBJECT) && !(ch.flags & CONTAINER_EXPECTS_KEY))
+	{
+		//error expected value
+	}
+	priv_disassembly_indent(ctx);
+	priv_ubjw_context_finish_container(ctx, &ch);
+}
+
+
+static inline void priv_ubjw_write_byteswap(ubjw_context_t* ctx, const uint8_t* data, int sz, size_t count)
+{
+	uint8_t buf[BUFFER_OUT_SIZE];
+
+	size_t i;
+	size_t nbytes = sz*count;
+	for (i = 0; i < nbytes; i+=BUFFER_OUT_SIZE)
+	{
+		size_t npass = min(nbytes - i, BUFFER_OUT_SIZE);
+		memcpy(buf, data + i, npass);
+		buf_endian_swap(buf, sz, npass/sz);
+		priv_ubjw_context_write(ctx, buf, npass);
+	}
+}
+void ubjw_write_buffer(ubjw_context_t* ctx, const uint8_t* data, UBJ_TYPE type, size_t count)
+{
+	int typesz = UBJI_TYPE_size[type];
+	if (typesz < 0)
+	{
+		//error cannot write an STC buffer of this type.
+	}
+	ubjw_begin_array(ctx, type, count);
+	if (type == UBJ_STRING || type == UBJ_HIGH_PRECISION)
+	{
+		const char** databufs = (const char**)data;
+		size_t i;
+		for (i = 0; i < count; i++)
+		{
+			priv_ubjw_write_raw_string(ctx, databufs[i]);
+		}
+	}
+#ifndef UBJW_DISASSEMBLY_MODE
+	else if (typesz == 1 || _is_bigendian())
+	{
+		size_t n = typesz*count;
+		priv_ubjw_context_write(ctx, data, typesz*count);
+	}
+	else if (typesz > 1) //and not big-endian
+	{
+		priv_ubjw_write_byteswap(ctx, data,typesz,count);
+	}
+#else
+	else
+	{
+		size_t i;
+		for (i = 0; i < count; i++)
+		{
+			ubjr_dynamic_t dyn = priv_ubjr_pointer_to_dynamic(type, data + i*typesz);
+			ubjrw_write_dynamic(ctx, dyn, 0);
+		}
+	}
+#endif
+	ubjw_end(ctx);
+}
-- 
GitLab