diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app/prototest/Makefile.inc | 96 | ||||
-rw-r--r-- | src/app/prototest/main.cc | 79 | ||||
-rw-r--r-- | src/lib/binn.cc | 3372 | ||||
-rw-r--r-- | src/lib/capnp-c/capn-malloc.cc | 424 | ||||
-rw-r--r-- | src/lib/capnp-c/capn-stream.cc | 217 | ||||
-rw-r--r-- | src/lib/capnp-c/capn.cc | 1117 | ||||
-rw-r--r-- | src/lib/mpack/mpack.cc | 6440 | ||||
-rw-r--r-- | src/lib/mpmalloc.cc | 42 | ||||
-rw-r--r-- | src/lib/nanopb/pb_common.cc | 97 | ||||
-rw-r--r-- | src/lib/nanopb/pb_decode.cc | 1528 | ||||
-rw-r--r-- | src/lib/nanopb/pb_encode.cc | 893 | ||||
-rw-r--r-- | src/lib/ubjson/ubjr.c | 525 | ||||
-rw-r--r-- | src/lib/ubjson/ubjrw.c | 169 | ||||
-rw-r--r-- | src/lib/ubjson/ubjw.c | 626 | ||||
-rw-r--r-- | src/lib/xdr.cc | 188 | ||||
-rw-r--r-- | src/lib/xdr16.cc | 216 |
16 files changed, 16029 insertions, 0 deletions
diff --git a/src/app/prototest/Makefile.inc b/src/app/prototest/Makefile.inc new file mode 100644 index 0000000..c088f6b --- /dev/null +++ b/src/app/prototest/Makefile.inc @@ -0,0 +1,96 @@ +ifeq (${aspectc}, 1) + CXX_FLAGS += --Xweaver -asrc/app/prototest/prototest.ah --Xcompiler +endif + +ifeq (${prototest_bench_energy}, 1) + loop ?= 1 + COMMON_FLAGS += -DPROTOTEST_BENCH_ENERGY +endif + +ifeq (${prototest_bench_cycles}, 1) + loop ?= 0 + arch_drivers += ,counter + COMMON_FLAGS += -DPROTOTEST_BENCH_CYCLES +endif + +ifneq (${prototest_include_global}, ) + COMMON_FLAGS += -DPROTOTEST_INCLUDE_GLOBAL=${prototest_include_global} +endif + +ifneq (${prototest_include_local}, ) + COMMON_FLAGS += -DPROTOTEST_INCLUDE_LOCAL=${prototest_include_local} +endif + +ifeq (${prototest_arduinojson}, 1) + COMMON_FLAGS += -DPROTOTEST_ARDUINOJSON +endif + +ifeq (${prototest_binn}, 1) + COMMON_FLAGS += -DPROTOTEST_BINN + CXX_TARGETS += src/lib/binn.cc +endif + +ifeq (${prototest_capnproto_c}, 1) + COMMON_FLAGS += -DPROTOTEST_CAPNPROTO_C + CXX_TARGETS += src/app/prototest/capnp_c_bench.capnp.cc + CXX_TARGETS += src/lib/capnp-c/capn.cc + CXX_TARGETS += src/lib/capnp-c/capn-malloc.cc + CXX_TARGETS += src/lib/capnp-c/capn-stream.cc + INCLUDES += -Iinclude/lib/capnp-c +endif + +ifeq (${prototest_manualjson}, 1) + COMMON_FLAGS += -DPROTOTEST_MANUALJSON +endif + +ifeq (${prototest_modernjson}, 1) + COMMON_FLAGS += -DPROTOTEST_MODERNJSON + ostream = 1 +endif + +ifeq (${prototest_mpack}, 1) + COMMON_FLAGS += -DPROTOTEST_MPACK + CXX_TARGETS += src/lib/mpack/mpack.cc + INCLUDES += -Iinclude/lib/mpack +endif + +ifeq (${prototest_nanopb}, 1) + COMMON_FLAGS += -DPROTOTEST_NANOPB + CXX_TARGETS += src/app/prototest/nanopbbench.pb.cc src/lib/nanopb/pb_common.cc + CXX_TARGETS += src/lib/nanopb/pb_decode.cc src/lib/nanopb/pb_encode.cc + INCLUDES += -Iinclude/lib/nanopb +endif + +ifeq (${prototest_ubjson}, 1) + COMMON_FLAGS += -DPROTOTEST_UBJSON + C_TARGETS += src/lib/ubjson/ubjr.c src/lib/ubjson/ubjw.c + INCLUDES += -Iinclude/lib/ubjson +endif + +ifeq (${prototest_xdr}, 1) + COMMON_FLAGS += -DPROTOTEST_XDR + CXX_TARGETS += src/lib/xdr.cc +endif + +ifeq (${prototest_xdr16}, 1) + COMMON_FLAGS += -DPROTOTEST_XDR16 + CXX_TARGETS += src/lib/xdr16.cc +endif + +# Don't try to make .capnp from .capnp.c +%.capnp: ; + +# Don't try to make .proto from .proto.c +%.proto: ; + +%.pb.cc: %.proto + ${QUIET}protoc --plugin=protoc-gen-nanopb=${HOME}/var/ess/protocol-modeling/nanopb/generator/protoc-gen-nanopb --nanopb_out=. src/app/prototest/nanopbbench.proto + ${QUIET}mv src/app/prototest/nanopbbench.pb.c src/app/prototest/nanopbbench.pb.cc + ${QUIET}cp src/app/prototest/nanopbbench.pb.cc /tmp + ${QUIET}sed -i 's!src/app/prototest/!!' src/app/prototest/nanopbbench.pb.cc + ${QUIET}sed -i 's!\#include "src/app/prototest/nanopb.pb.h"!!' src/app/prototest/nanopbbench.pb.h + +%.capnp.cc: %.capnp + ${QUIET}capnp compile -oc $< + ${QUIET}mv $<.c $<.cc + ${QUIET}cp $<.cc /tmp diff --git a/src/app/prototest/main.cc b/src/app/prototest/main.cc new file mode 100644 index 0000000..7fc897e --- /dev/null +++ b/src/app/prototest/main.cc @@ -0,0 +1,79 @@ +#include "arch.h" +#include "driver/gpio.h" +#include "driver/stdout.h" + +#ifdef PROTOTEST_BENCH_CYCLES +#include "driver/counter.h" +#endif + +#ifdef PROTOTEST_ARDUINOJSON +#include "lib/ArduinoJson.h" +#endif +#ifdef PROTOTEST_BINN +#include "lib/binn.h" +#endif +#ifdef PROTOTEST_CAPNPROTO_C +#include <capnp_c.h> +#include "capnp_c_bench.capnp.h" +#endif +#ifdef PROTOTEST_MANUALJSON +#include "object/stdbuf.h" +#endif +#ifdef PROTOTEST_MODERNJSON +#include "lib/modernjson/json.h" +#endif +#ifdef PROTOTEST_MPACK +#include "mpack.h" +#endif +#ifdef PROTOTEST_NANOPB +#include <pb.h> +#include "nanopbbench.pb.h" +#include <pb_encode.h> +#include <pb_decode.h> +#endif +#ifdef PROTOTEST_UBJSON +#include "ubj.h" +#endif +#ifdef PROTOTEST_XDR +#include "lib/xdr.h" +#endif +#ifdef PROTOTEST_XDR16 +#include "lib/xdr16.h" +#endif + +#include <stdint.h> + +#ifdef PROTOTEST_INCLUDE_GLOBAL +#include "prototest_global.cc.inc" +#endif + +void loop(void) +{ + //static uint16_t ts = 0; + //ts++; + +#ifdef PROTOTEST_INCLUDE_LOCAL +#include "prototest_local.cc.inc" +#endif + +} + +int main(void) +{ + arch.setup(); + gpio.setup(); + kout.setup(); + + //gpio.led_on(0); + kout << "Hello, World!" << endl; + +#if defined(PROTOTEST_BENCH_CYCLES) and !defined(PROTOTEST_ARCH_esp8266) + while (1) { + loop(); + } +#else + arch.idle_loop(); +#endif + + return 0; +} diff --git a/src/lib/binn.cc b/src/lib/binn.cc new file mode 100644 index 0000000..b7e4839 --- /dev/null +++ b/src/lib/binn.cc @@ -0,0 +1,3372 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <memory.h> +#include "lib/binn.h" + +#ifdef MULTIPASS_TRACE_MALLOC +#include "lib/mpmalloc.h" +#else +#define mpmalloc malloc +#define mprealloc realloc +#define mpfree free +#endif + +#define UNUSED(x) (void)(x) +#define round(dbl) dbl >= 0.0 ? (int)(dbl + 0.5) : ((dbl - (double)(int)dbl) <= -0.5 ? (int)dbl : (int)(dbl - 0.5)) + +// magic number: 0x1F 0xb1 0x22 0x1F => 0x1FB1221F or 0x1F22B11F +// because the BINN_STORAGE_NOBYTES (binary 000) may not have so many sub-types (BINN_STORAGE_HAS_MORE = 0x10) +#define BINN_MAGIC 0x1F22B11F + +#define MAX_BINN_HEADER 9 // [1:type][4:size][4:count] +#define MIN_BINN_SIZE 3 // [1:type][1:size][1:count] +#define CHUNK_SIZE 256 // 1024 + +#define BINN_STRUCT 1 +#define BINN_BUFFER 2 + +void* (*malloc_fn)(size_t len) = 0; +void* (*realloc_fn)(void *ptr, size_t len) = 0; +void (*free_fn)(void *ptr) = 0; + +/***************************************************************************/ + +#if defined(__alpha__) || defined(__hppa__) || defined(__mips__) || defined(__powerpc__) || defined(__sparc__) +#define BINN_ONLY_ALIGNED_ACCESS +#elif ( defined(__arm__) || defined(__aarch64__) ) && !defined(__ARM_FEATURE_UNALIGNED) +#define BINN_ONLY_ALIGNED_ACCESS +#endif + +#if defined(_WIN32) +#define BIG_ENDIAN 0x1000 +#define LITTLE_ENDIAN 0x0001 +#define BYTE_ORDER LITTLE_ENDIAN +#elif defined(__APPLE__) +/* macros already defined */ +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) +#include <sys/endian.h> +#elif defined(_AIX) +#include <sys/machine.h> +#else +#include <endian.h> +#endif + +#ifndef BYTE_ORDER +#error "BYTE_ORDER not defined" +#endif +#ifndef BIG_ENDIAN +#error "BIG_ENDIAN not defined" +#endif +#ifndef LITTLE_ENDIAN +#error "LITTLE_ENDIAN not defined" +#endif +#if BIG_ENDIAN == LITTLE_ENDIAN +#error "BIG_ENDIAN == LITTLE_ENDIAN" +#endif +#if BYTE_ORDER!=BIG_ENDIAN && BYTE_ORDER!=LITTLE_ENDIAN +#error "BYTE_ORDER not supported" +#endif + +typedef unsigned short int u16; +typedef unsigned int u32; +typedef unsigned long long int u64; + +BINN_PRIVATE void copy_be16(u16 *pdest, u16 *psource) { +#if BYTE_ORDER == LITTLE_ENDIAN + unsigned char *source = (unsigned char *) psource; + unsigned char *dest = (unsigned char *) pdest; + dest[0] = source[1]; + dest[1] = source[0]; +#else // if BYTE_ORDER == BIG_ENDIAN +#ifdef BINN_ONLY_ALIGNED_ACCESS + if (psource % 2 == 0){ // address aligned to 16 bit + *pdest = *psource; + } else { + unsigned char *source = (unsigned char *) psource; + unsigned char *dest = (unsigned char *) pdest; + dest[0] = source[0]; // indexes are the same + dest[1] = source[1]; + } +#else + *pdest = *psource; +#endif +#endif +} + +BINN_PRIVATE void copy_be32(u32 *pdest, u32 *psource) { +#if BYTE_ORDER == LITTLE_ENDIAN + unsigned char *source = (unsigned char *) psource; + unsigned char *dest = (unsigned char *) pdest; + dest[0] = source[3]; + dest[1] = source[2]; + dest[2] = source[1]; + dest[3] = source[0]; +#else // if BYTE_ORDER == BIG_ENDIAN +#ifdef BINN_ONLY_ALIGNED_ACCESS + if (psource % 4 == 0){ // address aligned to 32 bit + *pdest = *psource; + } else { + unsigned char *source = (unsigned char *) psource; + unsigned char *dest = (unsigned char *) pdest; + dest[0] = source[0]; // indexes are the same + dest[1] = source[1]; + dest[2] = source[2]; + dest[3] = source[3]; + } +#else + *pdest = *psource; +#endif +#endif +} + +BINN_PRIVATE void copy_be64(u64 *pdest, u64 *psource) { +#if BYTE_ORDER == LITTLE_ENDIAN + unsigned char *source = (unsigned char *) psource; + unsigned char *dest = (unsigned char *) pdest; + int i; + for (i=0; i < 8; i++) { + dest[i] = source[7-i]; + } +#else // if BYTE_ORDER == BIG_ENDIAN +#ifdef BINN_ONLY_ALIGNED_ACCESS + if (psource % 8 == 0){ // address aligned to 64 bit + *pdest = *psource; + } else { + unsigned char *source = (unsigned char *) psource; + unsigned char *dest = (unsigned char *) pdest; + int i; + for (i=0; i < 8; i++) { + dest[i] = source[i]; // indexes are the same + } + } +#else + *pdest = *psource; +#endif +#endif +} + +/***************************************************************************/ + +#ifndef WIN32 +#define stricmp strcasecmp +#define strnicmp strncasecmp +#endif + +/***************************************************************************/ + +void APIENTRY binn_set_alloc_functions(void* (*new_malloc)(size_t), void* (*new_realloc)(void*,size_t), void (*new_free)(void*)) { + + malloc_fn = new_malloc; + realloc_fn = new_realloc; + free_fn = new_free; + +} + +/***************************************************************************/ + +BINN_PRIVATE void check_alloc_functions() { + + if (malloc_fn == 0) malloc_fn = &mpmalloc; + if (realloc_fn == 0) realloc_fn = &mprealloc; + if (free_fn == 0) free_fn = &mpfree; + +} + +/***************************************************************************/ + +BINN_PRIVATE void * binn_malloc(int size) { + check_alloc_functions(); + return malloc_fn(size); +} + +/***************************************************************************/ + +BINN_PRIVATE void * binn_memdup(void *src, int size) { + void *dest; + + if (src == NULL || size <= 0) return NULL; + dest = binn_malloc(size); + if (dest == NULL) return NULL; + memcpy(dest, src, size); + return dest; + +} + +/***************************************************************************/ + +BINN_PRIVATE size_t strlen2(char *str) { + + if (str == NULL) return 0; + return strlen(str); + +} + +/***************************************************************************/ + +int APIENTRY binn_create_type(int storage_type, int data_type_index) { + if (data_type_index < 0) return -1; + if ((storage_type < BINN_STORAGE_MIN) || (storage_type > BINN_STORAGE_MAX)) return -1; + if (data_type_index < 16) + return storage_type | data_type_index; + else if (data_type_index < 4096) { + storage_type |= BINN_STORAGE_HAS_MORE; + storage_type <<= 8; + data_type_index >>= 4; + return storage_type | data_type_index; + } else + return -1; +} + +/***************************************************************************/ + +BOOL APIENTRY binn_get_type_info(int long_type, int *pstorage_type, int *pextra_type) { + int storage_type, extra_type; + BOOL retval=TRUE; + +again: + + if (long_type < 0) { + goto loc_invalid; + } else if (long_type <= 0xff) { + storage_type = long_type & BINN_STORAGE_MASK; + extra_type = long_type & BINN_TYPE_MASK; + } else if (long_type <= 0xffff) { + storage_type = long_type & BINN_STORAGE_MASK16; + storage_type >>= 8; + extra_type = long_type & BINN_TYPE_MASK16; + extra_type >>= 4; + } else if (long_type & BINN_STORAGE_VIRTUAL) { + //storage_type = BINN_STORAGE_VIRTUAL; + //extra_type = xxx; + long_type &= 0xffff; + goto again; + } else { +loc_invalid: + storage_type = -1; + extra_type = -1; + retval = FALSE; + } + + if (pstorage_type) *pstorage_type = storage_type; + if (pextra_type) *pextra_type = extra_type; + + return retval; + +} + +/***************************************************************************/ + +BOOL APIENTRY binn_create(binn *item, int type, int size, void *pointer) { + BOOL retval=FALSE; + + switch (type) { + case BINN_LIST: + case BINN_MAP: + case BINN_OBJECT: + break; + default: + goto loc_exit; + } + + if ((item == NULL) || (size < 0)) goto loc_exit; + if (size < MIN_BINN_SIZE) { + if (pointer) goto loc_exit; + else size = 0; + } + + memset(item, 0, sizeof(binn)); + + if (pointer) { + item->pre_allocated = TRUE; + item->pbuf = pointer; + item->alloc_size = size; + } else { + item->pre_allocated = FALSE; + if (size == 0) size = CHUNK_SIZE; + pointer = binn_malloc(size); + if (pointer == 0) return INVALID_BINN; + item->pbuf = pointer; + item->alloc_size = size; + } + + item->header = BINN_MAGIC; + //item->allocated = FALSE; -- already zeroed + item->writable = TRUE; + item->used_size = MAX_BINN_HEADER; // save space for the header + item->type = type; + //item->count = 0; -- already zeroed + item->dirty = TRUE; // the header is not written to the buffer + + retval = TRUE; + +loc_exit: + return retval; + +} + +/***************************************************************************/ + +binn * APIENTRY binn_new(int type, int size, void *pointer) { + binn *item; + + item = (binn*) binn_malloc(sizeof(binn)); + + if (binn_create(item, type, size, pointer) == FALSE) { + free_fn(item); + return NULL; + } + + item->allocated = TRUE; + return item; + +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_create_list(binn *list) { + + return binn_create(list, BINN_LIST, 0, NULL); + +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_create_map(binn *map) { + + return binn_create(map, BINN_MAP, 0, NULL); + +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_create_object(binn *object) { + + return binn_create(object, BINN_OBJECT, 0, NULL); + +} + +/***************************************************************************/ + +binn * APIENTRY binn_list() { + return binn_new(BINN_LIST, 0, 0); +} + +/***************************************************************************/ + +binn * APIENTRY binn_map() { + return binn_new(BINN_MAP, 0, 0); +} + +/***************************************************************************/ + +binn * APIENTRY binn_object() { + return binn_new(BINN_OBJECT, 0, 0); +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_load(void *data, binn *value) { + + if ((data == NULL) || (value == NULL)) return FALSE; + memset(value, 0, sizeof(binn)); + value->header = BINN_MAGIC; + //value->allocated = FALSE; -- already zeroed + //value->writable = FALSE; + + if (binn_is_valid(data, &value->type, &value->count, &value->size) == FALSE) return FALSE; + value->ptr = data; + return TRUE; + +} + +/*************************************************************************************/ + +binn * APIENTRY binn_open(void *data) { + binn *item; + + item = (binn*) binn_malloc(sizeof(binn)); + + if (binn_load(data, item) == FALSE) { + free_fn(item); + return NULL; + } + + item->allocated = TRUE; + return item; + +} + +/***************************************************************************/ + +BINN_PRIVATE int binn_get_ptr_type(void *ptr) { + + if (ptr == NULL) return 0; + + switch (*(unsigned int *)ptr) { + case BINN_MAGIC: + return BINN_STRUCT; + default: + return BINN_BUFFER; + } + +} + +/***************************************************************************/ + +BOOL APIENTRY binn_is_struct(void *ptr) { + + if (ptr == NULL) return FALSE; + + if ((*(unsigned int *)ptr) == BINN_MAGIC) { + return TRUE; + } else { + return FALSE; + } + +} + +/***************************************************************************/ + +BINN_PRIVATE int CalcAllocation(int needed_size, int alloc_size) { + int calc_size; + + calc_size = alloc_size; + while (calc_size < needed_size) { + calc_size <<= 1; // same as *= 2 + //calc_size += CHUNK_SIZE; -- this is slower than the above line, because there are more reallocations + } + return calc_size; + +} + +/***************************************************************************/ + +BINN_PRIVATE BOOL CheckAllocation(binn *item, int add_size) { + int alloc_size; + void *ptr; + + if (item->used_size + add_size > item->alloc_size) { + if (item->pre_allocated) return FALSE; + alloc_size = CalcAllocation(item->used_size + add_size, item->alloc_size); + ptr = realloc_fn(item->pbuf, alloc_size); + if (ptr == NULL) return FALSE; + item->pbuf = ptr; + item->alloc_size = alloc_size; + } + + return TRUE; + +} + +/***************************************************************************/ + +#if BYTE_ORDER == BIG_ENDIAN + +BINN_PRIVATE int get_storage_size(int storage_type) { + + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + return 0; + case BINN_STORAGE_BYTE: + return 1; + case BINN_STORAGE_WORD: + return 2; + case BINN_STORAGE_DWORD: + return 4; + case BINN_STORAGE_QWORD: + return 8; + default: + return 0; + } + +} + +#endif + +/***************************************************************************/ + +BINN_PRIVATE unsigned char * AdvanceDataPos(unsigned char *p, unsigned char *plimit) { + unsigned char byte; + int storage_type, DataSize; + + if (p > plimit) return 0; + + byte = *p; p++; + storage_type = byte & BINN_STORAGE_MASK; + if (byte & BINN_STORAGE_HAS_MORE) p++; + + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + //p += 0; + break; + case BINN_STORAGE_BYTE: + p ++; + break; + case BINN_STORAGE_WORD: + p += 2; + break; + case BINN_STORAGE_DWORD: + p += 4; + break; + case BINN_STORAGE_QWORD: + p += 8; + break; + case BINN_STORAGE_BLOB: + if (p + sizeof(int) - 1 > plimit) return 0; + copy_be32((u32*)&DataSize, (u32*)p); + p += 4 + DataSize; + break; + case BINN_STORAGE_CONTAINER: + if (p > plimit) return 0; + DataSize = *((unsigned char*)p); + if (DataSize & 0x80) { + if (p + sizeof(int) - 1 > plimit) return 0; + copy_be32((u32*)&DataSize, (u32*)p); + DataSize &= 0x7FFFFFFF; + } + DataSize--; // remove the type byte already added before + p += DataSize; + break; + case BINN_STORAGE_STRING: + if (p > plimit) return 0; + DataSize = *((unsigned char*)p); + if (DataSize & 0x80) { + if (p + sizeof(int) - 1 > plimit) return 0; + copy_be32((u32*)&DataSize, (u32*)p); + DataSize &= 0x7FFFFFFF; + p+=4; + } else { + p++; + } + p += DataSize; + p++; // null terminator. + break; + default: + return 0; + } + + if (p > plimit) return 0; + + return p; + +} + +/***************************************************************************/ + +BINN_PRIVATE unsigned char * SearchForID(unsigned char *p, int header_size, int size, int numitems, int id) { + unsigned char *plimit, *base; + int i, int32; + + base = p; + plimit = p + size - 1; + p += header_size; + + // search for the ID in all the arguments. + for (i = 0; i < numitems; i++) { + copy_be32((u32*)&int32, (u32*)p); + p += 4; + if (p > plimit) break; + // Compare if the IDs are equal. + if (int32 == id) return p; + // xxx + p = AdvanceDataPos(p, plimit); + if ((p == 0) || (p < base)) break; + } + + return NULL; + +} + +/***************************************************************************/ + +BINN_PRIVATE unsigned char * SearchForKey(unsigned char *p, int header_size, int size, int numitems, char *key) { + unsigned char len, *plimit, *base; + int i, keylen; + + base = p; + plimit = p + size - 1; + p += header_size; + + keylen = strlen(key); + + // search for the key in all the arguments. + for (i = 0; i < numitems; i++) { + len = *((unsigned char *)p); + p++; + if (p > plimit) break; + // Compare if the strings are equal. + if (len > 0) { + if (strnicmp((char*)p, key, len) == 0) { // note that there is no null terminator here + if (keylen == len) { + p += len; + return p; + } + } + p += len; + if (p > plimit) break; + } else if (len == keylen) { // in the case of empty string: "" + return p; + } + // xxx + p = AdvanceDataPos(p, plimit); + if ((p == 0) || (p < base)) break; + } + + return NULL; + +} + +/***************************************************************************/ + +BINN_PRIVATE BOOL AddValue(binn *item, int type, void *pvalue, int size); + +/***************************************************************************/ + +BINN_PRIVATE BOOL binn_list_add_raw(binn *item, int type, void *pvalue, int size) { + + if ((item == NULL) || (item->type != BINN_LIST) || (item->writable == FALSE)) return FALSE; + + //if (CheckAllocation(item, 4) == FALSE) return FALSE; // 4 bytes used for data_store and data_format. + + if (AddValue(item, type, pvalue, size) == FALSE) return FALSE; + + item->count++; + + return TRUE; + +} + +/***************************************************************************/ + +BINN_PRIVATE BOOL binn_object_set_raw(binn *item, char *key, int type, void *pvalue, int size) { + unsigned char *p, len; + int int32; + + if ((item == NULL) || (item->type != BINN_OBJECT) || (item->writable == FALSE)) return FALSE; + + if (key == NULL) return FALSE; + int32 = strlen(key); + if (int32 > 255) return FALSE; + + // is the key already in it? + p = SearchForKey((unsigned char*)item->pbuf, MAX_BINN_HEADER, item->used_size, item->count, key); + if (p) return FALSE; + + // start adding it + + if (CheckAllocation(item, 1 + int32) == FALSE) return FALSE; // bytes used for the key size and the key itself. + + p = ((unsigned char *) item->pbuf) + item->used_size; + len = int32; + *p = len; + p++; + memcpy(p, key, int32); + int32++; // now contains the strlen + 1 byte for the len + item->used_size += int32; + + if (AddValue(item, type, pvalue, size) == FALSE) { + item->used_size -= int32; + return FALSE; + } + + item->count++; + + return TRUE; + +} + +/***************************************************************************/ + +BINN_PRIVATE BOOL binn_map_set_raw(binn *item, int id, int type, void *pvalue, int size) { + unsigned char *p; + + if ((item == NULL) || (item->type != BINN_MAP) || (item->writable == FALSE)) return FALSE; + + // is the ID already in it? + p = SearchForID((unsigned char*)item->pbuf, MAX_BINN_HEADER, item->used_size, item->count, id); + if (p) return FALSE; + + // start adding it + + if (CheckAllocation(item, 4) == FALSE) return FALSE; // 4 bytes used for the id. + + p = ((unsigned char *) item->pbuf) + item->used_size; + copy_be32((u32*)p, (u32*)&id); + item->used_size += 4; + + if (AddValue(item, type, pvalue, size) == FALSE) { + item->used_size -= 4; + return FALSE; + } + + item->count++; + + return TRUE; + +} + +/***************************************************************************/ + +BINN_PRIVATE void * compress_int(int *pstorage_type, int *ptype, void *psource) { + int storage_type, storage_type2, type, type2=0; + int64 vint; + uint64 vuint; + char *pvalue; +#if BYTE_ORDER == BIG_ENDIAN + int size1, size2; +#endif + + storage_type = *pstorage_type; + if (storage_type == BINN_STORAGE_BYTE) return psource; + + type = *ptype; + + switch (type) { + case BINN_INT64: + vint = *(int64*)psource; + goto loc_signed; + case BINN_INT32: + vint = *(int*)psource; + goto loc_signed; + case BINN_INT16: + vint = *(short*)psource; + goto loc_signed; + case BINN_UINT64: + vuint = *(uint64*)psource; + goto loc_positive; + case BINN_UINT32: + vuint = *(unsigned int*)psource; + goto loc_positive; + case BINN_UINT16: + vuint = *(unsigned short*)psource; + goto loc_positive; + } + +loc_signed: + + if (vint >= 0) { + vuint = vint; + goto loc_positive; + } + +//loc_negative: + + if (vint >= INT8_MIN) { + type2 = BINN_INT8; + } else + if (vint >= INT16_MIN) { + type2 = BINN_INT16; + } else + if (vint >= INT32_MIN) { + type2 = BINN_INT32; + } + goto loc_exit; + +loc_positive: + + if (vuint <= UINT8_MAX) { + type2 = BINN_UINT8; + } else + if (vuint <= UINT16_MAX) { + type2 = BINN_UINT16; + } else + if (vuint <= UINT32_MAX) { + type2 = BINN_UINT32; + } + +loc_exit: + + pvalue = (char *) psource; + + if ((type2) && (type2 != type)) { + *ptype = type2; + storage_type2 = binn_get_write_storage(type2); + *pstorage_type = storage_type2; +#if BYTE_ORDER == BIG_ENDIAN + size1 = get_storage_size(storage_type); + size2 = get_storage_size(storage_type2); + pvalue += (size1 - size2); +#endif + } + + return pvalue; + +} + +/***************************************************************************/ + +BINN_PRIVATE int type_family(int type); + +BINN_PRIVATE BOOL AddValue(binn *item, int type, void *pvalue, int size) { + int int32, ArgSize, storage_type, extra_type; + unsigned char *p; + + binn_get_type_info(type, &storage_type, &extra_type); + + if (pvalue == NULL) { + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + break; + case BINN_STORAGE_BLOB: + case BINN_STORAGE_STRING: + if (size == 0) break; // the 2 above are allowed to have 0 length + default: + return FALSE; + } + } + + if ((type_family(type) == BINN_FAMILY_INT) && (item->disable_int_compression == FALSE)) + pvalue = compress_int(&storage_type, &type, pvalue); + + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + size = 0; + ArgSize = size; + break; + case BINN_STORAGE_BYTE: + size = 1; + ArgSize = size; + break; + case BINN_STORAGE_WORD: + size = 2; + ArgSize = size; + break; + case BINN_STORAGE_DWORD: + size = 4; + ArgSize = size; + break; + case BINN_STORAGE_QWORD: + size = 8; + ArgSize = size; + break; + case BINN_STORAGE_BLOB: + if (size < 0) return FALSE; + //if (size == 0) ... + ArgSize = size + 4; + break; + case BINN_STORAGE_STRING: + if (size < 0) return FALSE; + if (size == 0) size = strlen2( (char *) pvalue); + ArgSize = size + 5; // at least this size + break; + case BINN_STORAGE_CONTAINER: + if (size <= 0) return FALSE; + ArgSize = size; + break; + default: + return FALSE; + } + + ArgSize += 2; // at least 2 bytes used for data_type. + if (CheckAllocation(item, ArgSize) == FALSE) return FALSE; + + // Gets the pointer to the next place in buffer + p = ((unsigned char *) item->pbuf) + item->used_size; + + // If the data is not a container, store the data type + if (storage_type != BINN_STORAGE_CONTAINER) { + if (type > 255) { + u16 type16 = type; + copy_be16((u16*)p, (u16*)&type16); + p += 2; + item->used_size += 2; + } else { + *p = type; + p++; + item->used_size++; + } + } + + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + // Nothing to do. + break; + case BINN_STORAGE_BYTE: + *((char *) p) = *((char *) pvalue); + item->used_size += 1; + break; + case BINN_STORAGE_WORD: + copy_be16((u16*)p, (u16*)pvalue); + item->used_size += 2; + break; + case BINN_STORAGE_DWORD: + copy_be32((u32*)p, (u32*)pvalue); + item->used_size += 4; + break; + case BINN_STORAGE_QWORD: + copy_be64((u64*)p, (u64*)pvalue); + item->used_size += 8; + break; + case BINN_STORAGE_BLOB: + copy_be32((u32*)p, (u32*)&size); + p += 4; + memcpy(p, pvalue, size); + item->used_size += 4 + size; + break; + case BINN_STORAGE_STRING: + if (size > 127) { + int32 = size | 0x80000000; + copy_be32((u32*)p, (u32*)&int32); + p += 4; + item->used_size += 4; + } else { + *((unsigned char *) p) = size; + p++; + item->used_size++; + } + memcpy(p, pvalue, size); + p += size; + *((char *) p) = (char) 0; + size++; // null terminator + item->used_size += size; + break; + case BINN_STORAGE_CONTAINER: + memcpy(p, pvalue, size); + item->used_size += size; + break; + } + + item->dirty = TRUE; + + return TRUE; +} + +/***************************************************************************/ + +BINN_PRIVATE BOOL binn_save_header(binn *item) { + unsigned char byte, *p; + int int32, size; + + if (item == NULL) return FALSE; + +#ifndef BINN_DISABLE_SMALL_HEADER + + p = ((unsigned char *) item->pbuf) + MAX_BINN_HEADER; + size = item->used_size - MAX_BINN_HEADER + 3; // at least 3 bytes for the header + + // write the count + if (item->count > 127) { + p -= 4; + size += 3; + int32 = item->count | 0x80000000; + copy_be32((u32*)p, (u32*)&int32); + } else { + p--; + *p = (unsigned char) item->count; + } + + // write the size + if (size > 127) { + p -= 4; + size += 3; + int32 = size | 0x80000000; + copy_be32((u32*)p, (u32*)&int32); + } else { + p--; + *p = (unsigned char) size; + } + + // write the type. + p--; + *p = (unsigned char) item->type; + + // set the values + item->ptr = p; + item->size = size; + + UNUSED(byte); + +#else + + p = (unsigned char *) item->pbuf; + + // write the type. + byte = item->type; + *p = byte; p++; + // write the size + int32 = item->used_size | 0x80000000; + copy_be32((u32*)p, (u32*)&int32); + p+=4; + // write the count + int32 = item->count | 0x80000000; + copy_be32((u32*)p, (u32*)&int32); + + item->ptr = item->pbuf; + item->size = item->used_size; + +#endif + + item->dirty = FALSE; + + return TRUE; + +} + +/***************************************************************************/ + +void APIENTRY binn_free(binn *item) { + + if (item == NULL) return; + + if ((item->writable) && (item->pre_allocated == FALSE)) { + free_fn(item->pbuf); + } + + if (item->freefn) item->freefn(item->ptr); + + if (item->allocated) { + free_fn(item); + } else { + memset(item, 0, sizeof(binn)); + item->header = BINN_MAGIC; + } + +} + +/***************************************************************************/ +// free the binn structure but keeps the binn buffer allocated, returning a pointer to it. use the free function to release the buffer later +void * APIENTRY binn_release(binn *item) { + void *data; + + if (item == NULL) return NULL; + + data = binn_ptr(item); + + if (data > item->pbuf) { + memmove(item->pbuf, data, item->size); + data = item->pbuf; + } + + if (item->allocated) { + free_fn(item); + } else { + memset(item, 0, sizeof(binn)); + item->header = BINN_MAGIC; + } + + return data; + +} + +/***************************************************************************/ + +BINN_PRIVATE BOOL IsValidBinnHeader(void *pbuf, int *ptype, int *pcount, int *psize, int *pheadersize) { + unsigned char byte, *p, *plimit=0; + int int32, type, size, count; + + if (pbuf == NULL) return FALSE; + + p = (unsigned char *) pbuf; + + if (psize && *psize > 0) { + plimit = p + *psize - 1; + } + + // get the type + byte = *p; p++; + if ((byte & BINN_STORAGE_MASK) != BINN_STORAGE_CONTAINER) return FALSE; + if (byte & BINN_STORAGE_HAS_MORE) return FALSE; + type = byte; + + switch (type) { + case BINN_LIST: + case BINN_MAP: + case BINN_OBJECT: + break; + default: + return FALSE; + } + + // get the size + if (plimit && p > plimit) return FALSE; + int32 = *((unsigned char*)p); + if (int32 & 0x80) { + if (plimit && p + sizeof(int) - 1 > plimit) return FALSE; + copy_be32((u32*)&int32, (u32*)p); + int32 &= 0x7FFFFFFF; + p+=4; + } else { + p++; + } + size = int32; + + // get the count + if (plimit && p > plimit) return FALSE; + int32 = *((unsigned char*)p); + if (int32 & 0x80) { + if (plimit && p + sizeof(int) - 1 > plimit) return FALSE; + copy_be32((u32*)&int32, (u32*)p); + int32 &= 0x7FFFFFFF; + p+=4; + } else { + p++; + } + count = int32; + +#if 0 + // get the size + copy_be32((u32*)&size, (u32*)p); + size &= 0x7FFFFFFF; + p+=4; + + // get the count + copy_be32((u32*)&count, (u32*)p); + count &= 0x7FFFFFFF; + p+=4; +#endif + + if ((size < MIN_BINN_SIZE) || (count < 0)) return FALSE; + + // return the values + if (ptype) *ptype = type; + if (pcount) *pcount = count; + if (psize && *psize==0) *psize = size; + if (pheadersize) *pheadersize = (int) (p - (unsigned char*)pbuf); + return TRUE; +} + +/***************************************************************************/ + +BINN_PRIVATE int binn_buf_type(void *pbuf) { + int type; + + if (!IsValidBinnHeader(pbuf, &type, NULL, NULL, NULL)) return INVALID_BINN; + + return type; + +} + +/***************************************************************************/ + +BINN_PRIVATE int binn_buf_count(void *pbuf) { + int nitems; + + if (!IsValidBinnHeader(pbuf, NULL, &nitems, NULL, NULL)) return 0; + + return nitems; + +} + +/***************************************************************************/ + +BINN_PRIVATE int binn_buf_size(void *pbuf) { + int size=0; + + if (!IsValidBinnHeader(pbuf, NULL, NULL, &size, NULL)) return 0; + + return size; + +} + +/***************************************************************************/ + +void * APIENTRY binn_ptr(void *ptr) { + binn *item; + + switch (binn_get_ptr_type(ptr)) { + case BINN_STRUCT: + item = (binn*) ptr; + if (item->writable && item->dirty) { + binn_save_header(item); + } + return item->ptr; + case BINN_BUFFER: + return ptr; + default: + return NULL; + } + +} + +/***************************************************************************/ + +int APIENTRY binn_size(void *ptr) { + binn *item; + + switch (binn_get_ptr_type(ptr)) { + case BINN_STRUCT: + item = (binn*) ptr; + if (item->writable && item->dirty) { + binn_save_header(item); + } + return item->size; + case BINN_BUFFER: + return binn_buf_size(ptr); + default: + return 0; + } + +} + +/***************************************************************************/ + +int APIENTRY binn_type(void *ptr) { + binn *item; + + switch (binn_get_ptr_type(ptr)) { + case BINN_STRUCT: + item = (binn*) ptr; + return item->type; + case BINN_BUFFER: + return binn_buf_type(ptr); + default: + return -1; + } + +} + +/***************************************************************************/ + +int APIENTRY binn_count(void *ptr) { + binn *item; + + switch (binn_get_ptr_type(ptr)) { + case BINN_STRUCT: + item = (binn*) ptr; + return item->count; + case BINN_BUFFER: + return binn_buf_count(ptr); + default: + return -1; + } + +} + +/***************************************************************************/ + +BOOL APIENTRY binn_is_valid_ex(void *ptr, int *ptype, int *pcount, int *psize) { + int i, type, count, size, header_size; + unsigned char *p, *plimit, *base, len; + void *pbuf; + + pbuf = binn_ptr(ptr); + if (pbuf == NULL) return FALSE; + + // is there an informed size? + if (psize && *psize > 0) { + size = *psize; + } else { + size = 0; + } + + if (!IsValidBinnHeader(pbuf, &type, &count, &size, &header_size)) return FALSE; + + // is there an informed size? + if (psize && *psize > 0) { + // is it the same as the one in the buffer? + if (size != *psize) return FALSE; + } + // is there an informed count? + if (pcount && *pcount > 0) { + // is it the same as the one in the buffer? + if (count != *pcount) return FALSE; + } + // is there an informed type? + if (ptype && *ptype != 0) { + // is it the same as the one in the buffer? + if (type != *ptype) return FALSE; + } + + // it could compare the content size with the size informed on the header + + p = (unsigned char *)pbuf; + base = p; + plimit = p + size; + + p += header_size; + + // process all the arguments. + for (i = 0; i < count; i++) { + switch (type) { + case BINN_OBJECT: + // gets the string size (argument name) + len = *p; + p++; + //if (len == 0) goto Invalid; + // increment the used space + p += len; + break; + case BINN_MAP: + // increment the used space + p += 4; + break; + //case BINN_LIST: + // break; + } + // xxx + p = AdvanceDataPos(p, plimit); + if ((p == 0) || (p < base)) goto Invalid; + } + + if (ptype && *ptype==0) *ptype = type; + if (pcount && *pcount==0) *pcount = count; + if (psize && *psize==0) *psize = size; + return TRUE; + +Invalid: + return FALSE; + +} + +/***************************************************************************/ + +BOOL APIENTRY binn_is_valid(void *ptr, int *ptype, int *pcount, int *psize) { + + if (ptype) *ptype = 0; + if (pcount) *pcount = 0; + if (psize) *psize = 0; + + return binn_is_valid_ex(ptr, ptype, pcount, psize); + +} + +/***************************************************************************/ +/*** INTERNAL FUNCTIONS ****************************************************/ +/***************************************************************************/ + +BINN_PRIVATE BOOL GetValue(unsigned char *p, binn *value) { + unsigned char byte; + int data_type, storage_type; //, extra_type; + int DataSize; + void *p2; + + if (value == NULL) return FALSE; + memset(value, 0, sizeof(binn)); + value->header = BINN_MAGIC; + //value->allocated = FALSE; -- already zeroed + //value->writable = FALSE; + + // saves for use with BINN_STORAGE_CONTAINER + p2 = p; + + // read the data type + byte = *p; p++; + storage_type = byte & BINN_STORAGE_MASK; + if (byte & BINN_STORAGE_HAS_MORE) { + data_type = byte << 8; + byte = *p; p++; + data_type |= byte; + //extra_type = data_type & BINN_TYPE_MASK16; + } else { + data_type = byte; + //extra_type = byte & BINN_TYPE_MASK; + } + + //value->storage_type = storage_type; + value->type = data_type; + + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + break; + case BINN_STORAGE_BYTE: + value->vuint8 = *((unsigned char *) p); + value->ptr = p; //value->ptr = &value->vuint8; + break; + case BINN_STORAGE_WORD: + copy_be16((u16*)&value->vint16, (u16*)p); + value->ptr = &value->vint16; + break; + case BINN_STORAGE_DWORD: + copy_be32((u32*)&value->vint32, (u32*)p); + value->ptr = &value->vint32; + break; + case BINN_STORAGE_QWORD: + copy_be64((u64*)&value->vint64, (u64*)p); + value->ptr = &value->vint64; + break; + case BINN_STORAGE_BLOB: + copy_be32((u32*)&value->size, (u32*)p); + p+=4; + value->ptr = p; + break; + case BINN_STORAGE_CONTAINER: + value->ptr = p2; // <-- it returns the pointer to the container, not the data + if (IsValidBinnHeader(p2, NULL, &value->count, &value->size, NULL) == FALSE) return FALSE; + break; + case BINN_STORAGE_STRING: + DataSize = *((unsigned char*)p); + if (DataSize & 0x80) { + copy_be32((u32*)&DataSize, (u32*)p); + DataSize &= 0x7FFFFFFF; + p+=4; + } else { + p++; + } + value->size = DataSize; + value->ptr = p; + break; + default: + return FALSE; + } + + // convert the returned value, if needed + + switch (value->type) { + case BINN_TRUE: + value->type = BINN_BOOL; + value->vbool = TRUE; + value->ptr = &value->vbool; + break; + case BINN_FALSE: + value->type = BINN_BOOL; + value->vbool = FALSE; + value->ptr = &value->vbool; + break; +#ifdef BINN_EXTENDED + case BINN_SINGLE_STR: + value->type = BINN_SINGLE; + value->vfloat = (float) atof((const char*)value->ptr); // converts from string to double, and then to float + value->ptr = &value->vfloat; + break; + case BINN_DOUBLE_STR: + value->type = BINN_DOUBLE; + value->vdouble = atof((const char*)value->ptr); // converts from string to double + value->ptr = &value->vdouble; + break; +#endif + /* + case BINN_DECIMAL: + case BINN_CURRENCYSTR: + case BINN_DATE: + case BINN_DATETIME: + case BINN_TIME: + */ + } + + return TRUE; + +} + +/***************************************************************************/ + +#if BYTE_ORDER == LITTLE_ENDIAN + +// on little-endian devices we store the value so we can return a pointer to integers. +// it's valid only for single-threaded apps. multi-threaded apps must use the _get_ functions instead. + +binn local_value; + +BINN_PRIVATE void * store_value(binn *value) { + + memcpy(&local_value, value, sizeof(binn)); + + switch (binn_get_read_storage(value->type)) { + case BINN_STORAGE_NOBYTES: + // return a valid pointer + case BINN_STORAGE_WORD: + case BINN_STORAGE_DWORD: + case BINN_STORAGE_QWORD: + return &local_value.vint32; // returns the pointer to the converted value, from big-endian to little-endian + } + + return value->ptr; // returns from the on stack value to be thread-safe (for list, map, object, string and blob) + +} + +#endif + +/***************************************************************************/ +/*** READ FUNCTIONS ********************************************************/ +/***************************************************************************/ + +BOOL APIENTRY binn_object_get_value(void *ptr, char *key, binn *value) { + int type, count, size=0, header_size; + unsigned char *p; + + ptr = binn_ptr(ptr); + if ((ptr == 0) || (key == 0) || (value == 0)) return FALSE; + + // check the header + if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE; + + if (type != BINN_OBJECT) return FALSE; + if (count == 0) return FALSE; + + p = (unsigned char *) ptr; + p = SearchForKey(p, header_size, size, count, key); + if (p == FALSE) return FALSE; + + return GetValue(p, value); + +} + +/***************************************************************************/ + +BOOL APIENTRY binn_map_get_value(void* ptr, int id, binn *value) { + int type, count, size=0, header_size; + unsigned char *p; + + ptr = binn_ptr(ptr); + if ((ptr == 0) || (value == 0)) return FALSE; + + // check the header + if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE; + + if (type != BINN_MAP) return FALSE; + if (count == 0) return FALSE; + + p = (unsigned char *) ptr; + p = SearchForID(p, header_size, size, count, id); + if (p == FALSE) return FALSE; + + return GetValue(p, value); + +} + +/***************************************************************************/ + +BOOL APIENTRY binn_list_get_value(void* ptr, int pos, binn *value) { + int i, type, count, size=0, header_size; + unsigned char *p, *plimit, *base; + + ptr = binn_ptr(ptr); + if ((ptr == 0) || (value == 0)) return FALSE; + + // check the header + if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE; + + if (type != BINN_LIST) return FALSE; + if (count == 0) return FALSE; + if ((pos <= 0) | (pos > count)) return FALSE; + pos--; // convert from base 1 to base 0 + + p = (unsigned char *) ptr; + base = p; + plimit = p + size; + p += header_size; + + for (i = 0; i < pos; i++) { + p = AdvanceDataPos(p, plimit); + if ((p == 0) || (p < base)) return FALSE; + } + + return GetValue(p, value); + +} + +/***************************************************************************/ +/*** READ PAIR BY POSITION *************************************************/ +/***************************************************************************/ + +BINN_PRIVATE BOOL binn_read_pair(int expected_type, void *ptr, int pos, int *pid, char *pkey, binn *value) { + int type, count, size=0, header_size; + int i, int32, id, counter=0; + unsigned char *p, *plimit, *base, *key, len; + + ptr = binn_ptr(ptr); + + // check the header + if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE; + + if ((type != expected_type) || (count == 0) || (pos < 1) || (pos > count)) return FALSE; + + p = (unsigned char *) ptr; + base = p; + plimit = p + size - 1; + p += header_size; + + for (i = 0; i < count; i++) { + switch (type) { + case BINN_MAP: + copy_be32((u32*)&int32, (u32*)p); + p += 4; + if (p > plimit) return FALSE; + id = int32; + break; + case BINN_OBJECT: + len = *((unsigned char *)p); p++; + if (p > plimit) return FALSE; + key = p; + p += len; + if (p > plimit) return FALSE; + break; + } + counter++; + if (counter == pos) goto found; + // + p = AdvanceDataPos(p, plimit); + if ((p == 0) || (p < base)) return FALSE; + } + + return FALSE; + +found: + + switch (type) { + case BINN_MAP: + if (pid) *pid = id; + break; + case BINN_OBJECT: + if (pkey) { + memcpy(pkey, key, len); + pkey[len] = 0; + } + break; + } + + return GetValue(p, value); + +} + +/***************************************************************************/ + +BOOL APIENTRY binn_map_get_pair(void *ptr, int pos, int *pid, binn *value) { + + return binn_read_pair(BINN_MAP, ptr, pos, pid, NULL, value); + +} + +/***************************************************************************/ + +BOOL APIENTRY binn_object_get_pair(void *ptr, int pos, char *pkey, binn *value) { + + return binn_read_pair(BINN_OBJECT, ptr, pos, NULL, pkey, value); + +} + +/***************************************************************************/ + +binn * APIENTRY binn_map_pair(void *map, int pos, int *pid) { + binn *value; + + value = (binn *) binn_malloc(sizeof(binn)); + + if (binn_read_pair(BINN_MAP, map, pos, pid, NULL, value) == FALSE) { + free_fn(value); + return NULL; + } + + value->allocated = TRUE; + return value; + +} + +/***************************************************************************/ + +binn * APIENTRY binn_object_pair(void *obj, int pos, char *pkey) { + binn *value; + + value = (binn *) binn_malloc(sizeof(binn)); + + if (binn_read_pair(BINN_OBJECT, obj, pos, NULL, pkey, value) == FALSE) { + free_fn(value); + return NULL; + } + + value->allocated = TRUE; + return value; + +} + +/***************************************************************************/ +/***************************************************************************/ + +void * APIENTRY binn_map_read_pair(void *ptr, int pos, int *pid, int *ptype, int *psize) { + binn value; + + if (binn_map_get_pair(ptr, pos, pid, &value) == FALSE) return NULL; + if (ptype) *ptype = value.type; + if (psize) *psize = value.size; +#if BYTE_ORDER == LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif + +} + +/***************************************************************************/ + +void * APIENTRY binn_object_read_pair(void *ptr, int pos, char *pkey, int *ptype, int *psize) { + binn value; + + if (binn_object_get_pair(ptr, pos, pkey, &value) == FALSE) return NULL; + if (ptype) *ptype = value.type; + if (psize) *psize = value.size; +#if BYTE_ORDER == LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif + +} + +/***************************************************************************/ +/*** SEQUENTIAL READ FUNCTIONS *********************************************/ +/***************************************************************************/ + +BOOL APIENTRY binn_iter_init(binn_iter *iter, void *ptr, int expected_type) { + int type, count, size=0, header_size; + + ptr = binn_ptr(ptr); + if ((ptr == 0) || (iter == 0)) return FALSE; + memset(iter, 0, sizeof(binn_iter)); + + // check the header + if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE; + + if (type != expected_type) return FALSE; + //if (count == 0) return FALSE; -- should not be used + + iter->plimit = (unsigned char *)ptr + size - 1; + iter->pnext = (unsigned char *)ptr + header_size; + iter->count = count; + iter->current = 0; + iter->type = type; + + return TRUE; +} + +/***************************************************************************/ + +BOOL APIENTRY binn_list_next(binn_iter *iter, binn *value) { + unsigned char *pnow; + + if ((iter == 0) || (iter->pnext == 0) || (iter->pnext > iter->plimit) || (iter->current > iter->count) || (iter->type != BINN_LIST)) return FALSE; + + iter->current++; + if (iter->current > iter->count) return FALSE; + + pnow = iter->pnext; + iter->pnext = AdvanceDataPos(pnow, iter->plimit); + if (iter->pnext != 0 && iter->pnext < pnow) return FALSE; + + return GetValue(pnow, value); + +} + +/***************************************************************************/ + +BINN_PRIVATE BOOL binn_read_next_pair(int expected_type, binn_iter *iter, int *pid, char *pkey, binn *value) { + int int32, id; + unsigned char *p, *key; + unsigned short len; + + if ((iter == 0) || (iter->pnext == 0) || (iter->pnext > iter->plimit) || (iter->current > iter->count) || (iter->type != expected_type)) return FALSE; + + iter->current++; + if (iter->current > iter->count) return FALSE; + + p = iter->pnext; + + switch (expected_type) { + case BINN_MAP: + copy_be32((u32*)&int32, (u32*)p); + p += 4; + if (p > iter->plimit) return FALSE; + id = int32; + if (pid) *pid = id; + break; + case BINN_OBJECT: + len = *((unsigned char *)p); p++; + key = p; + p += len; + if (p > iter->plimit) return FALSE; + if (pkey) { + memcpy(pkey, key, len); + pkey[len] = 0; + } + break; + } + + iter->pnext = AdvanceDataPos(p, iter->plimit); + if (iter->pnext != 0 && iter->pnext < p) return FALSE; + + return GetValue(p, value); + +} + +/***************************************************************************/ + +BOOL APIENTRY binn_map_next(binn_iter *iter, int *pid, binn *value) { + + return binn_read_next_pair(BINN_MAP, iter, pid, NULL, value); + +} + +/***************************************************************************/ + +BOOL APIENTRY binn_object_next(binn_iter *iter, char *pkey, binn *value) { + + return binn_read_next_pair(BINN_OBJECT, iter, NULL, pkey, value); + +} + +/***************************************************************************/ +/***************************************************************************/ + +binn * APIENTRY binn_list_next_value(binn_iter *iter) { + binn *value; + + value = (binn *) binn_malloc(sizeof(binn)); + + if (binn_list_next(iter, value) == FALSE) { + free_fn(value); + return NULL; + } + + value->allocated = TRUE; + return value; + +} + +/***************************************************************************/ + +binn * APIENTRY binn_map_next_value(binn_iter *iter, int *pid) { + binn *value; + + value = (binn *) binn_malloc(sizeof(binn)); + + if (binn_map_next(iter, pid, value) == FALSE) { + free_fn(value); + return NULL; + } + + value->allocated = TRUE; + return value; + +} + +/***************************************************************************/ + +binn * APIENTRY binn_object_next_value(binn_iter *iter, char *pkey) { + binn *value; + + value = (binn *) binn_malloc(sizeof(binn)); + + if (binn_object_next(iter, pkey, value) == FALSE) { + free_fn(value); + return NULL; + } + + value->allocated = TRUE; + return value; + +} + +/***************************************************************************/ +/***************************************************************************/ + +void * APIENTRY binn_list_read_next(binn_iter *iter, int *ptype, int *psize) { + binn value; + + if (binn_list_next(iter, &value) == FALSE) return NULL; + if (ptype) *ptype = value.type; + if (psize) *psize = value.size; +#if BYTE_ORDER == LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif + +} + +/***************************************************************************/ + +void * APIENTRY binn_map_read_next(binn_iter *iter, int *pid, int *ptype, int *psize) { + binn value; + + if (binn_map_next(iter, pid, &value) == FALSE) return NULL; + if (ptype) *ptype = value.type; + if (psize) *psize = value.size; +#if BYTE_ORDER == LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif + +} + +/***************************************************************************/ + +void * APIENTRY binn_object_read_next(binn_iter *iter, char *pkey, int *ptype, int *psize) { + binn value; + + if (binn_object_next(iter, pkey, &value) == FALSE) return NULL; + if (ptype) *ptype = value.type; + if (psize) *psize = value.size; +#if BYTE_ORDER == LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif + +} + +/*************************************************************************************/ +/****** EXTENDED INTERFACE ***********************************************************/ +/****** none of the functions above call the functions below *************************/ +/*************************************************************************************/ + +int APIENTRY binn_get_write_storage(int type) { + int storage_type; + + switch (type) { + case BINN_SINGLE_STR: + case BINN_DOUBLE_STR: + return BINN_STORAGE_STRING; + + case BINN_BOOL: + return BINN_STORAGE_NOBYTES; + + default: + binn_get_type_info(type, &storage_type, NULL); + return storage_type; + } + +} + +/*************************************************************************************/ + +int APIENTRY binn_get_read_storage(int type) { + int storage_type; + + switch (type) { +#ifdef BINN_EXTENDED + case BINN_SINGLE_STR: + return BINN_STORAGE_DWORD; + case BINN_DOUBLE_STR: + return BINN_STORAGE_QWORD; +#endif + case BINN_BOOL: + case BINN_TRUE: + case BINN_FALSE: + return BINN_STORAGE_DWORD; + default: + binn_get_type_info(type, &storage_type, NULL); + return storage_type; + } + +} + +/*************************************************************************************/ + +BINN_PRIVATE BOOL GetWriteConvertedData(int *ptype, void **ppvalue, int *psize) { + int type; + float f1; + double d1; + char pstr[128]; + + UNUSED(pstr); + UNUSED(d1); + UNUSED(f1); + + type = *ptype; + + if (*ppvalue == NULL) { + switch (type) { + case BINN_NULL: + case BINN_TRUE: + case BINN_FALSE: + break; + case BINN_STRING: + case BINN_BLOB: + if (*psize == 0) break; + default: + return FALSE; + } + } + + switch (type) { +#ifdef BINN_EXTENDED + case BINN_SINGLE: + f1 = **(float**)ppvalue; + d1 = f1; // convert from float (32bits) to double (64bits) + type = BINN_SINGLE_STR; + goto conv_double; + case BINN_DOUBLE: + d1 = **(double**)ppvalue; + type = BINN_DOUBLE_STR; +conv_double: + // the '%.17e' is more precise than the '%g' + snprintf(pstr, 127, "%.17e", d1); + *ppvalue = pstr; + *ptype = type; + break; +#endif + case BINN_DECIMAL: + case BINN_CURRENCYSTR: + /* + if (binn_malloc_extptr(128) == NULL) return FALSE; + snprintf(sptr, 127, "%E", **ppvalue); + *ppvalue = sptr; + */ + return TRUE; //! temporary + break; + + case BINN_DATE: + case BINN_DATETIME: + case BINN_TIME: + return TRUE; //! temporary + break; + + case BINN_BOOL: + if (**((BOOL**)ppvalue) == FALSE) { + type = BINN_FALSE; + } else { + type = BINN_TRUE; + } + *ptype = type; + break; + + } + + return TRUE; + +} + +/*************************************************************************************/ + +BINN_PRIVATE int type_family(int type) { + + switch (type) { + case BINN_LIST: + case BINN_MAP: + case BINN_OBJECT: + return BINN_FAMILY_BINN; + + case BINN_INT8: + case BINN_INT16: + case BINN_INT32: + case BINN_INT64: + case BINN_UINT8: + case BINN_UINT16: + case BINN_UINT32: + case BINN_UINT64: + return BINN_FAMILY_INT; + + case BINN_FLOAT32: + case BINN_FLOAT64: + //case BINN_SINGLE: + case BINN_SINGLE_STR: + //case BINN_DOUBLE: + case BINN_DOUBLE_STR: + return BINN_FAMILY_FLOAT; + + case BINN_STRING: + case BINN_HTML: + case BINN_CSS: + case BINN_XML: + case BINN_JSON: + case BINN_JAVASCRIPT: + return BINN_FAMILY_STRING; + + case BINN_BLOB: + case BINN_JPEG: + case BINN_GIF: + case BINN_PNG: + case BINN_BMP: + return BINN_FAMILY_BLOB; + + case BINN_DECIMAL: + case BINN_CURRENCY: + case BINN_DATE: + case BINN_TIME: + case BINN_DATETIME: + return BINN_FAMILY_STRING; + + case BINN_BOOL: + return BINN_FAMILY_BOOL; + + case BINN_NULL: + return BINN_FAMILY_NULL; + + default: + // if it wasn't found + return BINN_FAMILY_NONE; + } + +} + +/*************************************************************************************/ + +BINN_PRIVATE int int_type(int type) { + + switch (type) { + case BINN_INT8: + case BINN_INT16: + case BINN_INT32: + case BINN_INT64: + return BINN_SIGNED_INT; + + case BINN_UINT8: + case BINN_UINT16: + case BINN_UINT32: + case BINN_UINT64: + return BINN_UNSIGNED_INT; + + default: + return 0; + } + +} + +/*************************************************************************************/ + +BINN_PRIVATE BOOL copy_raw_value(void *psource, void *pdest, int data_store) { + + switch (data_store) { + case BINN_STORAGE_NOBYTES: + break; + case BINN_STORAGE_BYTE: + *((char *) pdest) = *(char *)psource; + break; + case BINN_STORAGE_WORD: + *((short *) pdest) = *(short *)psource; + break; + case BINN_STORAGE_DWORD: + *((int *) pdest) = *(int *)psource; + break; + case BINN_STORAGE_QWORD: + *((uint64 *) pdest) = *(uint64 *)psource; + break; + case BINN_STORAGE_BLOB: + case BINN_STORAGE_STRING: + case BINN_STORAGE_CONTAINER: + *((char **) pdest) = (char *)psource; + break; + default: + return FALSE; + } + + return TRUE; + +} + +/*************************************************************************************/ + +BINN_PRIVATE BOOL copy_int_value(void *psource, void *pdest, int source_type, int dest_type) { + uint64 vuint64; int64 vint64; + + switch (source_type) { + case BINN_INT8: + vint64 = *(signed char *)psource; + break; + case BINN_INT16: + vint64 = *(short *)psource; + break; + case BINN_INT32: + vint64 = *(int *)psource; + break; + case BINN_INT64: + vint64 = *(int64 *)psource; + break; + + case BINN_UINT8: + vuint64 = *(unsigned char *)psource; + break; + case BINN_UINT16: + vuint64 = *(unsigned short *)psource; + break; + case BINN_UINT32: + vuint64 = *(unsigned int *)psource; + break; + case BINN_UINT64: + vuint64 = *(uint64 *)psource; + break; + + default: + return FALSE; + } + + + // copy from int64 to uint64, if possible + + if ((int_type(source_type) == BINN_UNSIGNED_INT) && (int_type(dest_type) == BINN_SIGNED_INT)) { + if (vuint64 > INT64_MAX) return FALSE; + vint64 = vuint64; + } else if ((int_type(source_type) == BINN_SIGNED_INT) && (int_type(dest_type) == BINN_UNSIGNED_INT)) { + if (vint64 < 0) return FALSE; + vuint64 = vint64; + } + + + switch (dest_type) { + case BINN_INT8: + if ((vint64 < INT8_MIN) || (vint64 > INT8_MAX)) return FALSE; + *(signed char *)pdest = (signed char) vint64; + break; + case BINN_INT16: + if ((vint64 < INT16_MIN) || (vint64 > INT16_MAX)) return FALSE; + *(short *)pdest = (short) vint64; + break; + case BINN_INT32: + if ((vint64 < INT32_MIN) || (vint64 > INT32_MAX)) return FALSE; + *(int *)pdest = (int) vint64; + break; + case BINN_INT64: + *(int64 *)pdest = vint64; + break; + + case BINN_UINT8: + if (vuint64 > UINT8_MAX) return FALSE; + *(unsigned char *)pdest = (unsigned char) vuint64; + break; + case BINN_UINT16: + if (vuint64 > UINT16_MAX) return FALSE; + *(unsigned short *)pdest = (unsigned short) vuint64; + break; + case BINN_UINT32: + if (vuint64 > UINT32_MAX) return FALSE; + *(unsigned int *)pdest = (unsigned int) vuint64; + break; + case BINN_UINT64: + *(uint64 *)pdest = vuint64; + break; + + default: + return FALSE; + } + + return TRUE; + +} + +/*************************************************************************************/ + +BINN_PRIVATE BOOL copy_float_value(void *psource, void *pdest, int source_type, int dest_type) { + + switch (source_type) { + case BINN_FLOAT32: + *(double *)pdest = *(float *)psource; + break; + case BINN_FLOAT64: + *(float *)pdest = (float) *(double *)psource; + break; + default: + return FALSE; + } + + return TRUE; + +} + +/*************************************************************************************/ + +BINN_PRIVATE void zero_value(void *pvalue, int type) { + //int size=0; + + switch (binn_get_read_storage(type)) { + case BINN_STORAGE_NOBYTES: + break; + case BINN_STORAGE_BYTE: + *((char *) pvalue) = 0; + //size=1; + break; + case BINN_STORAGE_WORD: + *((short *) pvalue) = 0; + //size=2; + break; + case BINN_STORAGE_DWORD: + *((int *) pvalue) = 0; + //size=4; + break; + case BINN_STORAGE_QWORD: + *((uint64 *) pvalue) = 0; + //size=8; + break; + case BINN_STORAGE_BLOB: + case BINN_STORAGE_STRING: + case BINN_STORAGE_CONTAINER: + *(char **)pvalue = NULL; + break; + } + + //if (size>0) memset(pvalue, 0, size); + +} + +/*************************************************************************************/ + +BINN_PRIVATE BOOL copy_value(void *psource, void *pdest, int source_type, int dest_type, int data_store) { + + if (type_family(source_type) != type_family(dest_type)) return FALSE; + + if ((type_family(source_type) == BINN_FAMILY_INT) && (source_type != dest_type)) { + return copy_int_value(psource, pdest, source_type, dest_type); + } else if ((type_family(source_type) == BINN_FAMILY_FLOAT) && (source_type != dest_type)) { + return copy_float_value(psource, pdest, source_type, dest_type); + } else { + return copy_raw_value(psource, pdest, data_store); + } + +} + +/*************************************************************************************/ +/*** WRITE FUNCTIONS *****************************************************************/ +/*************************************************************************************/ + +BOOL APIENTRY binn_list_add(binn *list, int type, void *pvalue, int size) { + + if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) return FALSE; + + return binn_list_add_raw(list, type, pvalue, size); + +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_map_set(binn *map, int id, int type, void *pvalue, int size) { + + if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) return FALSE; + + return binn_map_set_raw(map, id, type, pvalue, size); + +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_object_set(binn *obj, char *key, int type, void *pvalue, int size) { + + if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) return FALSE; + + return binn_object_set_raw(obj, key, type, pvalue, size); + +} + +/*************************************************************************************/ + +// this function is used by the wrappers +BOOL APIENTRY binn_add_value(binn *item, int binn_type, int id, char *name, int type, void *pvalue, int size) { + + switch (binn_type) { + case BINN_LIST: + return binn_list_add(item, type, pvalue, size); + case BINN_MAP: + return binn_map_set(item, id, type, pvalue, size); + case BINN_OBJECT: + return binn_object_set(item, name, type, pvalue, size); + default: + return FALSE; + } + +} + +/*************************************************************************************/ +/*************************************************************************************/ + +BOOL APIENTRY binn_list_add_new(binn *list, binn *value) { + BOOL retval; + + retval = binn_list_add_value(list, value); + if (value) free_fn(value); + return retval; + +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_map_set_new(binn *map, int id, binn *value) { + BOOL retval; + + retval = binn_map_set_value(map, id, value); + if (value) free_fn(value); + return retval; + +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_object_set_new(binn *obj, char *key, binn *value) { + BOOL retval; + + retval = binn_object_set_value(obj, key, value); + if (value) free_fn(value); + return retval; + +} + +/*************************************************************************************/ +/*** READ FUNCTIONS ******************************************************************/ +/*************************************************************************************/ + +binn * APIENTRY binn_list_value(void *ptr, int pos) { + binn *value; + + value = (binn *) binn_malloc(sizeof(binn)); + + if (binn_list_get_value(ptr, pos, value) == FALSE) { + free_fn(value); + return NULL; + } + + value->allocated = TRUE; + return value; + +} + +/*************************************************************************************/ + +binn * APIENTRY binn_map_value(void *ptr, int id) { + binn *value; + + value = (binn *) binn_malloc(sizeof(binn)); + + if (binn_map_get_value(ptr, id, value) == FALSE) { + free_fn(value); + return NULL; + } + + value->allocated = TRUE; + return value; + +} + +/*************************************************************************************/ + +binn * APIENTRY binn_object_value(void *ptr, char *key) { + binn *value; + + value = (binn *) binn_malloc(sizeof(binn)); + + if (binn_object_get_value(ptr, key, value) == FALSE) { + free_fn(value); + return NULL; + } + + value->allocated = TRUE; + return value; + +} + +/***************************************************************************/ +/***************************************************************************/ + +void * APIENTRY binn_list_read(void *list, int pos, int *ptype, int *psize) { + binn value; + + if (binn_list_get_value(list, pos, &value) == FALSE) return NULL; + if (ptype) *ptype = value.type; + if (psize) *psize = value.size; +#if BYTE_ORDER == LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif + +} + +/***************************************************************************/ + +void * APIENTRY binn_map_read(void *map, int id, int *ptype, int *psize) { + binn value; + + if (binn_map_get_value(map, id, &value) == FALSE) return NULL; + if (ptype) *ptype = value.type; + if (psize) *psize = value.size; +#if BYTE_ORDER == LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif + +} + +/***************************************************************************/ + +void * APIENTRY binn_object_read(void *obj, char *key, int *ptype, int *psize) { + binn value; + + if (binn_object_get_value(obj, key, &value) == FALSE) return NULL; + if (ptype) *ptype = value.type; + if (psize) *psize = value.size; +#if BYTE_ORDER == LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif + +} + +/***************************************************************************/ +/***************************************************************************/ + +BOOL APIENTRY binn_list_get(void *ptr, int pos, int type, void *pvalue, int *psize) { + binn value; + int storage_type; + + storage_type = binn_get_read_storage(type); + if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) return FALSE; + + zero_value(pvalue, type); + + if (binn_list_get_value(ptr, pos, &value) == FALSE) return FALSE; + + if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) return FALSE; + + if (psize) *psize = value.size; + + return TRUE; + +} + +/***************************************************************************/ + +BOOL APIENTRY binn_map_get(void *ptr, int id, int type, void *pvalue, int *psize) { + binn value; + int storage_type; + + storage_type = binn_get_read_storage(type); + if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) return FALSE; + + zero_value(pvalue, type); + + if (binn_map_get_value(ptr, id, &value) == FALSE) return FALSE; + + if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) return FALSE; + + if (psize) *psize = value.size; + + return TRUE; + +} + +/***************************************************************************/ + +// if (binn_object_get(obj, "multiplier", BINN_INT32, &multiplier, NULL) == FALSE) xxx; + +BOOL APIENTRY binn_object_get(void *ptr, char *key, int type, void *pvalue, int *psize) { + binn value; + int storage_type; + + storage_type = binn_get_read_storage(type); + if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) return FALSE; + + zero_value(pvalue, type); + + if (binn_object_get_value(ptr, key, &value) == FALSE) return FALSE; + + if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) return FALSE; + + if (psize) *psize = value.size; + + return TRUE; + +} + +/***************************************************************************/ +/***************************************************************************/ + +// these functions below may not be implemented as inline functions, because +// they use a lot of space, even for the variable. so they will be exported. + +// but what about using as static? +// is there any problem with wrappers? can these wrappers implement these functions using the header? +// if as static, will they be present even on modules that don't use the functions? + +signed char APIENTRY binn_list_int8(void *list, int pos) { + signed char value; + + binn_list_get(list, pos, BINN_INT8, &value, NULL); + + return value; +} + +short APIENTRY binn_list_int16(void *list, int pos) { + short value; + + binn_list_get(list, pos, BINN_INT16, &value, NULL); + + return value; +} + +int APIENTRY binn_list_int32(void *list, int pos) { + int value; + + binn_list_get(list, pos, BINN_INT32, &value, NULL); + + return value; +} + +int64 APIENTRY binn_list_int64(void *list, int pos) { + int64 value; + + binn_list_get(list, pos, BINN_INT64, &value, NULL); + + return value; +} + +unsigned char APIENTRY binn_list_uint8(void *list, int pos) { + unsigned char value; + + binn_list_get(list, pos, BINN_UINT8, &value, NULL); + + return value; +} + +unsigned short APIENTRY binn_list_uint16(void *list, int pos) { + unsigned short value; + + binn_list_get(list, pos, BINN_UINT16, &value, NULL); + + return value; +} + +unsigned int APIENTRY binn_list_uint32(void *list, int pos) { + unsigned int value; + + binn_list_get(list, pos, BINN_UINT32, &value, NULL); + + return value; +} + +uint64 APIENTRY binn_list_uint64(void *list, int pos) { + uint64 value; + + binn_list_get(list, pos, BINN_UINT64, &value, NULL); + + return value; +} + +float APIENTRY binn_list_float(void *list, int pos) { + float value; + + binn_list_get(list, pos, BINN_FLOAT32, &value, NULL); + + return value; +} + +double APIENTRY binn_list_double(void *list, int pos) { + double value; + + binn_list_get(list, pos, BINN_FLOAT64, &value, NULL); + + return value; +} + +BOOL APIENTRY binn_list_bool(void *list, int pos) { + BOOL value; + + binn_list_get(list, pos, BINN_BOOL, &value, NULL); + + return value; +} + +BOOL APIENTRY binn_list_null(void *list, int pos) { + + return binn_list_get(list, pos, BINN_NULL, NULL, NULL); + +} + +char * APIENTRY binn_list_str(void *list, int pos) { + char *value; + + binn_list_get(list, pos, BINN_STRING, &value, NULL); + + return value; +} + +void * APIENTRY binn_list_blob(void *list, int pos, int *psize) { + void *value; + + binn_list_get(list, pos, BINN_BLOB, &value, psize); + + return value; +} + +void * APIENTRY binn_list_list(void *list, int pos) { + void *value; + + binn_list_get(list, pos, BINN_LIST, &value, NULL); + + return value; +} + +void * APIENTRY binn_list_map(void *list, int pos) { + void *value; + + binn_list_get(list, pos, BINN_MAP, &value, NULL); + + return value; +} + +void * APIENTRY binn_list_object(void *list, int pos) { + void *value; + + binn_list_get(list, pos, BINN_OBJECT, &value, NULL); + + return value; +} + +/***************************************************************************/ + +signed char APIENTRY binn_map_int8(void *map, int id) { + signed char value; + + binn_map_get(map, id, BINN_INT8, &value, NULL); + + return value; +} + +short APIENTRY binn_map_int16(void *map, int id) { + short value; + + binn_map_get(map, id, BINN_INT16, &value, NULL); + + return value; +} + +int APIENTRY binn_map_int32(void *map, int id) { + int value; + + binn_map_get(map, id, BINN_INT32, &value, NULL); + + return value; +} + +int64 APIENTRY binn_map_int64(void *map, int id) { + int64 value; + + binn_map_get(map, id, BINN_INT64, &value, NULL); + + return value; +} + +unsigned char APIENTRY binn_map_uint8(void *map, int id) { + unsigned char value; + + binn_map_get(map, id, BINN_UINT8, &value, NULL); + + return value; +} + +unsigned short APIENTRY binn_map_uint16(void *map, int id) { + unsigned short value; + + binn_map_get(map, id, BINN_UINT16, &value, NULL); + + return value; +} + +unsigned int APIENTRY binn_map_uint32(void *map, int id) { + unsigned int value; + + binn_map_get(map, id, BINN_UINT32, &value, NULL); + + return value; +} + +uint64 APIENTRY binn_map_uint64(void *map, int id) { + uint64 value; + + binn_map_get(map, id, BINN_UINT64, &value, NULL); + + return value; +} + +float APIENTRY binn_map_float(void *map, int id) { + float value; + + binn_map_get(map, id, BINN_FLOAT32, &value, NULL); + + return value; +} + +double APIENTRY binn_map_double(void *map, int id) { + double value; + + binn_map_get(map, id, BINN_FLOAT64, &value, NULL); + + return value; +} + +BOOL APIENTRY binn_map_bool(void *map, int id) { + BOOL value; + + binn_map_get(map, id, BINN_BOOL, &value, NULL); + + return value; +} + +BOOL APIENTRY binn_map_null(void *map, int id) { + + return binn_map_get(map, id, BINN_NULL, NULL, NULL); + +} + +char * APIENTRY binn_map_str(void *map, int id) { + char *value; + + binn_map_get(map, id, BINN_STRING, &value, NULL); + + return value; +} + +void * APIENTRY binn_map_blob(void *map, int id, int *psize) { + void *value; + + binn_map_get(map, id, BINN_BLOB, &value, psize); + + return value; +} + +void * APIENTRY binn_map_list(void *map, int id) { + void *value; + + binn_map_get(map, id, BINN_LIST, &value, NULL); + + return value; +} + +void * APIENTRY binn_map_map(void *map, int id) { + void *value; + + binn_map_get(map, id, BINN_MAP, &value, NULL); + + return value; +} + +void * APIENTRY binn_map_object(void *map, int id) { + void *value; + + binn_map_get(map, id, BINN_OBJECT, &value, NULL); + + return value; +} + +/***************************************************************************/ + +signed char APIENTRY binn_object_int8(void *obj, char *key) { + signed char value; + + binn_object_get(obj, key, BINN_INT8, &value, NULL); + + return value; +} + +short APIENTRY binn_object_int16(void *obj, char *key) { + short value; + + binn_object_get(obj, key, BINN_INT16, &value, NULL); + + return value; +} + +int APIENTRY binn_object_int32(void *obj, char *key) { + int value; + + binn_object_get(obj, key, BINN_INT32, &value, NULL); + + return value; +} + +int64 APIENTRY binn_object_int64(void *obj, char *key) { + int64 value; + + binn_object_get(obj, key, BINN_INT64, &value, NULL); + + return value; +} + +unsigned char APIENTRY binn_object_uint8(void *obj, char *key) { + unsigned char value; + + binn_object_get(obj, key, BINN_UINT8, &value, NULL); + + return value; +} + +unsigned short APIENTRY binn_object_uint16(void *obj, char *key) { + unsigned short value; + + binn_object_get(obj, key, BINN_UINT16, &value, NULL); + + return value; +} + +unsigned int APIENTRY binn_object_uint32(void *obj, char *key) { + unsigned int value; + + binn_object_get(obj, key, BINN_UINT32, &value, NULL); + + return value; +} + +uint64 APIENTRY binn_object_uint64(void *obj, char *key) { + uint64 value; + + binn_object_get(obj, key, BINN_UINT64, &value, NULL); + + return value; +} + +float APIENTRY binn_object_float(void *obj, char *key) { + float value; + + binn_object_get(obj, key, BINN_FLOAT32, &value, NULL); + + return value; +} + +double APIENTRY binn_object_double(void *obj, char *key) { + double value; + + binn_object_get(obj, key, BINN_FLOAT64, &value, NULL); + + return value; +} + +BOOL APIENTRY binn_object_bool(void *obj, char *key) { + BOOL value; + + binn_object_get(obj, key, BINN_BOOL, &value, NULL); + + return value; +} + +BOOL APIENTRY binn_object_null(void *obj, char *key) { + + return binn_object_get(obj, key, BINN_NULL, NULL, NULL); + +} + +char * APIENTRY binn_object_str(void *obj, char *key) { + char *value; + + binn_object_get(obj, key, BINN_STRING, &value, NULL); + + return value; +} + +void * APIENTRY binn_object_blob(void *obj, char *key, int *psize) { + void *value; + + binn_object_get(obj, key, BINN_BLOB, &value, psize); + + return value; +} + +void * APIENTRY binn_object_list(void *obj, char *key) { + void *value; + + binn_object_get(obj, key, BINN_LIST, &value, NULL); + + return value; +} + +void * APIENTRY binn_object_map(void *obj, char *key) { + void *value; + + binn_object_get(obj, key, BINN_MAP, &value, NULL); + + return value; +} + +void * APIENTRY binn_object_object(void *obj, char *key) { + void *value; + + binn_object_get(obj, key, BINN_OBJECT, &value, NULL); + + return value; +} + +/*************************************************************************************/ +/*************************************************************************************/ + +BINN_PRIVATE binn * binn_alloc_item() { + binn *item; + item = (binn *) binn_malloc(sizeof(binn)); + if (item) { + memset(item, 0, sizeof(binn)); + item->header = BINN_MAGIC; + item->allocated = TRUE; + //item->writable = FALSE; -- already zeroed + } + return item; +} + +/*************************************************************************************/ + +binn * APIENTRY binn_value(int type, void *pvalue, int size, binn_mem_free freefn) { + int storage_type; + binn *item = binn_alloc_item(); + if (item) { + item->type = type; + binn_get_type_info(type, &storage_type, NULL); + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + break; + case BINN_STORAGE_STRING: + if (size == 0) size = strlen((char*)pvalue) + 1; + case BINN_STORAGE_BLOB: + case BINN_STORAGE_CONTAINER: + if (freefn == BINN_TRANSIENT) { + item->ptr = binn_memdup(pvalue, size); + if (item->ptr == NULL) { + free_fn(item); + return NULL; + } + item->freefn = free_fn; + if (storage_type == BINN_STORAGE_STRING) size--; + } else { + item->ptr = pvalue; + item->freefn = freefn; + } + item->size = size; + break; + default: + item->ptr = &item->vint32; + copy_raw_value(pvalue, item->ptr, storage_type); + } + } + return item; +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_set_string(binn *item, char *str, binn_mem_free pfree) { + + if (item == NULL || str == NULL) return FALSE; + + if (pfree == BINN_TRANSIENT) { + item->ptr = binn_memdup(str, strlen(str) + 1); + if (item->ptr == NULL) return FALSE; + item->freefn = free_fn; + } else { + item->ptr = str; + item->freefn = pfree; + } + + item->type = BINN_STRING; + return TRUE; + +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_set_blob(binn *item, void *ptr, int size, binn_mem_free pfree) { + + if (item == NULL || ptr == NULL) return FALSE; + + if (pfree == BINN_TRANSIENT) { + item->ptr = binn_memdup(ptr, size); + if (item->ptr == NULL) return FALSE; + item->freefn = free_fn; + } else { + item->ptr = ptr; + item->freefn = pfree; + } + + item->type = BINN_BLOB; + item->size = size; + return TRUE; + +} + +/*************************************************************************************/ +/*** READ CONVERTED VALUE ************************************************************/ +/*************************************************************************************/ + +#ifdef _MSC_VER +#define atoi64 _atoi64 +#else +int64 atoi64(char *str) { + int64 retval; + int is_negative=0; + + if (*str == '-') { + is_negative = 1; + str++; + } + retval = 0; + for (; *str; str++) { + retval = 10 * retval + (*str - '0'); + } + if (is_negative) retval *= -1; + return retval; +} +#endif + +/*****************************************************************************/ + +BINN_PRIVATE BOOL is_integer(char *p) { + BOOL retval; + + if (p == NULL) return FALSE; + if (*p == '-') p++; + if (*p == 0) return FALSE; + + retval = TRUE; + + for (; *p; p++) { + if ( (*p < '0') || (*p > '9') ) { + retval = FALSE; + } + } + + return retval; +} + +/*****************************************************************************/ + +BINN_PRIVATE BOOL is_float(char *p) { + BOOL retval, number_found=FALSE; + + if (p == NULL) return FALSE; + if (*p == '-') p++; + if (*p == 0) return FALSE; + + retval = TRUE; + + for (; *p; p++) { + if ((*p == '.') || (*p == ',')) { + if (!number_found) retval = FALSE; + } else if ( (*p >= '0') && (*p <= '9') ) { + number_found = TRUE; + } else { + return FALSE; + } + } + + return retval; +} + +/*************************************************************************************/ + +BINN_PRIVATE BOOL is_bool_str(char *str, BOOL *pbool) { + int64 vint; + double vdouble; + + if (str == NULL || pbool == NULL) return FALSE; + + if (stricmp(str, "true") == 0) goto loc_true; + if (stricmp(str, "yes") == 0) goto loc_true; + if (stricmp(str, "on") == 0) goto loc_true; + //if (stricmp(str, "1") == 0) goto loc_true; + + if (stricmp(str, "false") == 0) goto loc_false; + if (stricmp(str, "no") == 0) goto loc_false; + if (stricmp(str, "off") == 0) goto loc_false; + //if (stricmp(str, "0") == 0) goto loc_false; + + if (is_integer(str)) { + vint = atoi64(str); + *pbool = (vint != 0) ? TRUE : FALSE; + return TRUE; + } else if (is_float(str)) { + vdouble = atof(str); + *pbool = (vdouble != 0) ? TRUE : FALSE; + return TRUE; + } + + return FALSE; + +loc_true: + *pbool = TRUE; + return TRUE; + +loc_false: + *pbool = FALSE; + return TRUE; + +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_get_int32(binn *value, int *pint) { + + if (value == NULL || pint == NULL) return FALSE; + + if (type_family(value->type) == BINN_FAMILY_INT) { + return copy_int_value(value->ptr, pint, value->type, BINN_INT32); + } + + switch (value->type) { + case BINN_FLOAT: + if ((value->vfloat < INT32_MIN) || (value->vfloat > INT32_MAX)) return FALSE; + *pint = round(value->vfloat); + break; + case BINN_DOUBLE: + if ((value->vdouble < INT32_MIN) || (value->vdouble > INT32_MAX)) return FALSE; + *pint = round(value->vdouble); + break; + case BINN_STRING: + if (is_integer((char*)value->ptr)) + *pint = atoi((char*)value->ptr); + else if (is_float((char*)value->ptr)) + *pint = round(atof((char*)value->ptr)); + else + return FALSE; + break; + case BINN_BOOL: + *pint = value->vbool; + break; + default: + return FALSE; + } + + return TRUE; +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_get_int64(binn *value, int64 *pint) { + + if (value == NULL || pint == NULL) return FALSE; + + if (type_family(value->type) == BINN_FAMILY_INT) { + return copy_int_value(value->ptr, pint, value->type, BINN_INT64); + } + + switch (value->type) { + case BINN_FLOAT: + if ((value->vfloat < INT64_MIN) || (value->vfloat > INT64_MAX)) return FALSE; + *pint = round(value->vfloat); + break; + case BINN_DOUBLE: + if ((value->vdouble < INT64_MIN) || (value->vdouble > INT64_MAX)) return FALSE; + *pint = round(value->vdouble); + break; + case BINN_STRING: + if (is_integer((char*)value->ptr)) + *pint = atoi64((char*)value->ptr); + else if (is_float((char*)value->ptr)) + *pint = round(atof((char*)value->ptr)); + else + return FALSE; + break; + case BINN_BOOL: + *pint = value->vbool; + break; + default: + return FALSE; + } + + return TRUE; +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_get_double(binn *value, double *pfloat) { + int64 vint; + + if (value == NULL || pfloat == NULL) return FALSE; + + if (type_family(value->type) == BINN_FAMILY_INT) { + if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) return FALSE; + *pfloat = (double) vint; + return TRUE; + } + + switch (value->type) { + case BINN_FLOAT: + *pfloat = value->vfloat; + break; + case BINN_DOUBLE: + *pfloat = value->vdouble; + break; + case BINN_STRING: + if (is_integer((char*)value->ptr)) + *pfloat = (double) atoi64((char*)value->ptr); + else if (is_float((char*)value->ptr)) + *pfloat = atof((char*)value->ptr); + else + return FALSE; + break; + case BINN_BOOL: + *pfloat = value->vbool; + break; + default: + return FALSE; + } + + return TRUE; +} + +/*************************************************************************************/ + +BOOL APIENTRY binn_get_bool(binn *value, BOOL *pbool) { + int64 vint; + + if (value == NULL || pbool == NULL) return FALSE; + + if (type_family(value->type) == BINN_FAMILY_INT) { + if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) return FALSE; + *pbool = (vint != 0) ? TRUE : FALSE; + return TRUE; + } + + switch (value->type) { + case BINN_BOOL: + *pbool = value->vbool; + break; + case BINN_FLOAT: + *pbool = (value->vfloat != 0) ? TRUE : FALSE; + break; + case BINN_DOUBLE: + *pbool = (value->vdouble != 0) ? TRUE : FALSE; + break; + case BINN_STRING: + return is_bool_str((char*)value->ptr, pbool); + default: + return FALSE; + } + + return TRUE; +} + +/*************************************************************************************/ + +char * APIENTRY binn_get_str(binn *value) { + int64 vint; + char buf[128]; + + if (value == NULL) return NULL; + + if (type_family(value->type) == BINN_FAMILY_INT) { + if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) return NULL; + sprintf(buf, "%" INT64_FORMAT, vint); + goto loc_convert_value; + } + + switch (value->type) { + case BINN_FLOAT: + value->vdouble = value->vfloat; + case BINN_DOUBLE: + sprintf(buf, "%g", value->vdouble); + goto loc_convert_value; + case BINN_STRING: + return (char*) value->ptr; + case BINN_BOOL: + if (value->vbool) + strcpy(buf, "true"); + else + strcpy(buf, "false"); + goto loc_convert_value; + } + + return NULL; + +loc_convert_value: + + //value->vint64 = 0; + value->ptr = strdup(buf); + if (value->ptr == NULL) return NULL; + value->freefn = free; + value->type = BINN_STRING; + return (char*) value->ptr; + +} + +/*************************************************************************************/ +/*** GENERAL FUNCTIONS ***************************************************************/ +/*************************************************************************************/ + +BOOL APIENTRY binn_is_container(binn *item) { + + if (item == NULL) return FALSE; + + switch (item->type) { + case BINN_LIST: + case BINN_MAP: + case BINN_OBJECT: + return TRUE; + default: + return FALSE; + } + +} + +/*************************************************************************************/ diff --git a/src/lib/capnp-c/capn-malloc.cc b/src/lib/capnp-c/capn-malloc.cc new file mode 100644 index 0000000..79f08c8 --- /dev/null +++ b/src/lib/capnp-c/capn-malloc.cc @@ -0,0 +1,424 @@ +/* vim: set sw=8 ts=8 sts=8 noet: */ +/* capn-malloc.c + * + * Copyright (C) 2013 James McKaskill + * Copyright (C) 2014 Steve Dee + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +#include "capnp_c.h" +#include "capnp_priv.h" +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <errno.h> + +#ifdef MULTIPASS_TRACE_MALLOC +#include "lib/mpmalloc.h" +#else +#define mpcalloc calloc +#define mpfree free +#endif + +/* + * 8 byte alignment is required for struct capn_segment. + * This struct check_segment_alignment verifies this at compile time. + * + * Unless capn_segment is defined with 8 byte alignment, check_segment_alignment + * fails to compile in x86 mode (or on another CPU with 32-bit pointers), + * as (sizeof(struct capn_segment)&7) -> (44 & 7) evaluates to 4. + * It compiles in x64 mode (or on another CPU with 64-bit pointers), + * as (sizeof(struct capn_segment)&7) -> (80 & 7) evaluates to 0. + */ +struct check_segment_alignment { + unsigned int foo : (sizeof(struct capn_segment)&7) ? -1 : 1; +}; + +static struct capn_segment *create(void *u, uint32_t id, int sz) { + struct capn_segment *s; + sz += sizeof(*s); +#if 0 + if (sz < 1024) { + sz = 1024; + } else { + sz = (sz + 1023) & ~1023; + } +#else + if (sz < 4096) { + sz = 4096; + } else { + sz = (sz + 4095) & ~4095; + } +#endif + s = (struct capn_segment*) mpcalloc(1, sz); + s->data = (char*) (s+1); + s->cap = sz - sizeof(*s); + s->user = s; + return s; +} + +static struct capn_segment *create_local(void *u, int sz) { + return create(u, 0, sz); +} + +void capn_init_malloc(struct capn *c) { + memset(c, 0, sizeof(*c)); + c->create = &create; + c->create_local = &create_local; +} + +void capn_free(struct capn *c) { + struct capn_segment *s = c->seglist; + while (s != NULL) { + struct capn_segment *n = s->next; + mpfree(s->user); + s = n; + } + capn_reset_copy(c); +} + +void capn_reset_copy(struct capn *c) { + struct capn_segment *s = c->copylist; + while (s != NULL) { + struct capn_segment *n = s->next; + mpfree(s->user); + s = n; + } + c->copy = NULL; + c->copylist = NULL; +} + +#define ZBUF_SZ 4096 + +static int read_fp(void *p, size_t sz, FILE *f, struct capn_stream *z, uint8_t* zbuf, int packed) { + if (f && packed) { + z->next_out = (uint8_t*) p; + z->avail_out = sz; + + while (z->avail_out && capn_inflate(z) == CAPN_NEED_MORE) { + int r; + memmove(zbuf, z->next_in, z->avail_in); + r = fread(zbuf+z->avail_in, 1, ZBUF_SZ - z->avail_in, f); + if (r <= 0) + return -1; + z->avail_in += r; + } + return 0; + + } else if (f && !packed) { + return fread(p, sz, 1, f) != 1; + + } else if (packed) { + z->next_out = (uint8_t*) p; + z->avail_out = sz; + return capn_inflate(z) != 0; + + } else { + if (z->avail_in < sz) + return -1; + memcpy(p, z->next_in, sz); + z->next_in += sz; + z->avail_in -= sz; + return 0; + } +} + +static int init_fp(struct capn *c, FILE *f, struct capn_stream *z, int packed) { + /* + * Initialize 'c' from the contents of 'f', assuming the message has been + * serialized with the standard framing format. From https://capnproto.org/encoding.html: + * + * When transmitting over a stream, the following should be sent. All integers are unsigned and little-endian. + * (4 bytes) The number of segments, minus one (since there is always at least one segment). + * (N * 4 bytes) The size of each segment, in words. + * (0 or 4 bytes) Padding up to the next word boundary. + * The content of each segment, in order. + */ + + struct capn_segment *s = NULL; + uint32_t i, segnum, total = 0; + uint32_t hdr[1024]; + uint8_t zbuf[ZBUF_SZ]; + char *data = NULL; + + capn_init_malloc(c); + + /* Read the first four bytes to know how many headers we have */ + if (read_fp(&segnum, 4, f, z, zbuf, packed)) + goto err; + + segnum = capn_flip32(segnum); + if (segnum > 1023) + goto err; + segnum++; /* The wire encoding was zero-based */ + + /* Read the header list */ + if (read_fp(hdr, 8 * (segnum/2) + 4, f, z, zbuf, packed)) + goto err; + + for (i = 0; i < segnum; i++) { + uint32_t n = capn_flip32(hdr[i]); + if (n > INT_MAX/8 || n > UINT32_MAX/8 || UINT32_MAX - total < n*8) + goto err; + hdr[i] = n*8; + total += hdr[i]; + } + + /* Allocate space for the data and the capn_segment structs */ + s = (struct capn_segment*) mpcalloc(1, total + (sizeof(*s) * segnum)); + if (!s) + goto err; + + /* Now read the data and setup the capn_segment structs */ + data = (char*) (s+segnum); + if (read_fp(data, total, f, z, zbuf, packed)) + goto err; + + for (i = 0; i < segnum; i++) { + s[i].len = s[i].cap = hdr[i]; + s[i].data = data; + data += s[i].len; + capn_append_segment(c, &s[i]); + } + + /* Set the entire region to be freed on the last segment */ + s[segnum-1].user = s; + + return 0; + +err: + memset(c, 0, sizeof(*c)); + mpfree(s); + return -1; +} + +int capn_init_fp(struct capn *c, FILE *f, int packed) { + struct capn_stream z; + memset(&z, 0, sizeof(z)); + return init_fp(c, f, &z, packed); +} + +int capn_init_mem(struct capn *c, const uint8_t *p, size_t sz, int packed) { + struct capn_stream z; + memset(&z, 0, sizeof(z)); + z.next_in = p; + z.avail_in = sz; + return init_fp(c, NULL, &z, packed); +} + +static void header_calc(struct capn *c, uint32_t *headerlen, size_t *headersz) +{ + /* segnum == 1: + * [segnum][segsiz] + * segnum == 2: + * [segnum][segsiz][segsiz][zeroes] + * segnum == 3: + * [segnum][segsiz][segsiz][segsiz] + * segnum == 4: + * [segnum][segsiz][segsiz][segsiz][segsiz][zeroes] + */ + *headerlen = ((2 + c->segnum) / 2) * 2; + *headersz = 4 * *headerlen; +} + +static int header_render(struct capn *c, struct capn_segment *seg, uint32_t *header, uint32_t headerlen, size_t *datasz) +{ + size_t i; + + header[0] = capn_flip32(c->segnum - 1); + header[headerlen-1] = 0; /* Zero out the spare position in the header sizes */ + for (i = 0; i < c->segnum; i++, seg = seg->next) { + if (0 == seg) + return -1; + *datasz += seg->len; + header[1 + i] = capn_flip32(seg->len / 8); + } + if (0 != seg) + return -1; + + return 0; +} + +static int capn_write_mem_packed(struct capn *c, uint8_t *p, size_t sz) +{ + struct capn_segment *seg; + struct capn_ptr root; + uint32_t headerlen; + size_t headersz, datasz = 0; + uint32_t *header; + struct capn_stream z; + int ret; + + root = capn_root(c); + header_calc(c, &headerlen, &headersz); + header = (uint32_t*) (p + headersz + 2); /* must reserve two bytes for worst case expansion */ + + if (sz < headersz*2 + 2) /* We must have space for temporary writing of header to deflate */ + return -1; + + ret = header_render(c, root.seg, header, headerlen, &datasz); + if (ret != 0) + return -1; + + memset(&z, 0, sizeof(z)); + z.next_in = (uint8_t *)header; + z.avail_in = headersz; + z.next_out = p; + z.avail_out = sz; + + // pack the headers + ret = capn_deflate(&z); + if (ret != 0 || z.avail_in != 0) + return -1; + + for (seg = root.seg; seg; seg = seg->next) { + z.next_in = (uint8_t *)seg->data; + z.avail_in = seg->len; + ret = capn_deflate(&z); + if (ret != 0 || z.avail_in != 0) + return -1; + } + + return sz - z.avail_out; +} + +int +capn_write_mem(struct capn *c, uint8_t *p, size_t sz, int packed) +{ + struct capn_segment *seg; + struct capn_ptr root; + uint32_t headerlen; + size_t headersz, datasz = 0; + uint32_t *header; + int ret; + + if (c->segnum == 0) + return -1; + + if (packed) + return capn_write_mem_packed(c, p, sz); + + root = capn_root(c); + header_calc(c, &headerlen, &headersz); + header = (uint32_t*) p; + + if (sz < headersz) + return -1; + + ret = header_render(c, root.seg, header, headerlen, &datasz); + if (ret != 0) + return -1; + + if (sz < headersz + datasz) + return -1; + + p += headersz; + + for (seg = root.seg; seg; seg = seg->next) { + memcpy(p, seg->data, seg->len); + p += seg->len; + } + + return headersz+datasz; +} + +static int _write_fd(ssize_t (*write_fd)(int fd, const void *p, size_t count), int fd, void *p, size_t count) +{ + ssize_t ret; + size_t sent = 0; + + while (sent < count) { + ret = write_fd(fd, ((uint8_t*)p)+sent, count-sent); + if (ret < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + else + return -1; + } + sent += ret; + } + + return 0; +} + +int capn_write_fd(struct capn *c, ssize_t (*write_fd)(int fd, const void *p, size_t count), int fd, int packed) +{ + unsigned char buf[4096]; + struct capn_segment *seg; + struct capn_ptr root; + uint32_t headerlen; + size_t headersz, datasz = 0; + int ret; + struct capn_stream z; + unsigned char *p; + + if (c->segnum == 0) + return -1; + + root = capn_root(c); + header_calc(c, &headerlen, &headersz); + + if (sizeof(buf) < headersz) + return -1; + + ret = header_render(c, root.seg, (uint32_t*)buf, headerlen, &datasz); + if (ret != 0) + return -1; + + if (packed) { + const int headerrem = sizeof(buf) - headersz; + const int maxpack = headersz + 2; + if (headerrem < maxpack) + return -1; + + memset(&z, 0, sizeof(z)); + z.next_in = buf; + z.avail_in = headersz; + z.next_out = buf + headersz; + z.avail_out = headerrem; + ret = capn_deflate(&z); + if (ret != 0) + return -1; + + p = buf + headersz; + headersz = headerrem - z.avail_out; + } else { + p = buf; + } + + ret = _write_fd(write_fd, fd, p, headersz); + if (ret < 0) + return -1; + + datasz = headersz; + for (seg = root.seg; seg; seg = seg->next) { + size_t bufsz; + if (packed) { + memset(&z, 0, sizeof(z)); + z.next_in = (uint8_t*)seg->data; + z.avail_in = seg->len; + z.next_out = buf; + z.avail_out = sizeof(buf); + ret = capn_deflate(&z); + if (ret != 0) + return -1; + p = buf; + bufsz = sizeof(buf) - z.avail_out; + } else { + p = (uint8_t*)seg->data; + bufsz = seg->len; + } + ret = _write_fd(write_fd, fd, p, bufsz); + if (ret < 0) + return -1; + datasz += bufsz; + } + + return datasz; +} diff --git a/src/lib/capnp-c/capn-stream.cc b/src/lib/capnp-c/capn-stream.cc new file mode 100644 index 0000000..135d1b2 --- /dev/null +++ b/src/lib/capnp-c/capn-stream.cc @@ -0,0 +1,217 @@ +/* vim: set sw=8 ts=8 sts=8 noet: */ +/* capn-stream.c + * + * Copyright (C) 2013 James McKaskill + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#include "capnp_c.h" +#include "capnp_priv.h" +#include <string.h> + +#ifndef min +static unsigned min(unsigned a, unsigned b) { return (a < b) ? a : b; } +#endif + +int capn_deflate(struct capn_stream* s) { + if (s->avail_in % 8) { + return CAPN_MISALIGNED; + } + + while (s->avail_in) { + int i; + size_t sz; + uint8_t hdr = 0; + uint8_t *p; + + if (!s->avail_out) + return CAPN_NEED_MORE; + + if (s->raw > 0) { + sz = min(s->raw, min(s->avail_in, s->avail_out)); + memcpy(s->next_out, s->next_in, sz); + s->next_out += sz; + s->next_in += sz; + s->avail_out -= sz; + s->avail_in -= sz; + s->raw -= sz; + continue; + } + + if (s->avail_in < 8) + return CAPN_NEED_MORE; + + sz = 0; + for (i = 0; i < 8; i++) { + if (s->next_in[i]) { + sz ++; + hdr |= 1 << i; + } + } + + switch (sz) { + case 0: + if (s->avail_out < 2) + return CAPN_NEED_MORE; + + s->next_out[0] = 0; + for (sz = 1; sz < min(s->avail_in/8, 256); sz++) { + if (((uint64_t*) s->next_in)[sz] != 0) { + break; + } + } + + s->next_out[1] = (uint8_t) (sz-1); + s->next_in += sz*8; + s->avail_in -= sz*8; + s->next_out += 2; + s->avail_out -= 2; + continue; + + case 8: + if (s->avail_out < 10) + return CAPN_NEED_MORE; + + s->next_out[0] = 0xFF; + memcpy(s->next_out+1, s->next_in, 8); + s->next_in += 8; + s->avail_in -= 8; + + s->raw = min(s->avail_in, 256*8); + if ((p = (uint8_t*) memchr(s->next_in, 0, s->raw)) != NULL) { + s->raw = (p - s->next_in) & ~7; + } + + s->next_out[9] = (uint8_t) (s->raw/8); + s->next_out += 10; + s->avail_out -= 10; + continue; + + default: + if (s->avail_out < 1U + sz) + return CAPN_NEED_MORE; + + *(s->next_out++) = hdr; + for (i = 0; i < 8; i++) { + if (s->next_in[i]) { + *(s->next_out++) = s->next_in[i]; + } + } + s->avail_out -= sz + 1; + s->next_in += 8; + s->avail_in -= 8; + continue; + } + } + + return 0; +} + +int capn_inflate(struct capn_stream* s) { + while (s->avail_out) { + int i; + size_t sz; + uint8_t hdr; + uint8_t *wr; + + if (s->avail_buf && s->avail_out >= s->avail_buf) { + memcpy(s->next_out, s->inflate_buf, s->avail_buf); + s->next_out += s->avail_buf; + s->avail_out -= s->avail_buf; + s->avail_buf = 0; + if (!s->avail_out) + return 0; + } + if (s->avail_buf && s->avail_out < s->avail_buf) { + memcpy(s->next_out, s->inflate_buf, s->avail_out); + memmove(s->inflate_buf, s->inflate_buf + s->avail_out, + s->avail_buf - s->avail_out); + s->avail_buf -= s->avail_out; + s->avail_out = 0; + return 0; + } + + if (s->zeros > 0) { + sz = min(s->avail_out, s->zeros); + memset(s->next_out, 0, sz); + s->next_out += sz; + s->avail_out -= sz; + s->zeros -= sz; + continue; + } + + if (s->raw > 0) { + if (s->avail_in == 0) + return CAPN_NEED_MORE; + + sz = min(min(s->avail_out, s->raw), s->avail_in); + memcpy(s->next_out, s->next_in, sz); + s->next_in += sz; + s->next_out += sz; + s->avail_in -= sz; + s->avail_out -= sz; + s->raw -= sz; + continue; + } + + if (s->avail_in == 0) + return 0; + else if (s->avail_in < 2) + return CAPN_NEED_MORE; + + switch (s->next_in[0]) { + case 0xFF: + /* 0xFF is followed by 8 bytes raw, followed by + * a byte with length in words to read raw */ + if (s->avail_in < 10) + return CAPN_NEED_MORE; + + memcpy(s->inflate_buf, s->next_in+1, 8); + s->avail_buf = 8; + + s->raw = s->next_in[9] * 8; + s->next_in += 10; + s->avail_in -= 10; + continue; + + case 0x00: + /* 0x00 is followed by a single byte indicating + * the count of consecutive zero value words + * minus 1 */ + s->zeros = (s->next_in[1] + 1) * 8; + s->next_in += 2; + s->avail_in -= 2; + continue; + + default: + hdr = s->next_in[0]; + sz = 0; + for (i = 0; i < 8; i++) { + if (hdr & (1 << i)) + sz++; + } + if (s->avail_in < 1U + sz) + return CAPN_NEED_MORE; + + s->next_in += 1; + + wr = s->inflate_buf; + for (i = 0; i < 8; i++) { + if (hdr & (1 << i)) { + *wr++ = *s->next_in++; + } else { + *wr++ = 0; + } + } + + s->avail_buf = 8; + s->avail_in -= 1 + sz; + continue; + } + } + + return 0; +} + diff --git a/src/lib/capnp-c/capn.cc b/src/lib/capnp-c/capn.cc new file mode 100644 index 0000000..bbd7be3 --- /dev/null +++ b/src/lib/capnp-c/capn.cc @@ -0,0 +1,1117 @@ +/* vim: set sw=8 ts=8 sts=8 noet: */ +/* capn.c + * + * Copyright (C) 2013 James McKaskill + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +#include "capnp_c.h" + +#include <stdlib.h> +#include <string.h> +#ifndef _MSC_VER +#ifndef MULTIPASS_ARCH_arduino_nano +#include <sys/param.h> +#endif +#endif + +#define STRUCT_PTR 0 +#define LIST_PTR 1 +#define FAR_PTR 2 +#define DOUBLE_PTR 6 + +#define VOID_LIST 0 +#define BIT_1_LIST 1 +#define BYTE_1_LIST 2 +#define BYTE_2_LIST 3 +#define BYTE_4_LIST 4 +#define BYTE_8_LIST 5 +#define PTR_LIST 6 +#define COMPOSITE_LIST 7 + +#define U64(val) ((uint64_t) (val)) +#define I64(val) ((int64_t) (val)) +#define U32(val) ((uint32_t) (val)) +#define I32(val) ((int32_t) (val)) +#define U16(val) ((uint16_t) (val)) +#define I16(val) ((int16_t) (val)) + +#ifndef min +static int min(int a, int b) { return (a < b) ? a : b; } +#endif + +#ifdef BYTE_ORDER +#define CAPN_LITTLE (BYTE_ORDER == LITTLE_ENDIAN) +#elif defined(__BYTE_ORDER) +#define CAPN_LITTLE (__BYTE_ORDER == __LITTLE_ENDIAN) +#else +#define CAPN_LITTLE 0 +#endif + +struct capn_tree *capn_tree_insert(struct capn_tree *root, struct capn_tree *n) { + n->red = 1; + n->link[0] = n->link[1] = NULL; + + for (;;) { + /* parent, uncle, grandparent, great grandparent link */ + struct capn_tree *p, *u, *g, **gglink; + int dir; + + /* Case 1: N is root */ + p = n->parent; + if (!p) { + n->red = 0; + root = n; + break; + } + + /* Case 2: p is black */ + if (!p->red) { + break; + } + + g = p->parent; + dir = (p == g->link[1]); + + /* Case 3: P and U are red, switch g to red, but must + * loop as G could be root or have a red parent + * g to G + * / \ / \ + * P U p u + * / / + * N N + */ + u = g->link[!dir]; + if (u != NULL && u->red) { + p->red = 0; + u->red = 0; + g->red = 1; + n = g; + continue; + } + + if (!g->parent) { + gglink = &root; + } else if (g->parent->link[1] == g) { + gglink = &g->parent->link[1]; + } else { + gglink = &g->parent->link[0]; + } + + if (dir != (n == p->link[1])) { + /* Case 4: rotate on P, then on g + * here dir is / + * g to g to n + * / \ / \ / \ + * P u N u P G + * / \ / \ /| / \ + * 1 N P 3 1 2 3 u + * / \ / \ + * 2 3 1 2 + */ + struct capn_tree *two = n->link[dir]; + struct capn_tree *three = n->link[!dir]; + p->link[!dir] = two; + g->link[dir] = three; + n->link[dir] = p; + n->link[!dir] = g; + *gglink = n; + n->parent = g->parent; + p->parent = n; + g->parent = n; + if (two) + two->parent = p; + if (three) + three->parent = g; + n->red = 0; + g->red = 1; + } else { + /* Case 5: rotate on g + * here dir is / + * g to p + * / \ / \ + * P u N G + * / \ /| / \ + * N 3 1 2 3 u + * / \ + * 1 2 + */ + struct capn_tree *three = p->link[!dir]; + g->link[dir] = three; + p->link[!dir] = g; + *gglink = p; + p->parent = g->parent; + g->parent = p; + if (three) + three->parent = g; + g->red = 1; + p->red = 0; + } + + break; + } + + return root; +} + +void capn_append_segment(struct capn *c, struct capn_segment *s) { + s->id = c->segnum++; + s->capn = c; + s->next = NULL; + + if (c->lastseg) { + c->lastseg->next = s; + c->lastseg->hdr.link[1] = &s->hdr; + s->hdr.parent = &c->lastseg->hdr; + } else { + c->seglist = s; + s->hdr.parent = NULL; + } + + c->lastseg = s; + c->segtree = capn_tree_insert(c->segtree, &s->hdr); +} + +static char *new_data(struct capn *c, int sz, struct capn_segment **ps) { + struct capn_segment *s; + + /* find a segment with sufficient data */ + for (s = c->seglist; s != NULL; s = s->next) { + if (s->len + sz <= s->cap) { + goto end; + } + } + + s = c->create ? c->create(c->user, c->segnum, sz) : NULL; + if (!s) { + *ps = NULL; + return NULL; + } + + capn_append_segment(c, s); +end: + *ps = s; + s->len += sz; + return s->data + s->len - sz; +} + +static struct capn_segment *lookup_segment(struct capn* c, struct capn_segment *s, uint32_t id) { + struct capn_tree **x; + struct capn_segment *y = NULL; + + if (s && s->id == id) + return s; + if (!c) + return NULL; + + if (id < c->segnum) { + x = &c->segtree; + while (*x) { + y = (struct capn_segment*) *x; + if (id == y->id) { + return y; + } else if (id < y->id) { + x = &y->hdr.link[0]; + } else { + x = &y->hdr.link[1]; + } + } + } else { + /* Otherwise `x` may be uninitialized */ + return NULL; + } + + s = c->lookup ? c->lookup(c->user, id) : NULL; + if (!s) + return NULL; + + if (id < c->segnum) { + s->id = id; + s->capn = c; + s->next = c->seglist; + c->seglist = s; + s->hdr.parent = &y->hdr; + *x = &s->hdr; + c->segtree = capn_tree_insert(c->segtree, &s->hdr); + } else { + c->segnum = id; + capn_append_segment(c, s); + } + + return s; +} + +static uint64_t lookup_double(struct capn_segment **s, char **d, uint64_t val) { + uint64_t far, tag; + size_t off = (U32(val) >> 3) * 8; + char *p; + + if ((*s = lookup_segment((*s)->capn, *s, U32(val >> 32))) == NULL) { + return 0; + } + + p = (*s)->data + off; + if (off + 16 > (*s)->len) { + return 0; + } + + far = capn_flip64(*(uint64_t*) p); + tag = capn_flip64(*(uint64_t*) (p+8)); + + /* the far tag should not be another double, and the tag + * should be struct/list and have no offset */ + if ((far&7) != FAR_PTR || U32(tag) > LIST_PTR) { + return 0; + } + + if ((*s = lookup_segment((*s)->capn, *s, U32(far >> 32))) == NULL) { + return 0; + } + + /* -8 because far pointers reference from the start of + * the segment, but offsets reference the end of the + * pointer data. Here *d points to where an equivalent + * ptr would be. + */ + *d = (*s)->data - 8; + return U64(U32(far) >> 3 << 2) | tag; +} + +static uint64_t lookup_far(struct capn_segment **s, char **d, uint64_t val) { + size_t off = (U32(val) >> 3) * 8; + + if ((*s = lookup_segment((*s)->capn, *s, U32(val >> 32))) == NULL) { + return 0; + } + + if (off + 8 > (*s)->len) { + return 0; + } + + *d = (*s)->data + off; + return capn_flip64(*(uint64_t*)*d); +} + +static char *struct_ptr(struct capn_segment *s, char *d, int minsz) { + uint64_t val = capn_flip64(*(uint64_t*)d); + uint16_t datasz; + + switch (val&7) { + case FAR_PTR: + val = lookup_far(&s, &d, val); + break; + case DOUBLE_PTR: + val = lookup_double(&s, &d, val); + break; + } + + datasz = U16(val >> 32); + d += (I32(U32(val)) << 1) + 8; + + if (val != 0 && (val&3) != STRUCT_PTR && datasz >= minsz && s->data <= d && d < s->data + s->len) { + return d; + } + + return NULL; +} + +static capn_ptr read_ptr(struct capn_segment *s, char *d) { + capn_ptr ret = {CAPN_NULL}; + uint64_t val; + char *e = 0; + + val = capn_flip64(*(uint64_t*) d); + + switch (val&7) { + case FAR_PTR: + val = lookup_far(&s, &d, val); + ret.has_ptr_tag = (U32(val) >> 2) == 0; + break; + case DOUBLE_PTR: + val = lookup_double(&s, &d, val); + break; + } + + d += (I32(U32(val)) >> 2) * 8 + 8; + + if (d < s->data) { + goto err; + } + + switch (val & 3) { + case STRUCT_PTR: + ret.type = val ? CAPN_STRUCT : CAPN_NULL; + goto struct_common; + + struct_common: + ret.datasz = U32(U16(val >> 32)) * 8; + ret.ptrs = U32(U16(val >> 48)); + e = d + ret.datasz + 8 * ret.ptrs; + break; + + case LIST_PTR: + ret.type = CAPN_LIST; + ret.len = val >> 35; + + switch ((val >> 32) & 7) { + case VOID_LIST: + e = d; + break; + case BIT_1_LIST: + ret.type = CAPN_BIT_LIST; + ret.datasz = (ret.len+7)/8; + e = d + ret.datasz; + break; + case BYTE_1_LIST: + ret.datasz = 1; + e = d + ret.len; + break; + case BYTE_2_LIST: + ret.datasz = 2; + e = d + ret.len * 2; + break; + case BYTE_4_LIST: + ret.datasz = 4; + e = d + ret.len * 4; + break; + case BYTE_8_LIST: + ret.datasz = 8; + e = d + ret.len * 8; + break; + case PTR_LIST: + ret.type = CAPN_PTR_LIST; + e = d + ret.len * 8; + break; + case COMPOSITE_LIST: + if ((size_t)((d+8) - s->data) > s->len) { + goto err; + } + + val = capn_flip64(*(uint64_t*) d); + + d += 8; + e = d + ret.len * 8; + + ret.datasz = U32(U16(val >> 32)) * 8; + ret.ptrs = U32(U16(val >> 48)); + ret.len = U32(val) >> 2; + ret.is_composite_list = 1; + + if ((ret.datasz + 8*ret.ptrs) * ret.len != e - d) { + goto err; + } + break; + } + break; + + default: + goto err; + } + + if ((size_t)(e - s->data) > s->len) + goto err; + + ret.data = d; + ret.seg = s; + return ret; +err: + memset(&ret, 0, sizeof(ret)); + return ret; +} + +void capn_resolve(capn_ptr *p) { + if (p->type == CAPN_FAR_POINTER) { + *p = read_ptr(p->seg, p->data); + } +} + +/* TODO: should this handle CAPN_BIT_LIST? */ +capn_ptr capn_getp(capn_ptr p, int off, int resolve) { + capn_ptr ret = {CAPN_FAR_POINTER}; + ret.seg = p.seg; + + capn_resolve(&p); + + switch (p.type) { + case CAPN_LIST: + /* Return an inner pointer */ + if (off < p.len) { + capn_ptr ret = {CAPN_STRUCT}; + ret.is_list_member = 1; + ret.data = p.data + off * (p.datasz + 8*p.ptrs); + ret.seg = p.seg; + ret.datasz = p.datasz; + ret.ptrs = p.ptrs; + return ret; + } else { + goto err; + } + + case CAPN_STRUCT: + if (off >= p.ptrs) { + goto err; + } + ret.data = p.data + p.datasz + 8*off; + break; + + case CAPN_PTR_LIST: + if (off >= p.len) { + goto err; + } + ret.data = p.data + 8*off; + break; + + default: + goto err; + } + + if (resolve) { + ret = read_ptr(ret.seg, ret.data); + } + + return ret; + +err: + memset(&p, 0, sizeof(p)); + return p; +} + +static void write_ptr_tag(char *d, capn_ptr p, int off) { + uint64_t val = U64(U32(I32(off/8) << 2)); + + switch (p.type) { + case CAPN_STRUCT: + val |= STRUCT_PTR | (U64(p.datasz/8) << 32) | (U64(p.ptrs) << 48); + break; + + case CAPN_LIST: + if (p.is_composite_list) { + val |= LIST_PTR | (U64(COMPOSITE_LIST) << 32) | (U64(p.len * (p.datasz/8 + p.ptrs)) << 35); + } else { + val |= LIST_PTR | (U64(p.len) << 35); + + switch (p.datasz) { + case 8: + val |= (U64(BYTE_8_LIST) << 32); + break; + case 4: + val |= (U64(BYTE_4_LIST) << 32); + break; + case 2: + val |= (U64(BYTE_2_LIST) << 32); + break; + case 1: + val |= (U64(BYTE_1_LIST) << 32); + break; + case 0: + val |= (U64(VOID_LIST) << 32); + break; + } + } + break; + + case CAPN_BIT_LIST: + val |= LIST_PTR | (U64(BIT_1_LIST) << 32) | (U64(p.len) << 35); + break; + + case CAPN_PTR_LIST: + val |= LIST_PTR | (U64(PTR_LIST) << 32) | (U64(p.len) << 35); + break; + + default: + val = 0; + break; + } + + *(uint64_t*) d = capn_flip64(val); +} + +static void write_far_ptr(char *d, struct capn_segment *s, char *tgt) { + *(uint64_t*) d = capn_flip64(FAR_PTR | U64(tgt - s->data) | (U64(s->id) << 32)); +} + +static void write_double_far(char *d, struct capn_segment *s, char *tgt) { + *(uint64_t*) d = capn_flip64(DOUBLE_PTR | U64(tgt - s->data) | (U64(s->id) << 32)); +} + +#define NEED_TO_COPY 1 + +static int write_ptr(struct capn_segment *s, char *d, capn_ptr p) { + /* note p.seg can be NULL if its a ptr to static data */ + char *pdata = p.data - 8*p.is_composite_list; + + if (p.type == CAPN_NULL || (p.type == CAPN_STRUCT && p.datasz == 0 && p.ptrs == 0)) { + write_ptr_tag(d, p, 0); + return 0; + + } else if (!p.seg || p.seg->capn != s->capn || p.is_list_member) { + return NEED_TO_COPY; + + } else if (p.seg == s) { + write_ptr_tag(d, p, pdata - d - 8); + return 0; + + } else if (p.has_ptr_tag) { + /* By lucky chance, the data has a tag in front + * of it. This happens when new_object had to move + * the data to a new segment. */ + write_far_ptr(d, p.seg, pdata-8); + return 0; + + } else if (p.seg->len + 8 <= p.seg->cap) { + /* The target segment has enough room for tag */ + char *t = p.seg->data + p.seg->len; + write_ptr_tag(t, p, pdata - t - 8); + write_far_ptr(d, p.seg, t); + p.seg->len += 8; + return 0; + + } else { + /* have to allocate room for a double far + * pointer */ + char *t; + + if (s->len + 16 <= s->cap) { + /* Try and allocate in the src segment + * first. This should improve lookup on + * read. */ + t = s->data + s->len; + s->len += 16; + } else { + t = new_data(s->capn, 16, &s); + if (!t) return -1; + } + + write_far_ptr(t, p.seg, pdata); + write_ptr_tag(t+8, p, 0); + write_double_far(d, s, t); + return 0; + } +} + +struct copy { + struct capn_tree hdr; + struct capn_ptr to, from; + char *fbegin, *fend; +}; + +static capn_ptr new_clone(struct capn_segment *s, capn_ptr p) { + switch (p.type) { + case CAPN_STRUCT: + return capn_new_struct(s, p.datasz, p.ptrs); + case CAPN_PTR_LIST: + return capn_new_ptr_list(s, p.len); + case CAPN_BIT_LIST: + return capn_new_list1(s, p.len).p; + case CAPN_LIST: + return capn_new_list(s, p.len, p.datasz, p.ptrs); + default: + return p; + } +} + +static int is_ptr_equal(const struct capn_ptr *a, const struct capn_ptr *b) { + return a->data == b->data + && a->type == b->type + && a->len == b->len + && a->datasz == b->datasz + && a->ptrs == b->ptrs; +} + +static int data_size(struct capn_ptr p) { + switch (p.type) { + case CAPN_BIT_LIST: + return p.datasz; + case CAPN_PTR_LIST: + return p.len*8; + case CAPN_STRUCT: + return p.datasz + 8*p.ptrs; + case CAPN_LIST: + return p.len * (p.datasz + 8*p.ptrs) + 8*p.is_composite_list; + default: + return 0; + } +} + +static int copy_ptr(struct capn_segment *seg, char *data, struct capn_ptr *t, struct capn_ptr *f, int *dep) { + struct capn *c = seg->capn; + struct copy *cp = NULL; + struct capn_tree **xcp; + char *fbegin = f->data - 8*f->is_composite_list; + char *fend = fbegin + data_size(*f); + int zero_sized = (fend == fbegin); + + /* We always copy list members as it would otherwise be an + * overlapped pointer (the data is owned by the enclosing list). + * We do not bother with the overlapped lookup for zero sized + * structures/lists as they never overlap. Nor do we add them to + * the copy list as there is no data to be shared by multiple + * pointers. + */ + + xcp = &c->copy; + while (*xcp && !zero_sized) { + cp = (struct copy*) *xcp; + if (fend <= cp->fbegin) { + xcp = &cp->hdr.link[0]; + } else if (cp->fend <= fbegin) { + xcp = &cp->hdr.link[1]; + } else if (is_ptr_equal(f, &cp->from)) { + /* we already have a copy so just point to that */ + return write_ptr(seg, data, cp->to); + } else { + /* pointer to overlapped data */ + return -1; + } + } + + /* no copy found - have to create a new copy */ + *t = new_clone(seg, *f); + + if (write_ptr(seg, data, *t)) + return -1; + + /* add the copy to the copy tree so we can look for overlapping + * source pointers and handle recursive structures */ + if (!zero_sized) { + struct copy *n; + struct capn_segment *cs = c->copylist; + + /* need to allocate a struct copy */ + if (!cs || cs->len + (int)sizeof(*n) > cs->cap) { + cs = c->create_local ? c->create_local(c->user, sizeof(*n)) : NULL; + if (!cs) { + /* can't allocate a copy structure */ + return -1; + } + cs->next = c->copylist; + c->copylist = cs; + } + + n = (struct copy*) (cs->data + cs->len); + cs->len += sizeof(*n); + + n->from = *f; + n->to = *t; + n->fbegin = fbegin; + n->fend = fend; + + *xcp = &n->hdr; + n->hdr.parent = &cp->hdr; + + c->copy = capn_tree_insert(c->copy, &n->hdr); + } + + /* minimize the number of types the main copy routine has to + * deal with to just CAPN_LIST and CAPN_PTR_LIST. ptr list only + * needs t->type, t->len, t->data, t->seg, f->data, f->seg to + * be valid */ + switch (t->type) { + case CAPN_STRUCT: + if (t->datasz) { + memcpy(t->data, f->data, t->datasz); + t->data += t->datasz; + f->data += t->datasz; + } + if (t->ptrs) { + t->type = CAPN_PTR_LIST; + t->len = t->ptrs; + (*dep)++; + } + return 0; + + case CAPN_BIT_LIST: + memcpy(t->data, f->data, t->datasz); + return 0; + + case CAPN_LIST: + if (!t->len) { + /* empty list - nothing to copy */ + } else if (t->ptrs && t->datasz) { + (*dep)++; + } else if (t->datasz) { + memcpy(t->data, f->data, t->len * t->datasz); + } else if (t->ptrs) { + t->type = CAPN_PTR_LIST; + t->len *= t->ptrs; + (*dep)++; + } + return 0; + + case CAPN_PTR_LIST: + if (t->len) { + (*dep)++; + } + return 0; + + default: + return -1; + } +} + +static void copy_list_member(capn_ptr* t, capn_ptr *f, int *dep) { + /* copy struct data */ + int sz = min(t->datasz, f->datasz); + memcpy(t->data, f->data, sz); + memset(t->data + sz, 0, t->datasz - sz); + t->data += t->datasz; + f->data += f->datasz; + + /* reset excess pointers */ + sz = min(t->ptrs, f->ptrs); + memset(t->data + sz, 0, 8*(t->ptrs - sz)); + + /* create a pointer list for the main loop to copy */ + if (t->ptrs) { + t->type = CAPN_PTR_LIST; + t->len = t->ptrs; + (*dep)++; + } +} + +#define MAX_COPY_DEPTH 32 + +/* TODO: handle CAPN_BIT_LIST and setting from an inner bit list member */ +int capn_setp(capn_ptr p, int off, capn_ptr tgt) { + struct capn_ptr to[MAX_COPY_DEPTH], from[MAX_COPY_DEPTH]; + char *data; + int err, dep = 0; + + capn_resolve(&p); + + if (tgt.type == CAPN_FAR_POINTER && tgt.seg->capn == p.seg->capn) { + uint64_t val = capn_flip64(*(uint64_t*) tgt.data); + if ((val & 3) == FAR_PTR) { + *(uint64_t*) p.data = *(uint64_t*) tgt.data; + return 0; + } + } + + capn_resolve(&tgt); + + switch (p.type) { + case CAPN_LIST: + if (off >= p.len || tgt.type != CAPN_STRUCT) + return -1; + + to[0] = p; + to[0].data += off * (p.datasz + 8*p.ptrs); + from[0] = tgt; + copy_list_member(to, from, &dep); + break; + + case CAPN_PTR_LIST: + if (off >= p.len) + return -1; + data = p.data + 8*off; + goto copy_ptr; + + case CAPN_STRUCT: + if (off >= p.ptrs) + return -1; + data = p.data + p.datasz + 8*off; + goto copy_ptr; + + copy_ptr: + err = write_ptr(p.seg, data, tgt); + if (err != NEED_TO_COPY) + return err; + + /* Depth first copy the source whilst using a pointer stack to + * maintain the ptr to set and size left to copy at each level. + * We also maintain a rbtree (capn->copy) of the copies indexed + * by the source data. This way we can detect overlapped + * pointers in the source (and bail) and recursive structures + * (and point to the previous copy). + */ + + from[0] = tgt; + if (copy_ptr(p.seg, data, to, from, &dep)) + return -1; + break; + + default: + return -1; + } + + while (dep) { + struct capn_ptr *tc = &to[dep-1], *tn = &to[dep]; + struct capn_ptr *fc = &from[dep-1], *fn = &from[dep]; + + if (dep+1 == MAX_COPY_DEPTH) { + return -1; + } + + if (!tc->len) { + dep--; + continue; + } + + if (tc->type == CAPN_LIST) { + *fn = capn_getp(*fc, 0, 1); + *tn = capn_getp(*tc, 0, 1); + + copy_list_member(tn, fn, &dep); + + fc->data += fc->datasz + 8*fc->ptrs; + tc->data += tc->datasz + 8*tc->ptrs; + tc->len--; + + } else { /* CAPN_PTR_LIST */ + *fn = read_ptr(fc->seg, fc->data); + + if (fn->type && copy_ptr(tc->seg, tc->data, tn, fn, &dep)) + return -1; + + fc->data += 8; + tc->data += 8; + tc->len--; + } + } + + return 0; +} + +/* TODO: handle CAPN_LIST, CAPN_PTR_LIST for bit lists */ + +int capn_get1(capn_list1 l, int off) { + return l.p.type == CAPN_BIT_LIST + && off < l.p.len + && (l.p.data[off/8] & (1 << (off%8))) != 0; +} + +int capn_set1(capn_list1 l, int off, int val) { + if (l.p.type != CAPN_BIT_LIST || off >= l.p.len) + return -1; + if (val) { + l.p.data[off/8] |= 1 << (off%8); + } else { + l.p.data[off/8] &= ~(1 << (off%8)); + } + return 0; +} + +int capn_getv1(capn_list1 l, int off, uint8_t *data, int sz) { + /* Note we only support aligned reads */ + int bsz; + capn_ptr p; + capn_resolve(&l.p); + p = l.p; + if (p.type != CAPN_BIT_LIST || (off & 7) != 0) + return -1; + + bsz = (sz + 7) / 8; + off /= 8; + + if (off + sz > p.datasz) { + memcpy(data, p.data + off, p.datasz - off); + return p.len - off*8; + } else { + memcpy(data, p.data + off, bsz); + return sz; + } +} + +int capn_setv1(capn_list1 l, int off, const uint8_t *data, int sz) { + /* Note we only support aligned writes */ + int bsz; + capn_ptr p = l.p; + if (p.type != CAPN_BIT_LIST || (off & 7) != 0) + return -1; + + bsz = (sz + 7) / 8; + off /= 8; + + if (off + sz > p.datasz) { + memcpy(p.data + off, data, p.datasz - off); + return p.len - off*8; + } else { + memcpy(p.data + off, data, bsz); + return sz; + } +} + +/* pull out whether we add a tag or not as a define so the unit test can + * test double far pointers by not creating tags */ +#ifndef ADD_TAG +#define ADD_TAG 1 +#endif + +static void new_object(capn_ptr *p, int bytes) { + struct capn_segment *s = p->seg; + + if (!s) { + memset(p, 0, sizeof(*p)); + return; + } + + /* pointer needs to be initialised to get a valid offset on write */ + if (!bytes) { + p->data = s->data + s->len; + return; + } + + /* all allocations are 8 byte aligned */ + bytes = (bytes + 7) & ~7; + + if (s->len + bytes <= s->cap) { + p->data = s->data + s->len; + s->len += bytes; + return; + } + + /* add a tag whenever we switch segments so that write_ptr can + * use it */ + p->data = new_data(s->capn, bytes + ADD_TAG*8, &p->seg); + if (!p->data) { + memset(p, 0, sizeof(*p)); + return; + } + + if (ADD_TAG) { + write_ptr_tag(p->data, *p, 0); + p->data += 8; + p->has_ptr_tag = 1; + } +} + +capn_ptr capn_root(struct capn *c) { + capn_ptr r = {CAPN_PTR_LIST}; + r.seg = lookup_segment(c, NULL, 0); + r.data = r.seg ? r.seg->data : new_data(c, 8, &r.seg); + r.len = 1; + + if (!r.seg || r.seg->cap < 8) { + memset(&r, 0, sizeof(r)); + } else if (r.seg->len < 8) { + r.seg->len = 8; + } + + return r; +} + +capn_ptr capn_new_struct(struct capn_segment *seg, int datasz, int ptrs) { + capn_ptr p = {CAPN_STRUCT}; + p.seg = seg; + p.datasz = (datasz + 7) & ~7; + p.ptrs = ptrs; + new_object(&p, p.datasz + 8*p.ptrs); + return p; +} + +capn_ptr capn_new_list(struct capn_segment *seg, int sz, int datasz, int ptrs) { + capn_ptr p = {CAPN_LIST}; + p.seg = seg; + p.len = sz; + + if (ptrs || datasz > 8) { + p.is_composite_list = 1; + p.datasz = (datasz + 7) & ~7; + p.ptrs = ptrs; + new_object(&p, p.len * (p.datasz + 8*p.ptrs) + 8); + if (p.data) { + uint64_t hdr = STRUCT_PTR | (U64(p.len) << 2) | (U64(p.datasz/8) << 32) | (U64(ptrs) << 48); + *(uint64_t*) p.data = capn_flip64(hdr); + p.data += 8; + } + } else if (datasz > 4) { + p.datasz = 8; + new_object(&p, p.len * 8); + } else if (datasz > 2) { + p.datasz = 4; + new_object(&p, p.len * 4); + } else { + p.datasz = datasz; + new_object(&p, p.len * datasz); + } + + return p; +} + +capn_list1 capn_new_list1(struct capn_segment *seg, int sz) { + capn_list1 l = {{CAPN_BIT_LIST}}; + l.p.seg = seg; + l.p.datasz = (sz+7)/8; + l.p.len = sz; + new_object(&l.p, l.p.datasz); + return l; +} + +capn_ptr capn_new_ptr_list(struct capn_segment *seg, int sz) { + capn_ptr p = {CAPN_PTR_LIST}; + p.seg = seg; + p.len = sz; + p.ptrs = 0; + p.datasz = 0; + new_object(&p, sz*8); + return p; +} + +capn_ptr capn_new_string(struct capn_segment *seg, const char *str, ssize_t sz) { + capn_ptr p = {CAPN_LIST}; + p.seg = seg; + p.len = ((sz >= 0) ? (size_t)sz : strlen(str)) + 1; + p.datasz = 1; + new_object(&p, p.len); + if (p.data) { + memcpy(p.data, str, p.len - 1); + p.data[p.len - 1] = '\0'; + } + return p; +} + +capn_text capn_get_text(capn_ptr p, int off, capn_text def) { + capn_ptr m = capn_getp(p, off, 1); + capn_text ret = def; + if (m.type == CAPN_LIST && m.datasz == 1 && m.len && m.data[m.len - 1] == 0) { + ret.seg = m.seg; + ret.str = m.data; + ret.len = m.len - 1; + } + return ret; +} + +int capn_set_text(capn_ptr p, int off, capn_text tgt) { + capn_ptr m = {CAPN_NULL}; + if (tgt.seg) { + m.type = CAPN_LIST; + m.seg = tgt.seg; + m.data = (char*)tgt.str; + m.len = tgt.len + 1; + m.datasz = 1; + } else if (tgt.str) { + m = capn_new_string(p.seg, tgt.str, tgt.len); + } + return capn_setp(p, off, m); +} + +capn_data capn_get_data(capn_ptr p, int off) { + capn_data ret; + ret.p = capn_getp(p, off, 1); + if (ret.p.type != CAPN_LIST || ret.p.datasz != 1) { + memset(&ret, 0, sizeof(ret)); + } + return ret; +} + +#define SZ 8 +#include "capn-list.inc" +#undef SZ + +#define SZ 16 +#include "capn-list.inc" +#undef SZ + +#define SZ 32 +#include "capn-list.inc" +#undef SZ + +#define SZ 64 +#include "capn-list.inc" +#undef SZ diff --git a/src/lib/mpack/mpack.cc b/src/lib/mpack/mpack.cc new file mode 100644 index 0000000..67e54e8 --- /dev/null +++ b/src/lib/mpack/mpack.cc @@ -0,0 +1,6440 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015-2018 Nicholas Fraser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +/* + * This is the MPack 1.0 amalgamation package. + * + * http://github.com/ludocode/mpack + */ + +#define MPACK_INTERNAL 1 +#define MPACK_EMIT_INLINE_DEFS 1 + +#include "mpack.h" + + +/* mpack/mpack-platform.c.c */ + + +// We define MPACK_EMIT_INLINE_DEFS and include mpack.h to emit +// standalone definitions of all (non-static) inline functions in MPack. + +#define MPACK_INTERNAL 1 +#define MPACK_EMIT_INLINE_DEFS 1 + +/* #include "mpack-platform.h" */ +/* #include "mpack.h" */ + + +#if MPACK_DEBUG && MPACK_STDIO +#include <stdarg.h> +#endif + + + +#if MPACK_DEBUG + +#if MPACK_STDIO +void mpack_assert_fail_format(const char* format, ...) { + char buffer[512]; + va_list args; + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + buffer[sizeof(buffer) - 1] = 0; + mpack_assert_fail_wrapper(buffer); +} + +void mpack_break_hit_format(const char* format, ...) { + char buffer[512]; + va_list args; + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + buffer[sizeof(buffer) - 1] = 0; + mpack_break_hit(buffer); +} +#endif + +#if !MPACK_CUSTOM_ASSERT +void mpack_assert_fail(const char* message) { + MPACK_UNUSED(message); + + #if MPACK_STDIO + fprintf(stderr, "%s\n", message); + #endif +} +#endif + +// We split the assert failure from the wrapper so that a +// custom assert function can return. +void mpack_assert_fail_wrapper(const char* message) { + + #ifdef MPACK_GCOV + // gcov marks even __builtin_unreachable() as an uncovered line. this + // silences it. + (mpack_assert_fail(message), __builtin_unreachable()); + + #else + mpack_assert_fail(message); + + // mpack_assert_fail() is not supposed to return. in case it does, we + // abort. + + #if !MPACK_NO_BUILTINS + #if defined(__GNUC__) || defined(__clang__) + __builtin_trap(); + #elif defined(WIN32) + __debugbreak(); + #endif + #endif + + #if (defined(__GNUC__) || defined(__clang__)) && !MPACK_NO_BUILTINS + __builtin_abort(); + #elif MPACK_STDLIB + abort(); + #endif + + MPACK_UNREACHABLE; + #endif +} + +#if !MPACK_CUSTOM_BREAK + +// If we have a custom assert handler, break wraps it by default. +// This allows users of MPack to only implement mpack_assert_fail() without +// having to worry about the difference between assert and break. +// +// MPACK_CUSTOM_BREAK is available to define a separate break handler +// (which is needed by the unit test suite), but this is not offered in +// mpack-config.h for simplicity. + +#if MPACK_CUSTOM_ASSERT +void mpack_break_hit(const char* message) { + mpack_assert_fail_wrapper(message); +} +#else +void mpack_break_hit(const char* message) { + MPACK_UNUSED(message); + + #if MPACK_STDIO + fprintf(stderr, "%s\n", message); + #endif + + #if defined(__GNUC__) || defined(__clang__) && !MPACK_NO_BUILTINS + __builtin_trap(); + #elif defined(WIN32) && !MPACK_NO_BUILTINS + __debugbreak(); + #elif MPACK_STDLIB + abort(); + #endif +} +#endif + +#endif + +#endif + + + +// The below are adapted from the C wikibook: +// https://en.wikibooks.org/wiki/C_Programming/Strings + +#ifndef mpack_memcmp +int mpack_memcmp(const void* s1, const void* s2, size_t n) { + const unsigned char *us1 = (const unsigned char *) s1; + const unsigned char *us2 = (const unsigned char *) s2; + while (n-- != 0) { + if (*us1 != *us2) + return (*us1 < *us2) ? -1 : +1; + us1++; + us2++; + } + return 0; +} +#endif + +#ifndef mpack_memcpy +void* mpack_memcpy(void* MPACK_RESTRICT s1, const void* MPACK_RESTRICT s2, size_t n) { + char* MPACK_RESTRICT dst = (char *)s1; + const char* MPACK_RESTRICT src = (const char *)s2; + while (n-- != 0) + *dst++ = *src++; + return s1; +} +#endif + +#ifndef mpack_memmove +void* mpack_memmove(void* s1, const void* s2, size_t n) { + char *p1 = (char *)s1; + const char *p2 = (const char *)s2; + if (p2 < p1 && p1 < p2 + n) { + p2 += n; + p1 += n; + while (n-- != 0) + *--p1 = *--p2; + } else + while (n-- != 0) + *p1++ = *p2++; + return s1; +} +#endif + +#ifndef mpack_memset +void* mpack_memset(void* s, int c, size_t n) { + unsigned char *us = (unsigned char *)s; + unsigned char uc = (unsigned char)c; + while (n-- != 0) + *us++ = uc; + return s; +} +#endif + +#ifndef mpack_strlen +size_t mpack_strlen(const char* s) { + const char* p = s; + while (*p != '\0') + p++; + return (size_t)(p - s); +} +#endif + + + +#if defined(MPACK_MALLOC) && !defined(MPACK_REALLOC) +void* mpack_realloc(void* old_ptr, size_t used_size, size_t new_size) { + if (new_size == 0) { + if (old_ptr) + MPACK_FREE(old_ptr); + return NULL; + } + + void* new_ptr = MPACK_MALLOC(new_size); + if (new_ptr == NULL) + return NULL; + + mpack_memcpy(new_ptr, old_ptr, used_size); + MPACK_FREE(old_ptr); + return new_ptr; +} +#endif + +/* mpack/mpack-common.c.c */ + +#define MPACK_INTERNAL 1 + +/* #include "mpack-common.h" */ + +#if MPACK_DEBUG && MPACK_STDIO +#include <stdarg.h> +#endif + +const char* mpack_error_to_string(mpack_error_t error) { + #if MPACK_STRINGS + switch (error) { + #define MPACK_ERROR_STRING_CASE(e) case e: return #e + MPACK_ERROR_STRING_CASE(mpack_ok); + MPACK_ERROR_STRING_CASE(mpack_error_io); + MPACK_ERROR_STRING_CASE(mpack_error_invalid); + MPACK_ERROR_STRING_CASE(mpack_error_unsupported); + MPACK_ERROR_STRING_CASE(mpack_error_type); + MPACK_ERROR_STRING_CASE(mpack_error_too_big); + MPACK_ERROR_STRING_CASE(mpack_error_memory); + MPACK_ERROR_STRING_CASE(mpack_error_bug); + MPACK_ERROR_STRING_CASE(mpack_error_data); + MPACK_ERROR_STRING_CASE(mpack_error_eof); + #undef MPACK_ERROR_STRING_CASE + } + mpack_assert(0, "unrecognized error %i", (int)error); + return "(unknown mpack_error_t)"; + #else + MPACK_UNUSED(error); + return ""; + #endif +} + +const char* mpack_type_to_string(mpack_type_t type) { + #if MPACK_STRINGS + switch (type) { + #define MPACK_TYPE_STRING_CASE(e) case e: return #e + MPACK_TYPE_STRING_CASE(mpack_type_missing); + MPACK_TYPE_STRING_CASE(mpack_type_nil); + MPACK_TYPE_STRING_CASE(mpack_type_bool); + MPACK_TYPE_STRING_CASE(mpack_type_float); + MPACK_TYPE_STRING_CASE(mpack_type_double); + MPACK_TYPE_STRING_CASE(mpack_type_int); + MPACK_TYPE_STRING_CASE(mpack_type_uint); + MPACK_TYPE_STRING_CASE(mpack_type_str); + MPACK_TYPE_STRING_CASE(mpack_type_bin); + MPACK_TYPE_STRING_CASE(mpack_type_array); + MPACK_TYPE_STRING_CASE(mpack_type_map); + #if MPACK_EXTENSIONS + MPACK_TYPE_STRING_CASE(mpack_type_ext); + #endif + #undef MPACK_TYPE_STRING_CASE + } + mpack_assert(0, "unrecognized type %i", (int)type); + return "(unknown mpack_type_t)"; + #else + MPACK_UNUSED(type); + return ""; + #endif +} + +int mpack_tag_cmp(mpack_tag_t left, mpack_tag_t right) { + + // positive numbers may be stored as int; convert to uint + if (left.type == mpack_type_int && left.v.i >= 0) { + left.type = mpack_type_uint; + left.v.u = (uint64_t)left.v.i; + } + if (right.type == mpack_type_int && right.v.i >= 0) { + right.type = mpack_type_uint; + right.v.u = (uint64_t)right.v.i; + } + + if (left.type != right.type) + return ((int)left.type < (int)right.type) ? -1 : 1; + + switch (left.type) { + case mpack_type_missing: // fallthrough + case mpack_type_nil: + return 0; + + case mpack_type_bool: + return (int)left.v.b - (int)right.v.b; + + case mpack_type_int: + if (left.v.i == right.v.i) + return 0; + return (left.v.i < right.v.i) ? -1 : 1; + + case mpack_type_uint: + if (left.v.u == right.v.u) + return 0; + return (left.v.u < right.v.u) ? -1 : 1; + + case mpack_type_array: + case mpack_type_map: + if (left.v.n == right.v.n) + return 0; + return (left.v.n < right.v.n) ? -1 : 1; + + case mpack_type_str: + case mpack_type_bin: + if (left.v.l == right.v.l) + return 0; + return (left.v.l < right.v.l) ? -1 : 1; + + #if MPACK_EXTENSIONS + case mpack_type_ext: + if (left.exttype == right.exttype) { + if (left.v.l == right.v.l) + return 0; + return (left.v.l < right.v.l) ? -1 : 1; + } + return (int)left.exttype - (int)right.exttype; + #endif + + // floats should not normally be compared for equality. we compare + // with memcmp() to silence compiler warnings, but this will return + // equal if both are NaNs with the same representation (though we may + // want this, for instance if you are for some bizarre reason using + // floats as map keys.) i'm not sure what the right thing to + // do is here. check for NaN first? always return false if the type + // is float? use operator== and pragmas to silence compiler warning? + // please send me your suggestions. + // note also that we don't convert floats to doubles, so when this is + // used for ordering purposes, all floats are ordered before all + // doubles. + case mpack_type_float: + return mpack_memcmp(&left.v.f, &right.v.f, sizeof(left.v.f)); + case mpack_type_double: + return mpack_memcmp(&left.v.d, &right.v.d, sizeof(left.v.d)); + } + + mpack_assert(0, "unrecognized type %i", (int)left.type); + return false; +} + +#if MPACK_DEBUG && MPACK_STDIO +static char mpack_hex_char(uint8_t hex_value) { + return (hex_value < 10) ? (char)('0' + hex_value) : (char)('a' + (hex_value - 10)); +} + +static void mpack_tag_debug_complete_bin_ext(mpack_tag_t tag, size_t string_length, char* buffer, size_t buffer_size, + const char* prefix, size_t prefix_size) +{ + // If at any point in this function we run out of space in the buffer, we + // bail out. The outer tag print wrapper will make sure we have a + // null-terminator. + + if (string_length == 0 || string_length >= buffer_size) + return; + buffer += string_length; + buffer_size -= string_length; + + size_t total = mpack_tag_bytes(&tag); + if (total == 0) { + strncpy(buffer, ">", buffer_size); + return; + } + + strncpy(buffer, ": ", buffer_size); + if (buffer_size < 2) + return; + buffer += 2; + buffer_size -= 2; + + size_t hex_bytes = 0; + for (size_t i = 0; i < MPACK_PRINT_BYTE_COUNT && i < prefix_size && buffer_size > 2; ++i) { + uint8_t byte = (uint8_t)prefix[i]; + buffer[0] = mpack_hex_char((uint8_t)(byte >> 4)); + buffer[1] = mpack_hex_char((uint8_t)(byte & 0xfu)); + buffer += 2; + buffer_size -= 2; + ++hex_bytes; + } + + if (buffer_size != 0) + mpack_snprintf(buffer, buffer_size, "%s>", (total > hex_bytes) ? "..." : ""); +} + +static void mpack_tag_debug_pseudo_json_bin(mpack_tag_t tag, char* buffer, size_t buffer_size, + const char* prefix, size_t prefix_size) +{ + mpack_assert(mpack_tag_type(&tag) == mpack_type_bin); + size_t length = (size_t)mpack_snprintf(buffer, buffer_size, "<binary data of length %u", tag.v.l); + mpack_tag_debug_complete_bin_ext(tag, length, buffer, buffer_size, prefix, prefix_size); +} + +#if MPACK_EXTENSIONS +static void mpack_tag_debug_pseudo_json_ext(mpack_tag_t tag, char* buffer, size_t buffer_size, + const char* prefix, size_t prefix_size) +{ + mpack_assert(mpack_tag_type(&tag) == mpack_type_ext); + size_t length = (size_t)mpack_snprintf(buffer, buffer_size, "<ext data of type %i and length %u", + mpack_tag_ext_exttype(&tag), mpack_tag_ext_length(&tag)); + mpack_tag_debug_complete_bin_ext(tag, length, buffer, buffer_size, prefix, prefix_size); +} +#endif + +static void mpack_tag_debug_pseudo_json_impl(mpack_tag_t tag, char* buffer, size_t buffer_size, + const char* prefix, size_t prefix_size) +{ + switch (tag.type) { + case mpack_type_missing: + mpack_snprintf(buffer, buffer_size, "<missing!>"); + return; + case mpack_type_nil: + mpack_snprintf(buffer, buffer_size, "null"); + return; + case mpack_type_bool: + mpack_snprintf(buffer, buffer_size, tag.v.b ? "true" : "false"); + return; + case mpack_type_int: + mpack_snprintf(buffer, buffer_size, "%" PRIi64, tag.v.i); + return; + case mpack_type_uint: + mpack_snprintf(buffer, buffer_size, "%" PRIu64, tag.v.u); + return; + case mpack_type_float: + mpack_snprintf(buffer, buffer_size, "%f", tag.v.f); + return; + case mpack_type_double: + mpack_snprintf(buffer, buffer_size, "%f", tag.v.d); + return; + + case mpack_type_str: + mpack_snprintf(buffer, buffer_size, "<string of %u bytes>", tag.v.l); + return; + case mpack_type_bin: + mpack_tag_debug_pseudo_json_bin(tag, buffer, buffer_size, prefix, prefix_size); + return; + #if MPACK_EXTENSIONS + case mpack_type_ext: + mpack_tag_debug_pseudo_json_ext(tag, buffer, buffer_size, prefix, prefix_size); + return; + #endif + + case mpack_type_array: + mpack_snprintf(buffer, buffer_size, "<array of %u elements>", tag.v.n); + return; + case mpack_type_map: + mpack_snprintf(buffer, buffer_size, "<map of %u key-value pairs>", tag.v.n); + return; + } + + mpack_snprintf(buffer, buffer_size, "<unknown!>"); +} + +void mpack_tag_debug_pseudo_json(mpack_tag_t tag, char* buffer, size_t buffer_size, + const char* prefix, size_t prefix_size) +{ + mpack_assert(buffer_size > 0, "buffer size cannot be zero!"); + buffer[0] = 0; + + mpack_tag_debug_pseudo_json_impl(tag, buffer, buffer_size, prefix, prefix_size); + + // We always null-terminate the buffer manually just in case the snprintf() + // function doesn't null-terminate when the string doesn't fit. + buffer[buffer_size - 1] = 0; +} + +static void mpack_tag_debug_describe_impl(mpack_tag_t tag, char* buffer, size_t buffer_size) { + switch (tag.type) { + case mpack_type_missing: + mpack_snprintf(buffer, buffer_size, "missing"); + return; + case mpack_type_nil: + mpack_snprintf(buffer, buffer_size, "nil"); + return; + case mpack_type_bool: + mpack_snprintf(buffer, buffer_size, tag.v.b ? "true" : "false"); + return; + case mpack_type_int: + mpack_snprintf(buffer, buffer_size, "int %" PRIi64, tag.v.i); + return; + case mpack_type_uint: + mpack_snprintf(buffer, buffer_size, "uint %" PRIu64, tag.v.u); + return; + case mpack_type_float: + mpack_snprintf(buffer, buffer_size, "float %f", tag.v.f); + return; + case mpack_type_double: + mpack_snprintf(buffer, buffer_size, "double %f", tag.v.d); + return; + case mpack_type_str: + mpack_snprintf(buffer, buffer_size, "str of %u bytes", tag.v.l); + return; + case mpack_type_bin: + mpack_snprintf(buffer, buffer_size, "bin of %u bytes", tag.v.l); + return; + #if MPACK_EXTENSIONS + case mpack_type_ext: + mpack_snprintf(buffer, buffer_size, "ext of type %i, %u bytes", + mpack_tag_ext_exttype(&tag), mpack_tag_ext_length(&tag)); + return; + #endif + case mpack_type_array: + mpack_snprintf(buffer, buffer_size, "array of %u elements", tag.v.n); + return; + case mpack_type_map: + mpack_snprintf(buffer, buffer_size, "map of %u key-value pairs", tag.v.n); + return; + } + + mpack_snprintf(buffer, buffer_size, "unknown!"); +} + +void mpack_tag_debug_describe(mpack_tag_t tag, char* buffer, size_t buffer_size) { + mpack_assert(buffer_size > 0, "buffer size cannot be zero!"); + buffer[0] = 0; + + mpack_tag_debug_describe_impl(tag, buffer, buffer_size); + + // We always null-terminate the buffer manually just in case the snprintf() + // function doesn't null-terminate when the string doesn't fit. + buffer[buffer_size - 1] = 0; +} +#endif + + + +#if MPACK_READ_TRACKING || MPACK_WRITE_TRACKING + +#ifndef MPACK_TRACKING_INITIAL_CAPACITY +// seems like a reasonable number. we grow by doubling, and it only +// needs to be as long as the maximum depth of the message. +#define MPACK_TRACKING_INITIAL_CAPACITY 8 +#endif + +mpack_error_t mpack_track_init(mpack_track_t* track) { + track->count = 0; + track->capacity = MPACK_TRACKING_INITIAL_CAPACITY; + track->elements = (mpack_track_element_t*)MPACK_MALLOC(sizeof(mpack_track_element_t) * track->capacity); + if (track->elements == NULL) + return mpack_error_memory; + return mpack_ok; +} + +mpack_error_t mpack_track_grow(mpack_track_t* track) { + mpack_assert(track->elements, "null track elements!"); + mpack_assert(track->count == track->capacity, "incorrect growing?"); + + size_t new_capacity = track->capacity * 2; + + mpack_track_element_t* new_elements = (mpack_track_element_t*)mpack_realloc(track->elements, + sizeof(mpack_track_element_t) * track->count, sizeof(mpack_track_element_t) * new_capacity); + if (new_elements == NULL) + return mpack_error_memory; + + track->elements = new_elements; + track->capacity = new_capacity; + return mpack_ok; +} + +mpack_error_t mpack_track_push(mpack_track_t* track, mpack_type_t type, uint64_t count) { + mpack_assert(track->elements, "null track elements!"); + mpack_log("track pushing %s count %i\n", mpack_type_to_string(type), (int)count); + + // maps have twice the number of elements (key/value pairs) + if (type == mpack_type_map) + count *= 2; + + // grow if needed + if (track->count == track->capacity) { + mpack_error_t error = mpack_track_grow(track); + if (error != mpack_ok) + return error; + } + + // insert new track + track->elements[track->count].type = type; + track->elements[track->count].left = count; + ++track->count; + return mpack_ok; +} + +mpack_error_t mpack_track_pop(mpack_track_t* track, mpack_type_t type) { + mpack_assert(track->elements, "null track elements!"); + mpack_log("track popping %s\n", mpack_type_to_string(type)); + + if (track->count == 0) { + mpack_break("attempting to close a %s but nothing was opened!", mpack_type_to_string(type)); + return mpack_error_bug; + } + + mpack_track_element_t* element = &track->elements[track->count - 1]; + + if (element->type != type) { + mpack_break("attempting to close a %s but the open element is a %s!", + mpack_type_to_string(type), mpack_type_to_string(element->type)); + return mpack_error_bug; + } + + if (element->left != 0) { + mpack_break("attempting to close a %s but there are %" PRIu64 " %s left", + mpack_type_to_string(type), element->left, + (type == mpack_type_map || type == mpack_type_array) ? "elements" : "bytes"); + return mpack_error_bug; + } + + --track->count; + return mpack_ok; +} + +mpack_error_t mpack_track_peek_element(mpack_track_t* track, bool read) { + MPACK_UNUSED(read); + mpack_assert(track->elements, "null track elements!"); + + // if there are no open elements, that's fine, we can read/write elements at will + if (track->count == 0) + return mpack_ok; + + mpack_track_element_t* element = &track->elements[track->count - 1]; + + if (element->type != mpack_type_map && element->type != mpack_type_array) { + mpack_break("elements cannot be %s within an %s", read ? "read" : "written", + mpack_type_to_string(element->type)); + return mpack_error_bug; + } + + if (element->left == 0) { + mpack_break("too many elements %s for %s", read ? "read" : "written", + mpack_type_to_string(element->type)); + return mpack_error_bug; + } + + return mpack_ok; +} + +mpack_error_t mpack_track_element(mpack_track_t* track, bool read) { + mpack_error_t error = mpack_track_peek_element(track, read); + if (track->count > 0 && error == mpack_ok) + --track->elements[track->count - 1].left; + return error; +} + +mpack_error_t mpack_track_bytes(mpack_track_t* track, bool read, uint64_t count) { + MPACK_UNUSED(read); + mpack_assert(track->elements, "null track elements!"); + + if (track->count == 0) { + mpack_break("bytes cannot be %s with no open bin, str or ext", read ? "read" : "written"); + return mpack_error_bug; + } + + mpack_track_element_t* element = &track->elements[track->count - 1]; + + if (element->type == mpack_type_map || element->type == mpack_type_array) { + mpack_break("bytes cannot be %s within an %s", read ? "read" : "written", + mpack_type_to_string(element->type)); + return mpack_error_bug; + } + + if (element->left < count) { + mpack_break("too many bytes %s for %s", read ? "read" : "written", + mpack_type_to_string(element->type)); + return mpack_error_bug; + } + + element->left -= count; + return mpack_ok; +} + +mpack_error_t mpack_track_str_bytes_all(mpack_track_t* track, bool read, uint64_t count) { + mpack_error_t error = mpack_track_bytes(track, read, count); + if (error != mpack_ok) + return error; + + mpack_track_element_t* element = &track->elements[track->count - 1]; + + if (element->type != mpack_type_str) { + mpack_break("the open type must be a string, not a %s", mpack_type_to_string(element->type)); + return mpack_error_bug; + } + + if (element->left != 0) { + mpack_break("not all bytes were read; the wrong byte count was requested for a string read."); + return mpack_error_bug; + } + + return mpack_ok; +} + +mpack_error_t mpack_track_check_empty(mpack_track_t* track) { + if (track->count != 0) { + mpack_break("unclosed %s", mpack_type_to_string(track->elements[0].type)); + return mpack_error_bug; + } + return mpack_ok; +} + +mpack_error_t mpack_track_destroy(mpack_track_t* track, bool cancel) { + mpack_error_t error = cancel ? mpack_ok : mpack_track_check_empty(track); + if (track->elements) { + MPACK_FREE(track->elements); + track->elements = NULL; + } + return error; +} +#endif + + + +static bool mpack_utf8_check_impl(const uint8_t* str, size_t count, bool allow_null) { + while (count > 0) { + uint8_t lead = str[0]; + + // NUL + if (!allow_null && lead == '\0') // we don't allow NUL bytes in MPack C-strings + return false; + + // ASCII + if (lead <= 0x7F) { + ++str; + --count; + + // 2-byte sequence + } else if ((lead & 0xE0) == 0xC0) { + if (count < 2) // truncated sequence + return false; + + uint8_t cont = str[1]; + if ((cont & 0xC0) != 0x80) // not a continuation byte + return false; + + str += 2; + count -= 2; + + uint32_t z = ((uint32_t)(lead & ~0xE0) << 6) | + (uint32_t)(cont & ~0xC0); + + if (z < 0x80) // overlong sequence + return false; + + // 3-byte sequence + } else if ((lead & 0xF0) == 0xE0) { + if (count < 3) // truncated sequence + return false; + + uint8_t cont1 = str[1]; + if ((cont1 & 0xC0) != 0x80) // not a continuation byte + return false; + uint8_t cont2 = str[2]; + if ((cont2 & 0xC0) != 0x80) // not a continuation byte + return false; + + str += 3; + count -= 3; + + uint32_t z = ((uint32_t)(lead & ~0xF0) << 12) | + ((uint32_t)(cont1 & ~0xC0) << 6) | + (uint32_t)(cont2 & ~0xC0); + + if (z < 0x800) // overlong sequence + return false; + if (z >= 0xD800 && z <= 0xDFFF) // surrogate + return false; + + // 4-byte sequence + } else if ((lead & 0xF8) == 0xF0) { + if (count < 4) // truncated sequence + return false; + + uint8_t cont1 = str[1]; + if ((cont1 & 0xC0) != 0x80) // not a continuation byte + return false; + uint8_t cont2 = str[2]; + if ((cont2 & 0xC0) != 0x80) // not a continuation byte + return false; + uint8_t cont3 = str[3]; + if ((cont3 & 0xC0) != 0x80) // not a continuation byte + return false; + + str += 4; + count -= 4; + + uint32_t z = ((uint32_t)(lead & ~0xF8) << 18) | + ((uint32_t)(cont1 & ~0xC0) << 12) | + ((uint32_t)(cont2 & ~0xC0) << 6) | + (uint32_t)(cont3 & ~0xC0); + + if (z < 0x10000) // overlong sequence + return false; + if (z > 0x10FFFF) // codepoint limit + return false; + + } else { + return false; // continuation byte without a lead, or lead for a 5-byte sequence or longer + } + } + return true; +} + +bool mpack_utf8_check(const char* str, size_t bytes) { + return mpack_utf8_check_impl((const uint8_t*)str, bytes, true); +} + +bool mpack_utf8_check_no_null(const char* str, size_t bytes) { + return mpack_utf8_check_impl((const uint8_t*)str, bytes, false); +} + +bool mpack_str_check_no_null(const char* str, size_t bytes) { + for (size_t i = 0; i < bytes; ++i) + if (str[i] == '\0') + return false; + return true; +} + +#if MPACK_DEBUG && MPACK_STDIO +void mpack_print_append(mpack_print_t* print, const char* data, size_t count) { + + // copy whatever fits into the buffer + size_t copy = print->size - print->count; + if (copy > count) + copy = count; + mpack_memcpy(print->buffer + print->count, data, copy); + print->count += copy; + data += copy; + count -= copy; + + // if we don't need to flush or can't flush there's nothing else to do + if (count == 0 || print->callback == NULL) + return; + + // flush the buffer + print->callback(print->context, print->buffer, print->count); + + if (count > print->size / 2) { + // flush the rest of the data + print->count = 0; + print->callback(print->context, data, count); + } else { + // copy the rest of the data into the buffer + mpack_memcpy(print->buffer, data, count); + print->count = count; + } + +} + +void mpack_print_flush(mpack_print_t* print) { + if (print->count > 0 && print->callback != NULL) { + print->callback(print->context, print->buffer, print->count); + print->count = 0; + } +} + +void mpack_print_file_callback(void* context, const char* data, size_t count) { + FILE* file = (FILE*)context; + fwrite(data, 1, count, file); +} +#endif + +/* mpack/mpack-writer.c.c */ + +#define MPACK_INTERNAL 1 + +/* #include "mpack-writer.h" */ + +#if MPACK_WRITER + +#if MPACK_WRITE_TRACKING +static void mpack_writer_flag_if_error(mpack_writer_t* writer, mpack_error_t error) { + if (error != mpack_ok) + mpack_writer_flag_error(writer, error); +} + +void mpack_writer_track_push(mpack_writer_t* writer, mpack_type_t type, uint64_t count) { + if (writer->error == mpack_ok) + mpack_writer_flag_if_error(writer, mpack_track_push(&writer->track, type, count)); +} + +void mpack_writer_track_pop(mpack_writer_t* writer, mpack_type_t type) { + if (writer->error == mpack_ok) + mpack_writer_flag_if_error(writer, mpack_track_pop(&writer->track, type)); +} + +void mpack_writer_track_element(mpack_writer_t* writer) { + if (writer->error == mpack_ok) + mpack_writer_flag_if_error(writer, mpack_track_element(&writer->track, false)); +} + +void mpack_writer_track_bytes(mpack_writer_t* writer, size_t count) { + if (writer->error == mpack_ok) + mpack_writer_flag_if_error(writer, mpack_track_bytes(&writer->track, false, count)); +} +#endif + +static void mpack_writer_clear(mpack_writer_t* writer) { + #if MPACK_COMPATIBILITY + writer->version = mpack_version_current; + #endif + writer->flush = NULL; + writer->error_fn = NULL; + writer->teardown = NULL; + writer->context = NULL; + + writer->buffer = NULL; + writer->current = NULL; + writer->end = NULL; + writer->error = mpack_ok; + + #if MPACK_WRITE_TRACKING + mpack_memset(&writer->track, 0, sizeof(writer->track)); + #endif +} + +void mpack_writer_init(mpack_writer_t* writer, char* buffer, size_t size) { + mpack_assert(buffer != NULL, "cannot initialize writer with empty buffer"); + mpack_writer_clear(writer); + writer->buffer = buffer; + writer->current = buffer; + writer->end = writer->buffer + size; + + #if MPACK_WRITE_TRACKING + mpack_writer_flag_if_error(writer, mpack_track_init(&writer->track)); + #endif + + mpack_log("===========================\n"); + mpack_log("initializing writer with buffer size %i\n", (int)size); +} + +void mpack_writer_init_error(mpack_writer_t* writer, mpack_error_t error) { + mpack_writer_clear(writer); + writer->error = error; + + mpack_log("===========================\n"); + mpack_log("initializing writer in error state %i\n", (int)error); +} + +void mpack_writer_set_flush(mpack_writer_t* writer, mpack_writer_flush_t flush) { + MPACK_STATIC_ASSERT(MPACK_WRITER_MINIMUM_BUFFER_SIZE >= MPACK_MAXIMUM_TAG_SIZE, + "minimum buffer size must fit any tag!"); + MPACK_STATIC_ASSERT(31 + MPACK_TAG_SIZE_FIXSTR >= MPACK_WRITER_MINIMUM_BUFFER_SIZE, + "minimum buffer size must fit the largest possible fixstr!"); + + if (mpack_writer_buffer_size(writer) < MPACK_WRITER_MINIMUM_BUFFER_SIZE) { + mpack_break("buffer size is %i, but minimum buffer size for flush is %i", + (int)mpack_writer_buffer_size(writer), MPACK_WRITER_MINIMUM_BUFFER_SIZE); + mpack_writer_flag_error(writer, mpack_error_bug); + return; + } + + writer->flush = flush; +} + +#ifdef MPACK_MALLOC +typedef struct mpack_growable_writer_t { + char** target_data; + size_t* target_size; +} mpack_growable_writer_t; + +static char* mpack_writer_get_reserved(mpack_writer_t* writer) { + // This is in a separate function in order to avoid false strict aliasing + // warnings. We aren't actually violating strict aliasing (the reserved + // space is only ever dereferenced as an mpack_growable_writer_t.) + return (char*)writer->reserved; +} + +static void mpack_growable_writer_flush(mpack_writer_t* writer, const char* data, size_t count) { + + // This is an intrusive flush function which modifies the writer's buffer + // in response to a flush instead of emptying it in order to add more + // capacity for data. This removes the need to copy data from a fixed buffer + // into a growable one, improving performance. + // + // There are three ways flush can be called: + // - flushing the buffer during writing (used is zero, count is all data, data is buffer) + // - flushing extra data during writing (used is all flushed data, count is extra data, data is not buffer) + // - flushing during teardown (used and count are both all flushed data, data is buffer) + // + // In the first two cases, we grow the buffer by at least double, enough + // to ensure that new data will fit. We ignore the teardown flush. + + if (data == writer->buffer) { + + // teardown, do nothing + if (mpack_writer_buffer_used(writer) == count) + return; + + // otherwise leave the data in the buffer and just grow + writer->current = writer->buffer + count; + count = 0; + } + + size_t used = mpack_writer_buffer_used(writer); + size_t size = mpack_writer_buffer_size(writer); + + mpack_log("flush size %i used %i data %p buffer %p\n", + (int)count, (int)used, data, writer->buffer); + + mpack_assert(data == writer->buffer || used + count > size, + "extra flush for %i but there is %i space left in the buffer! (%i/%i)", + (int)count, (int)mpack_writer_buffer_left(writer), (int)used, (int)size); + + // grow to fit the data + // TODO: this really needs to correctly test for overflow + size_t new_size = size * 2; + while (new_size < used + count) + new_size *= 2; + + mpack_log("flush growing buffer size from %i to %i\n", (int)size, (int)new_size); + + // grow the buffer + char* new_buffer = (char*)mpack_realloc(writer->buffer, used, new_size); + if (new_buffer == NULL) { + mpack_writer_flag_error(writer, mpack_error_memory); + return; + } + writer->current = new_buffer + used; + writer->buffer = new_buffer; + writer->end = writer->buffer + new_size; + + // append the extra data + if (count > 0) { + mpack_memcpy(writer->current, data, count); + writer->current += count; + } + + mpack_log("new buffer %p, used %i\n", new_buffer, (int)mpack_writer_buffer_used(writer)); +} + +static void mpack_growable_writer_teardown(mpack_writer_t* writer) { + mpack_growable_writer_t* growable_writer = (mpack_growable_writer_t*)mpack_writer_get_reserved(writer); + + if (mpack_writer_error(writer) == mpack_ok) { + + // shrink the buffer to an appropriate size if the data is + // much smaller than the buffer + if (mpack_writer_buffer_used(writer) < mpack_writer_buffer_size(writer) / 2) { + size_t used = mpack_writer_buffer_used(writer); + + // We always return a non-null pointer that must be freed, even if + // nothing was written. malloc() and realloc() do not necessarily + // do this so we enforce it ourselves. + size_t size = (used != 0) ? used : 1; + + char* buffer = (char*)mpack_realloc(writer->buffer, used, size); + if (!buffer) { + MPACK_FREE(writer->buffer); + mpack_writer_flag_error(writer, mpack_error_memory); + return; + } + writer->buffer = buffer; + writer->end = (writer->current = writer->buffer + used); + } + + *growable_writer->target_data = writer->buffer; + *growable_writer->target_size = mpack_writer_buffer_used(writer); + writer->buffer = NULL; + + } else if (writer->buffer) { + MPACK_FREE(writer->buffer); + writer->buffer = NULL; + } + + writer->context = NULL; +} + +void mpack_writer_init_growable(mpack_writer_t* writer, char** target_data, size_t* target_size) { + mpack_assert(target_data != NULL, "cannot initialize writer without a destination for the data"); + mpack_assert(target_size != NULL, "cannot initialize writer without a destination for the size"); + + *target_data = NULL; + *target_size = 0; + + MPACK_STATIC_ASSERT(sizeof(mpack_growable_writer_t) <= sizeof(writer->reserved), + "not enough reserved space for growable writer!"); + mpack_growable_writer_t* growable_writer = (mpack_growable_writer_t*)mpack_writer_get_reserved(writer); + + growable_writer->target_data = target_data; + growable_writer->target_size = target_size; + + size_t capacity = MPACK_BUFFER_SIZE; + char* buffer = (char*)MPACK_MALLOC(capacity); + if (buffer == NULL) { + mpack_writer_init_error(writer, mpack_error_memory); + return; + } + + mpack_writer_init(writer, buffer, capacity); + mpack_writer_set_flush(writer, mpack_growable_writer_flush); + mpack_writer_set_teardown(writer, mpack_growable_writer_teardown); +} +#endif + +#if MPACK_STDIO +static void mpack_file_writer_flush(mpack_writer_t* writer, const char* buffer, size_t count) { + FILE* file = (FILE*)writer->context; + size_t written = fwrite((const void*)buffer, 1, count, file); + if (written != count) + mpack_writer_flag_error(writer, mpack_error_io); +} + +static void mpack_file_writer_teardown(mpack_writer_t* writer) { + MPACK_FREE(writer->buffer); + writer->buffer = NULL; + writer->context = NULL; +} + +static void mpack_file_writer_teardown_close(mpack_writer_t* writer) { + FILE* file = (FILE*)writer->context; + + if (file) { + int ret = fclose(file); + if (ret != 0) + mpack_writer_flag_error(writer, mpack_error_io); + } + + mpack_file_writer_teardown(writer); +} + +void mpack_writer_init_stdfile(mpack_writer_t* writer, FILE* file, bool close_when_done) { + mpack_assert(file != NULL, "file is NULL"); + + size_t capacity = MPACK_BUFFER_SIZE; + char* buffer = (char*)MPACK_MALLOC(capacity); + if (buffer == NULL) { + mpack_writer_init_error(writer, mpack_error_memory); + if (close_when_done) { + fclose(file); + } + return; + } + + mpack_writer_init(writer, buffer, capacity); + mpack_writer_set_context(writer, file); + mpack_writer_set_flush(writer, mpack_file_writer_flush); + mpack_writer_set_teardown(writer, close_when_done ? + mpack_file_writer_teardown_close : + mpack_file_writer_teardown); +} + +void mpack_writer_init_filename(mpack_writer_t* writer, const char* filename) { + mpack_assert(filename != NULL, "filename is NULL"); + + FILE* file = fopen(filename, "wb"); + if (file == NULL) { + mpack_writer_init_error(writer, mpack_error_io); + return; + } + + mpack_writer_init_stdfile(writer, file, true); +} +#endif + +void mpack_writer_flag_error(mpack_writer_t* writer, mpack_error_t error) { + mpack_log("writer %p setting error %i: %s\n", writer, (int)error, mpack_error_to_string(error)); + + if (writer->error == mpack_ok) { + writer->error = error; + if (writer->error_fn) + writer->error_fn(writer, writer->error); + } +} + +MPACK_STATIC_INLINE void mpack_writer_flush_unchecked(mpack_writer_t* writer) { + // This is a bit ugly; we reset used before calling flush so that + // a flush function can distinguish between flushing the buffer + // versus flushing external data. see mpack_growable_writer_flush() + size_t used = mpack_writer_buffer_used(writer); + writer->current = writer->buffer; + writer->flush(writer, writer->buffer, used); +} + +void mpack_writer_flush_message(mpack_writer_t* writer) { + if (writer->error != mpack_ok) + return; + + #if MPACK_WRITE_TRACKING + mpack_writer_flag_if_error(writer, mpack_track_check_empty(&writer->track)); + if (writer->error != mpack_ok) + return; + #endif + + if (writer->flush == NULL) { + mpack_break("cannot call mpack_writer_flush_message() without a flush function!"); + mpack_writer_flag_error(writer, mpack_error_bug); + return; + } + + if (mpack_writer_buffer_used(writer) > 0) + mpack_writer_flush_unchecked(writer); +} + +// Ensures there are at least count bytes free in the buffer. This +// will flag an error if the flush function fails to make enough +// room in the buffer. +MPACK_NOINLINE static bool mpack_writer_ensure(mpack_writer_t* writer, size_t count) { + mpack_assert(count != 0, "cannot ensure zero bytes!"); + mpack_assert(count <= MPACK_WRITER_MINIMUM_BUFFER_SIZE, + "cannot ensure %i bytes, this is more than the minimum buffer size %i!", + (int)count, (int)MPACK_WRITER_MINIMUM_BUFFER_SIZE); + mpack_assert(count > mpack_writer_buffer_left(writer), + "request to ensure %i bytes but there are already %i left in the buffer!", + (int)count, (int)mpack_writer_buffer_left(writer)); + + mpack_log("ensuring %i bytes, %i left\n", (int)count, (int)mpack_writer_buffer_left(writer)); + + if (mpack_writer_error(writer) != mpack_ok) + return false; + + if (writer->flush == NULL) { + mpack_writer_flag_error(writer, mpack_error_too_big); + return false; + } + + mpack_writer_flush_unchecked(writer); + if (mpack_writer_error(writer) != mpack_ok) + return false; + + if (mpack_writer_buffer_left(writer) >= count) + return true; + + mpack_writer_flag_error(writer, mpack_error_io); + return false; +} + +// Writes encoded bytes to the buffer when we already know the data +// does not fit in the buffer (i.e. it straddles the edge of the +// buffer.) If there is a flush function, it is guaranteed to be +// called; otherwise mpack_error_too_big is raised. +MPACK_NOINLINE static void mpack_write_native_straddle(mpack_writer_t* writer, const char* p, size_t count) { + mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count); + + if (mpack_writer_error(writer) != mpack_ok) + return; + mpack_log("big write for %i bytes from %p, %i space left in buffer\n", + (int)count, p, (int)mpack_writer_buffer_left(writer)); + mpack_assert(count > mpack_writer_buffer_left(writer), + "big write requested for %i bytes, but there is %i available " + "space in buffer. should have called mpack_write_native() instead", + (int)count, (int)(mpack_writer_buffer_left(writer))); + + // we'll need a flush function + if (!writer->flush) { + mpack_writer_flag_error(writer, mpack_error_too_big); + return; + } + + // flush the buffer + mpack_writer_flush_unchecked(writer); + if (mpack_writer_error(writer) != mpack_ok) + return; + + // note that an intrusive flush function (such as mpack_growable_writer_flush()) + // may have changed size and/or reset used to a non-zero value. we treat both as + // though they may have changed, and there may still be data in the buffer. + + // flush the extra data directly if it doesn't fit in the buffer + if (count > mpack_writer_buffer_left(writer)) { + writer->flush(writer, p, count); + if (mpack_writer_error(writer) != mpack_ok) + return; + } else { + mpack_memcpy(writer->current, p, count); + writer->current += count; + } +} + +// Writes encoded bytes to the buffer, flushing if necessary. +MPACK_STATIC_INLINE void mpack_write_native(mpack_writer_t* writer, const char* p, size_t count) { + mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count); + + if (mpack_writer_buffer_left(writer) < count) { + mpack_write_native_straddle(writer, p, count); + } else { + mpack_memcpy(writer->current, p, count); + writer->current += count; + } +} + +mpack_error_t mpack_writer_destroy(mpack_writer_t* writer) { + + // clean up tracking, asserting if we're not already in an error state + #if MPACK_WRITE_TRACKING + mpack_track_destroy(&writer->track, writer->error != mpack_ok); + #endif + + // flush any outstanding data + if (mpack_writer_error(writer) == mpack_ok && mpack_writer_buffer_used(writer) != 0 && writer->flush != NULL) { + writer->flush(writer, writer->buffer, mpack_writer_buffer_used(writer)); + writer->flush = NULL; + } + + if (writer->teardown) { + writer->teardown(writer); + writer->teardown = NULL; + } + + return writer->error; +} + +void mpack_write_tag(mpack_writer_t* writer, mpack_tag_t value) { + switch (value.type) { + case mpack_type_missing: + mpack_break("cannot write a missing value!"); + mpack_writer_flag_error(writer, mpack_error_bug); + return; + + case mpack_type_nil: mpack_write_nil (writer); return; + case mpack_type_bool: mpack_write_bool (writer, value.v.b); return; + case mpack_type_float: mpack_write_float (writer, value.v.f); return; + case mpack_type_double: mpack_write_double(writer, value.v.d); return; + case mpack_type_int: mpack_write_int (writer, value.v.i); return; + case mpack_type_uint: mpack_write_uint (writer, value.v.u); return; + + case mpack_type_str: mpack_start_str(writer, value.v.l); return; + case mpack_type_bin: mpack_start_bin(writer, value.v.l); return; + + #if MPACK_EXTENSIONS + case mpack_type_ext: + mpack_start_ext(writer, mpack_tag_ext_exttype(&value), mpack_tag_ext_length(&value)); + return; + #endif + + case mpack_type_array: mpack_start_array(writer, value.v.n); return; + case mpack_type_map: mpack_start_map(writer, value.v.n); return; + } + + mpack_break("unrecognized type %i", (int)value.type); + mpack_writer_flag_error(writer, mpack_error_bug); +} + +MPACK_STATIC_INLINE void mpack_write_byte_element(mpack_writer_t* writer, char value) { + mpack_writer_track_element(writer); + if (MPACK_LIKELY(mpack_writer_buffer_left(writer) >= 1) || mpack_writer_ensure(writer, 1)) + *(writer->current++) = value; +} + +void mpack_write_nil(mpack_writer_t* writer) { + mpack_write_byte_element(writer, (char)0xc0); +} + +void mpack_write_bool(mpack_writer_t* writer, bool value) { + mpack_write_byte_element(writer, (char)(0xc2 | (value ? 1 : 0))); +} + +void mpack_write_true(mpack_writer_t* writer) { + mpack_write_byte_element(writer, (char)0xc3); +} + +void mpack_write_false(mpack_writer_t* writer) { + mpack_write_byte_element(writer, (char)0xc2); +} + +void mpack_write_object_bytes(mpack_writer_t* writer, const char* data, size_t bytes) { + mpack_writer_track_element(writer); + mpack_write_native(writer, data, bytes); +} + +/* + * Encode functions + */ + +MPACK_STATIC_INLINE void mpack_encode_fixuint(char* p, uint8_t value) { + mpack_assert(value <= 127); + mpack_store_u8(p, value); +} + +MPACK_STATIC_INLINE void mpack_encode_u8(char* p, uint8_t value) { + mpack_assert(value > 127); + mpack_store_u8(p, 0xcc); + mpack_store_u8(p + 1, value); +} + +MPACK_STATIC_INLINE void mpack_encode_u16(char* p, uint16_t value) { + mpack_assert(value > UINT8_MAX); + mpack_store_u8(p, 0xcd); + mpack_store_u16(p + 1, value); +} + +MPACK_STATIC_INLINE void mpack_encode_u32(char* p, uint32_t value) { + mpack_assert(value > UINT16_MAX); + mpack_store_u8(p, 0xce); + mpack_store_u32(p + 1, value); +} + +MPACK_STATIC_INLINE void mpack_encode_u64(char* p, uint64_t value) { + mpack_assert(value > UINT32_MAX); + mpack_store_u8(p, 0xcf); + mpack_store_u64(p + 1, value); +} + +MPACK_STATIC_INLINE void mpack_encode_fixint(char* p, int8_t value) { + // this can encode positive or negative fixints + mpack_assert(value >= -32); + mpack_store_i8(p, value); +} + +MPACK_STATIC_INLINE void mpack_encode_i8(char* p, int8_t value) { + mpack_assert(value < -32); + mpack_store_u8(p, 0xd0); + mpack_store_i8(p + 1, value); +} + +MPACK_STATIC_INLINE void mpack_encode_i16(char* p, int16_t value) { + mpack_assert(value < INT8_MIN); + mpack_store_u8(p, 0xd1); + mpack_store_i16(p + 1, value); +} + +MPACK_STATIC_INLINE void mpack_encode_i32(char* p, int32_t value) { + mpack_assert(value < INT16_MIN); + mpack_store_u8(p, 0xd2); + mpack_store_i32(p + 1, value); +} + +MPACK_STATIC_INLINE void mpack_encode_i64(char* p, int64_t value) { + mpack_assert(value < INT32_MIN); + mpack_store_u8(p, 0xd3); + mpack_store_i64(p + 1, value); +} + +MPACK_STATIC_INLINE void mpack_encode_float(char* p, float value) { + mpack_store_u8(p, 0xca); + mpack_store_float(p + 1, value); +} + +MPACK_STATIC_INLINE void mpack_encode_double(char* p, double value) { + mpack_store_u8(p, 0xcb); + mpack_store_double(p + 1, value); +} + +MPACK_STATIC_INLINE void mpack_encode_fixarray(char* p, uint8_t count) { + mpack_assert(count <= 15); + mpack_store_u8(p, (uint8_t)(0x90 | count)); +} + +MPACK_STATIC_INLINE void mpack_encode_array16(char* p, uint16_t count) { + mpack_assert(count > 15); + mpack_store_u8(p, 0xdc); + mpack_store_u16(p + 1, count); +} + +MPACK_STATIC_INLINE void mpack_encode_array32(char* p, uint32_t count) { + mpack_assert(count > UINT16_MAX); + mpack_store_u8(p, 0xdd); + mpack_store_u32(p + 1, count); +} + +MPACK_STATIC_INLINE void mpack_encode_fixmap(char* p, uint8_t count) { + mpack_assert(count <= 15); + mpack_store_u8(p, (uint8_t)(0x80 | count)); +} + +MPACK_STATIC_INLINE void mpack_encode_map16(char* p, uint16_t count) { + mpack_assert(count > 15); + mpack_store_u8(p, 0xde); + mpack_store_u16(p + 1, count); +} + +MPACK_STATIC_INLINE void mpack_encode_map32(char* p, uint32_t count) { + mpack_assert(count > UINT16_MAX); + mpack_store_u8(p, 0xdf); + mpack_store_u32(p + 1, count); +} + +MPACK_STATIC_INLINE void mpack_encode_fixstr(char* p, uint8_t count) { + mpack_assert(count <= 31); + mpack_store_u8(p, (uint8_t)(0xa0 | count)); +} + +MPACK_STATIC_INLINE void mpack_encode_str8(char* p, uint8_t count) { + mpack_assert(count > 31); + mpack_store_u8(p, 0xd9); + mpack_store_u8(p + 1, count); +} + +MPACK_STATIC_INLINE void mpack_encode_str16(char* p, uint16_t count) { + // we might be encoding a raw in compatibility mode, so we + // allow count to be in the range [32, UINT8_MAX]. + mpack_assert(count > 31); + mpack_store_u8(p, 0xda); + mpack_store_u16(p + 1, count); +} + +MPACK_STATIC_INLINE void mpack_encode_str32(char* p, uint32_t count) { + mpack_assert(count > UINT16_MAX); + mpack_store_u8(p, 0xdb); + mpack_store_u32(p + 1, count); +} + +MPACK_STATIC_INLINE void mpack_encode_bin8(char* p, uint8_t count) { + mpack_store_u8(p, 0xc4); + mpack_store_u8(p + 1, count); +} + +MPACK_STATIC_INLINE void mpack_encode_bin16(char* p, uint16_t count) { + mpack_assert(count > UINT8_MAX); + mpack_store_u8(p, 0xc5); + mpack_store_u16(p + 1, count); +} + +MPACK_STATIC_INLINE void mpack_encode_bin32(char* p, uint32_t count) { + mpack_assert(count > UINT16_MAX); + mpack_store_u8(p, 0xc6); + mpack_store_u32(p + 1, count); +} + +#if MPACK_EXTENSIONS +MPACK_STATIC_INLINE void mpack_encode_fixext1(char* p, int8_t exttype) { + mpack_store_u8(p, 0xd4); + mpack_store_i8(p + 1, exttype); +} + +MPACK_STATIC_INLINE void mpack_encode_fixext2(char* p, int8_t exttype) { + mpack_store_u8(p, 0xd5); + mpack_store_i8(p + 1, exttype); +} + +MPACK_STATIC_INLINE void mpack_encode_fixext4(char* p, int8_t exttype) { + mpack_store_u8(p, 0xd6); + mpack_store_i8(p + 1, exttype); +} + +MPACK_STATIC_INLINE void mpack_encode_fixext8(char* p, int8_t exttype) { + mpack_store_u8(p, 0xd7); + mpack_store_i8(p + 1, exttype); +} + +MPACK_STATIC_INLINE void mpack_encode_fixext16(char* p, int8_t exttype) { + mpack_store_u8(p, 0xd8); + mpack_store_i8(p + 1, exttype); +} + +MPACK_STATIC_INLINE void mpack_encode_ext8(char* p, int8_t exttype, uint8_t count) { + mpack_assert(count != 1 && count != 2 && count != 4 && count != 8 && count != 16); + mpack_store_u8(p, 0xc7); + mpack_store_u8(p + 1, count); + mpack_store_i8(p + 2, exttype); +} + +MPACK_STATIC_INLINE void mpack_encode_ext16(char* p, int8_t exttype, uint16_t count) { + mpack_assert(count > UINT8_MAX); + mpack_store_u8(p, 0xc8); + mpack_store_u16(p + 1, count); + mpack_store_i8(p + 3, exttype); +} + +MPACK_STATIC_INLINE void mpack_encode_ext32(char* p, int8_t exttype, uint32_t count) { + mpack_assert(count > UINT16_MAX); + mpack_store_u8(p, 0xc9); + mpack_store_u32(p + 1, count); + mpack_store_i8(p + 5, exttype); +} + +MPACK_STATIC_INLINE void mpack_encode_timestamp_4(char* p, uint32_t seconds) { + mpack_encode_fixext4(p, MPACK_EXTTYPE_TIMESTAMP); + mpack_store_u32(p + MPACK_TAG_SIZE_FIXEXT4, seconds); +} + +MPACK_STATIC_INLINE void mpack_encode_timestamp_8(char* p, int64_t seconds, uint32_t nanoseconds) { + mpack_assert(nanoseconds <= MPACK_TIMESTAMP_NANOSECONDS_MAX); + mpack_encode_fixext8(p, MPACK_EXTTYPE_TIMESTAMP); + uint64_t encoded = ((uint64_t)nanoseconds << 34) | (uint64_t)seconds; + mpack_store_u64(p + MPACK_TAG_SIZE_FIXEXT8, encoded); +} + +MPACK_STATIC_INLINE void mpack_encode_timestamp_12(char* p, int64_t seconds, uint32_t nanoseconds) { + mpack_assert(nanoseconds <= MPACK_TIMESTAMP_NANOSECONDS_MAX); + mpack_encode_ext8(p, MPACK_EXTTYPE_TIMESTAMP, 12); + mpack_store_u32(p + MPACK_TAG_SIZE_EXT8, nanoseconds); + mpack_store_i64(p + MPACK_TAG_SIZE_EXT8 + 4, seconds); +} +#endif + + + +/* + * Write functions + */ + +// This is a macro wrapper to the encode functions to encode +// directly into the buffer. If mpack_writer_ensure() fails +// it will flag an error so we don't have to do anything. +#define MPACK_WRITE_ENCODED(encode_fn, size, ...) do { \ + if (MPACK_LIKELY(mpack_writer_buffer_left(writer) >= size) || mpack_writer_ensure(writer, size)) { \ + MPACK_EXPAND(encode_fn(writer->current, __VA_ARGS__)); \ + writer->current += size; \ + } \ +} while (0) + +void mpack_write_u8(mpack_writer_t* writer, uint8_t value) { + #if MPACK_OPTIMIZE_FOR_SIZE + mpack_write_u64(writer, value); + #else + mpack_writer_track_element(writer); + if (value <= 127) { + MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, value); + } else { + MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, value); + } + #endif +} + +void mpack_write_u16(mpack_writer_t* writer, uint16_t value) { + #if MPACK_OPTIMIZE_FOR_SIZE + mpack_write_u64(writer, value); + #else + mpack_writer_track_element(writer); + if (value <= 127) { + MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, (uint8_t)value); + } else if (value <= UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value); + } else { + MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, value); + } + #endif +} + +void mpack_write_u32(mpack_writer_t* writer, uint32_t value) { + #if MPACK_OPTIMIZE_FOR_SIZE + mpack_write_u64(writer, value); + #else + mpack_writer_track_element(writer); + if (value <= 127) { + MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, (uint8_t)value); + } else if (value <= UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value); + } else if (value <= UINT16_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value); + } else { + MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, value); + } + #endif +} + +void mpack_write_u64(mpack_writer_t* writer, uint64_t value) { + mpack_writer_track_element(writer); + + if (value <= 127) { + MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, (uint8_t)value); + } else if (value <= UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value); + } else if (value <= UINT16_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value); + } else if (value <= UINT32_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, (uint32_t)value); + } else { + MPACK_WRITE_ENCODED(mpack_encode_u64, MPACK_TAG_SIZE_U64, value); + } +} + +void mpack_write_i8(mpack_writer_t* writer, int8_t value) { + #if MPACK_OPTIMIZE_FOR_SIZE + mpack_write_i64(writer, value); + #else + mpack_writer_track_element(writer); + if (value >= -32) { + // we encode positive and negative fixints together + MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value); + } else { + MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value); + } + #endif +} + +void mpack_write_i16(mpack_writer_t* writer, int16_t value) { + #if MPACK_OPTIMIZE_FOR_SIZE + mpack_write_i64(writer, value); + #else + mpack_writer_track_element(writer); + if (value >= -32) { + if (value <= 127) { + // we encode positive and negative fixints together + MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value); + } else if (value <= UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value); + } else { + MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value); + } + } else if (value >= INT8_MIN) { + MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value); + } else { + MPACK_WRITE_ENCODED(mpack_encode_i16, MPACK_TAG_SIZE_I16, (int16_t)value); + } + #endif +} + +void mpack_write_i32(mpack_writer_t* writer, int32_t value) { + #if MPACK_OPTIMIZE_FOR_SIZE + mpack_write_i64(writer, value); + #else + mpack_writer_track_element(writer); + if (value >= -32) { + if (value <= 127) { + // we encode positive and negative fixints together + MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value); + } else if (value <= UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value); + } else if (value <= UINT16_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value); + } else { + MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, (uint32_t)value); + } + } else if (value >= INT8_MIN) { + MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value); + } else if (value >= INT16_MIN) { + MPACK_WRITE_ENCODED(mpack_encode_i16, MPACK_TAG_SIZE_I16, (int16_t)value); + } else { + MPACK_WRITE_ENCODED(mpack_encode_i32, MPACK_TAG_SIZE_I32, value); + } + #endif +} + +void mpack_write_i64(mpack_writer_t* writer, int64_t value) { + #if MPACK_OPTIMIZE_FOR_SIZE + if (value > 127) { + // for non-fix positive ints we call the u64 writer to save space + mpack_write_u64(writer, (uint64_t)value); + return; + } + #endif + + mpack_writer_track_element(writer); + if (value >= -32) { + #if MPACK_OPTIMIZE_FOR_SIZE + MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value); + #else + if (value <= 127) { + MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value); + } else if (value <= UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value); + } else if (value <= UINT16_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value); + } else if (value <= UINT32_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, (uint32_t)value); + } else { + MPACK_WRITE_ENCODED(mpack_encode_u64, MPACK_TAG_SIZE_U64, (uint64_t)value); + } + #endif + } else if (value >= INT8_MIN) { + MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value); + } else if (value >= INT16_MIN) { + MPACK_WRITE_ENCODED(mpack_encode_i16, MPACK_TAG_SIZE_I16, (int16_t)value); + } else if (value >= INT32_MIN) { + MPACK_WRITE_ENCODED(mpack_encode_i32, MPACK_TAG_SIZE_I32, (int32_t)value); + } else { + MPACK_WRITE_ENCODED(mpack_encode_i64, MPACK_TAG_SIZE_I64, value); + } +} + +void mpack_write_float(mpack_writer_t* writer, float value) { + mpack_writer_track_element(writer); + MPACK_WRITE_ENCODED(mpack_encode_float, MPACK_TAG_SIZE_FLOAT, value); +} + +void mpack_write_double(mpack_writer_t* writer, double value) { + mpack_writer_track_element(writer); + MPACK_WRITE_ENCODED(mpack_encode_double, MPACK_TAG_SIZE_DOUBLE, value); +} + +#if MPACK_EXTENSIONS +void mpack_write_timestamp(mpack_writer_t* writer, int64_t seconds, uint32_t nanoseconds) { + #if MPACK_COMPATIBILITY + if (writer->version <= mpack_version_v4) { + mpack_break("Timestamps require spec version v5 or later. This writer is in v%i mode.", (int)writer->version); + mpack_writer_flag_error(writer, mpack_error_bug); + return; + } + #endif + + if (nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) { + mpack_break("timestamp nanoseconds out of bounds: %u", nanoseconds); + mpack_writer_flag_error(writer, mpack_error_bug); + return; + } + + mpack_writer_track_element(writer); + + if (seconds < 0 || seconds >= (INT64_C(1) << 34)) { + MPACK_WRITE_ENCODED(mpack_encode_timestamp_12, MPACK_EXT_SIZE_TIMESTAMP12, seconds, nanoseconds); + } else if (seconds > UINT32_MAX || nanoseconds > 0) { + MPACK_WRITE_ENCODED(mpack_encode_timestamp_8, MPACK_EXT_SIZE_TIMESTAMP8, seconds, nanoseconds); + } else { + MPACK_WRITE_ENCODED(mpack_encode_timestamp_4, MPACK_EXT_SIZE_TIMESTAMP4, (uint32_t)seconds); + } +} +#endif + +void mpack_start_array(mpack_writer_t* writer, uint32_t count) { + mpack_writer_track_element(writer); + + if (count <= 15) { + MPACK_WRITE_ENCODED(mpack_encode_fixarray, MPACK_TAG_SIZE_FIXARRAY, (uint8_t)count); + } else if (count <= UINT16_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_array16, MPACK_TAG_SIZE_ARRAY16, (uint16_t)count); + } else { + MPACK_WRITE_ENCODED(mpack_encode_array32, MPACK_TAG_SIZE_ARRAY32, (uint32_t)count); + } + + mpack_writer_track_push(writer, mpack_type_array, count); +} + +void mpack_start_map(mpack_writer_t* writer, uint32_t count) { + mpack_writer_track_element(writer); + + if (count <= 15) { + MPACK_WRITE_ENCODED(mpack_encode_fixmap, MPACK_TAG_SIZE_FIXMAP, (uint8_t)count); + } else if (count <= UINT16_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_map16, MPACK_TAG_SIZE_MAP16, (uint16_t)count); + } else { + MPACK_WRITE_ENCODED(mpack_encode_map32, MPACK_TAG_SIZE_MAP32, (uint32_t)count); + } + + mpack_writer_track_push(writer, mpack_type_map, count); +} + +static void mpack_start_str_notrack(mpack_writer_t* writer, uint32_t count) { + if (count <= 31) { + MPACK_WRITE_ENCODED(mpack_encode_fixstr, MPACK_TAG_SIZE_FIXSTR, (uint8_t)count); + + // str8 is only supported in v5 or later. + } else if (count <= UINT8_MAX + #if MPACK_COMPATIBILITY + && writer->version >= mpack_version_v5 + #endif + ) { + MPACK_WRITE_ENCODED(mpack_encode_str8, MPACK_TAG_SIZE_STR8, (uint8_t)count); + + } else if (count <= UINT16_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_str16, MPACK_TAG_SIZE_STR16, (uint16_t)count); + } else { + MPACK_WRITE_ENCODED(mpack_encode_str32, MPACK_TAG_SIZE_STR32, (uint32_t)count); + } +} + +static void mpack_start_bin_notrack(mpack_writer_t* writer, uint32_t count) { + #if MPACK_COMPATIBILITY + // In the v4 spec, there was only the raw type for any kind of + // variable-length data. In v4 mode, we support the bin functions, + // but we produce an old-style raw. + if (writer->version <= mpack_version_v4) { + mpack_start_str_notrack(writer, count); + return; + } + #endif + + if (count <= UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_bin8, MPACK_TAG_SIZE_BIN8, (uint8_t)count); + } else if (count <= UINT16_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_bin16, MPACK_TAG_SIZE_BIN16, (uint16_t)count); + } else { + MPACK_WRITE_ENCODED(mpack_encode_bin32, MPACK_TAG_SIZE_BIN32, (uint32_t)count); + } +} + +void mpack_start_str(mpack_writer_t* writer, uint32_t count) { + mpack_writer_track_element(writer); + mpack_start_str_notrack(writer, count); + mpack_writer_track_push(writer, mpack_type_str, count); +} + +void mpack_start_bin(mpack_writer_t* writer, uint32_t count) { + mpack_writer_track_element(writer); + mpack_start_bin_notrack(writer, count); + mpack_writer_track_push(writer, mpack_type_bin, count); +} + +#if MPACK_EXTENSIONS +void mpack_start_ext(mpack_writer_t* writer, int8_t exttype, uint32_t count) { + #if MPACK_COMPATIBILITY + if (writer->version <= mpack_version_v4) { + mpack_break("Ext types require spec version v5 or later. This writer is in v%i mode.", (int)writer->version); + mpack_writer_flag_error(writer, mpack_error_bug); + return; + } + #endif + + mpack_writer_track_element(writer); + + if (count == 1) { + MPACK_WRITE_ENCODED(mpack_encode_fixext1, MPACK_TAG_SIZE_FIXEXT1, exttype); + } else if (count == 2) { + MPACK_WRITE_ENCODED(mpack_encode_fixext2, MPACK_TAG_SIZE_FIXEXT2, exttype); + } else if (count == 4) { + MPACK_WRITE_ENCODED(mpack_encode_fixext4, MPACK_TAG_SIZE_FIXEXT4, exttype); + } else if (count == 8) { + MPACK_WRITE_ENCODED(mpack_encode_fixext8, MPACK_TAG_SIZE_FIXEXT8, exttype); + } else if (count == 16) { + MPACK_WRITE_ENCODED(mpack_encode_fixext16, MPACK_TAG_SIZE_FIXEXT16, exttype); + } else if (count <= UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_ext8, MPACK_TAG_SIZE_EXT8, exttype, (uint8_t)count); + } else if (count <= UINT16_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_ext16, MPACK_TAG_SIZE_EXT16, exttype, (uint16_t)count); + } else { + MPACK_WRITE_ENCODED(mpack_encode_ext32, MPACK_TAG_SIZE_EXT32, exttype, (uint32_t)count); + } + + mpack_writer_track_push(writer, mpack_type_ext, count); +} +#endif + + + +/* + * Compound helpers and other functions + */ + +void mpack_write_str(mpack_writer_t* writer, const char* data, uint32_t count) { + mpack_assert(data != NULL, "data for string of length %i is NULL", (int)count); + + #if MPACK_OPTIMIZE_FOR_SIZE + mpack_writer_track_element(writer); + mpack_start_str_notrack(writer, count); + mpack_write_native(writer, data, count); + #else + + mpack_writer_track_element(writer); + + if (count <= 31) { + // The minimum buffer size when using a flush function is guaranteed to + // fit the largest possible fixstr. + size_t size = count + MPACK_TAG_SIZE_FIXSTR; + if (MPACK_LIKELY(mpack_writer_buffer_left(writer) >= size) || mpack_writer_ensure(writer, size)) { + char* MPACK_RESTRICT p = writer->current; + mpack_encode_fixstr(p, (uint8_t)count); + mpack_memcpy(p + MPACK_TAG_SIZE_FIXSTR, data, count); + writer->current += count + MPACK_TAG_SIZE_FIXSTR; + } + return; + } + + if (count <= UINT8_MAX + #if MPACK_COMPATIBILITY + && writer->version >= mpack_version_v5 + #endif + ) { + if (count + MPACK_TAG_SIZE_STR8 <= mpack_writer_buffer_left(writer)) { + char* MPACK_RESTRICT p = writer->current; + mpack_encode_str8(p, (uint8_t)count); + mpack_memcpy(p + MPACK_TAG_SIZE_STR8, data, count); + writer->current += count + MPACK_TAG_SIZE_STR8; + } else { + MPACK_WRITE_ENCODED(mpack_encode_str8, MPACK_TAG_SIZE_STR8, (uint8_t)count); + mpack_write_native(writer, data, count); + } + return; + } + + // str16 and str32 are likely to be a significant fraction of the buffer + // size, so we don't bother with a combined space check in order to + // minimize code size. + if (count <= UINT16_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_str16, MPACK_TAG_SIZE_STR16, (uint16_t)count); + mpack_write_native(writer, data, count); + } else { + MPACK_WRITE_ENCODED(mpack_encode_str32, MPACK_TAG_SIZE_STR32, (uint32_t)count); + mpack_write_native(writer, data, count); + } + + #endif +} + +void mpack_write_bin(mpack_writer_t* writer, const char* data, uint32_t count) { + mpack_assert(data != NULL, "data pointer for bin of %i bytes is NULL", (int)count); + mpack_start_bin(writer, count); + mpack_write_bytes(writer, data, count); + mpack_finish_bin(writer); +} + +#if MPACK_EXTENSIONS +void mpack_write_ext(mpack_writer_t* writer, int8_t exttype, const char* data, uint32_t count) { + mpack_assert(data != NULL, "data pointer for ext of type %i and %i bytes is NULL", exttype, (int)count); + mpack_start_ext(writer, exttype, count); + mpack_write_bytes(writer, data, count); + mpack_finish_ext(writer); +} +#endif + +void mpack_write_bytes(mpack_writer_t* writer, const char* data, size_t count) { + mpack_assert(data != NULL, "data pointer for %i bytes is NULL", (int)count); + mpack_writer_track_bytes(writer, count); + mpack_write_native(writer, data, count); +} + +void mpack_write_cstr(mpack_writer_t* writer, const char* cstr) { + mpack_assert(cstr != NULL, "cstr pointer is NULL"); + size_t length = mpack_strlen(cstr); + if (length > UINT32_MAX) + mpack_writer_flag_error(writer, mpack_error_invalid); + mpack_write_str(writer, cstr, (uint32_t)length); +} + +void mpack_write_cstr_or_nil(mpack_writer_t* writer, const char* cstr) { + if (cstr) + mpack_write_cstr(writer, cstr); + else + mpack_write_nil(writer); +} + +void mpack_write_utf8(mpack_writer_t* writer, const char* str, uint32_t length) { + mpack_assert(str != NULL, "data for string of length %i is NULL", (int)length); + if (!mpack_utf8_check(str, length)) { + mpack_writer_flag_error(writer, mpack_error_invalid); + return; + } + mpack_write_str(writer, str, length); +} + +void mpack_write_utf8_cstr(mpack_writer_t* writer, const char* cstr) { + mpack_assert(cstr != NULL, "cstr pointer is NULL"); + size_t length = mpack_strlen(cstr); + if (length > UINT32_MAX) { + mpack_writer_flag_error(writer, mpack_error_invalid); + return; + } + mpack_write_utf8(writer, cstr, (uint32_t)length); +} + +void mpack_write_utf8_cstr_or_nil(mpack_writer_t* writer, const char* cstr) { + if (cstr) + mpack_write_utf8_cstr(writer, cstr); + else + mpack_write_nil(writer); +} + +#endif + + +/* mpack/mpack-reader.c.c */ + +#define MPACK_INTERNAL 1 + +/* #include "mpack-reader.h" */ + +#if MPACK_READER + +static void mpack_reader_skip_using_fill(mpack_reader_t* reader, size_t count); + +void mpack_reader_init(mpack_reader_t* reader, char* buffer, size_t size, size_t count) { + mpack_assert(buffer != NULL, "buffer is NULL"); + + mpack_memset(reader, 0, sizeof(*reader)); + reader->buffer = buffer; + reader->size = size; + reader->data = buffer; + reader->end = buffer + count; + + #if MPACK_READ_TRACKING + mpack_reader_flag_if_error(reader, mpack_track_init(&reader->track)); + #endif + + mpack_log("===========================\n"); + mpack_log("initializing reader with buffer size %i\n", (int)size); +} + +void mpack_reader_init_error(mpack_reader_t* reader, mpack_error_t error) { + mpack_memset(reader, 0, sizeof(*reader)); + reader->error = error; + + mpack_log("===========================\n"); + mpack_log("initializing reader error state %i\n", (int)error); +} + +void mpack_reader_init_data(mpack_reader_t* reader, const char* data, size_t count) { + mpack_assert(data != NULL, "data is NULL"); + + mpack_memset(reader, 0, sizeof(*reader)); + reader->data = data; + reader->end = data + count; + + #if MPACK_READ_TRACKING + mpack_reader_flag_if_error(reader, mpack_track_init(&reader->track)); + #endif + + mpack_log("===========================\n"); + mpack_log("initializing reader with data size %i\n", (int)count); +} + +void mpack_reader_set_fill(mpack_reader_t* reader, mpack_reader_fill_t fill) { + MPACK_STATIC_ASSERT(MPACK_READER_MINIMUM_BUFFER_SIZE >= MPACK_MAXIMUM_TAG_SIZE, + "minimum buffer size must fit any tag!"); + + if (reader->size == 0) { + mpack_break("cannot use fill function without a writeable buffer!"); + mpack_reader_flag_error(reader, mpack_error_bug); + return; + } + + if (reader->size < MPACK_READER_MINIMUM_BUFFER_SIZE) { + mpack_break("buffer size is %i, but minimum buffer size for fill is %i", + (int)reader->size, MPACK_READER_MINIMUM_BUFFER_SIZE); + mpack_reader_flag_error(reader, mpack_error_bug); + return; + } + + reader->fill = fill; +} + +void mpack_reader_set_skip(mpack_reader_t* reader, mpack_reader_skip_t skip) { + mpack_assert(reader->size != 0, "cannot use skip function without a writeable buffer!"); + reader->skip = skip; +} + +#if MPACK_STDIO +static size_t mpack_file_reader_fill(mpack_reader_t* reader, char* buffer, size_t count) { + if (feof((FILE *)reader->context)) { + mpack_reader_flag_error(reader, mpack_error_eof); + return 0; + } + return fread((void*)buffer, 1, count, (FILE*)reader->context); +} + +static void mpack_file_reader_skip(mpack_reader_t* reader, size_t count) { + if (mpack_reader_error(reader) != mpack_ok) + return; + FILE* file = (FILE*)reader->context; + + // We call ftell() to test whether the stream is seekable + // without causing a file error. + if (ftell(file) >= 0) { + mpack_log("seeking forward %i bytes\n", (int)count); + if (fseek(file, (long int)count, SEEK_CUR) == 0) + return; + mpack_log("fseek() didn't return zero!\n"); + if (ferror(file)) { + mpack_reader_flag_error(reader, mpack_error_io); + return; + } + } + + // If the stream is not seekable, fall back to the fill function. + mpack_reader_skip_using_fill(reader, count); +} + +static void mpack_file_reader_teardown(mpack_reader_t* reader) { + MPACK_FREE(reader->buffer); + reader->buffer = NULL; + reader->context = NULL; + reader->size = 0; + reader->fill = NULL; + reader->skip = NULL; + reader->teardown = NULL; +} + +static void mpack_file_reader_teardown_close(mpack_reader_t* reader) { + FILE* file = (FILE*)reader->context; + + if (file) { + int ret = fclose(file); + if (ret != 0) + mpack_reader_flag_error(reader, mpack_error_io); + } + + mpack_file_reader_teardown(reader); +} + +void mpack_reader_init_stdfile(mpack_reader_t* reader, FILE* file, bool close_when_done) { + mpack_assert(file != NULL, "file is NULL"); + + size_t capacity = MPACK_BUFFER_SIZE; + char* buffer = (char*)MPACK_MALLOC(capacity); + if (buffer == NULL) { + mpack_reader_init_error(reader, mpack_error_memory); + if (close_when_done) { + fclose(file); + } + return; + } + + mpack_reader_init(reader, buffer, capacity, 0); + mpack_reader_set_context(reader, file); + mpack_reader_set_fill(reader, mpack_file_reader_fill); + mpack_reader_set_skip(reader, mpack_file_reader_skip); + mpack_reader_set_teardown(reader, close_when_done ? + mpack_file_reader_teardown_close : + mpack_file_reader_teardown); +} + +void mpack_reader_init_filename(mpack_reader_t* reader, const char* filename) { + mpack_assert(filename != NULL, "filename is NULL"); + + FILE* file = fopen(filename, "rb"); + if (file == NULL) { + mpack_reader_init_error(reader, mpack_error_io); + return; + } + + mpack_reader_init_stdfile(reader, file, true); +} +#endif + +mpack_error_t mpack_reader_destroy(mpack_reader_t* reader) { + + // clean up tracking, asserting if we're not already in an error state + #if MPACK_READ_TRACKING + mpack_reader_flag_if_error(reader, mpack_track_destroy(&reader->track, mpack_reader_error(reader) != mpack_ok)); + #endif + + if (reader->teardown) + reader->teardown(reader); + reader->teardown = NULL; + + return reader->error; +} + +size_t mpack_reader_remaining(mpack_reader_t* reader, const char** data) { + if (mpack_reader_error(reader) != mpack_ok) + return 0; + + #if MPACK_READ_TRACKING + if (mpack_reader_flag_if_error(reader, mpack_track_check_empty(&reader->track)) != mpack_ok) + return 0; + #endif + + if (data) + *data = reader->data; + return (size_t)(reader->end - reader->data); +} + +void mpack_reader_flag_error(mpack_reader_t* reader, mpack_error_t error) { + mpack_log("reader %p setting error %i: %s\n", reader, (int)error, mpack_error_to_string(error)); + + if (reader->error == mpack_ok) { + reader->error = error; + reader->end = reader->data; + if (reader->error_fn) + reader->error_fn(reader, error); + } +} + +// Loops on the fill function, reading between the minimum and +// maximum number of bytes and flagging an error if it fails. +MPACK_NOINLINE static size_t mpack_fill_range(mpack_reader_t* reader, char* p, size_t min_bytes, size_t max_bytes) { + mpack_assert(reader->fill != NULL, "mpack_fill_range() called with no fill function?"); + mpack_assert(min_bytes > 0, "cannot fill zero bytes!"); + mpack_assert(max_bytes >= min_bytes, "min_bytes %i cannot be larger than max_bytes %i!", + (int)min_bytes, (int)max_bytes); + + size_t count = 0; + while (count < min_bytes) { + size_t read = reader->fill(reader, p + count, max_bytes - count); + + // Reader fill functions can flag an error or return 0 on failure. We + // also guard against functions that -1 just in case. + if (mpack_reader_error(reader) != mpack_ok) + return 0; + if (read == 0 || read == ((size_t)(-1))) { + mpack_reader_flag_error(reader, mpack_error_io); + return 0; + } + + count += read; + } + return count; +} + +MPACK_NOINLINE bool mpack_reader_ensure_straddle(mpack_reader_t* reader, size_t count) { + mpack_assert(count != 0, "cannot ensure zero bytes!"); + mpack_assert(reader->error == mpack_ok, "reader cannot be in an error state!"); + + mpack_assert(count > (size_t)(reader->end - reader->data), + "straddling ensure requested for %i bytes, but there are %i bytes " + "left in buffer. call mpack_reader_ensure() instead", + (int)count, (int)(reader->end - reader->data)); + + // we'll need a fill function to get more data. if there's no + // fill function, the buffer should contain an entire MessagePack + // object, so we raise mpack_error_invalid instead of mpack_error_io + // on truncated data. + if (reader->fill == NULL) { + mpack_reader_flag_error(reader, mpack_error_invalid); + return false; + } + + // we need enough space in the buffer. if the buffer is not + // big enough, we return mpack_error_too_big (since this is + // for an in-place read larger than the buffer size.) + if (count > reader->size) { + mpack_reader_flag_error(reader, mpack_error_too_big); + return false; + } + + // move the existing data to the start of the buffer + size_t left = (size_t)(reader->end - reader->data); + mpack_memmove(reader->buffer, reader->data, left); + reader->end -= reader->data - reader->buffer; + reader->data = reader->buffer; + + // read at least the necessary number of bytes, accepting up to the + // buffer size + size_t read = mpack_fill_range(reader, reader->buffer + left, + count - left, reader->size - left); + if (mpack_reader_error(reader) != mpack_ok) + return false; + reader->end += read; + return true; +} + +// Reads count bytes into p. Used when there are not enough bytes +// left in the buffer to satisfy a read. +MPACK_NOINLINE void mpack_read_native_straddle(mpack_reader_t* reader, char* p, size_t count) { + mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count); + + if (mpack_reader_error(reader) != mpack_ok) { + mpack_memset(p, 0, count); + return; + } + + size_t left = (size_t)(reader->end - reader->data); + mpack_log("big read for %i bytes into %p, %i left in buffer, buffer size %i\n", + (int)count, p, (int)left, (int)reader->size); + + if (count <= left) { + mpack_assert(0, + "big read requested for %i bytes, but there are %i bytes " + "left in buffer. call mpack_read_native() instead", + (int)count, (int)left); + mpack_reader_flag_error(reader, mpack_error_bug); + mpack_memset(p, 0, count); + return; + } + + // we'll need a fill function to get more data. if there's no + // fill function, the buffer should contain an entire MessagePack + // object, so we raise mpack_error_invalid instead of mpack_error_io + // on truncated data. + if (reader->fill == NULL) { + mpack_reader_flag_error(reader, mpack_error_invalid); + mpack_memset(p, 0, count); + return; + } + + if (reader->size == 0) { + // somewhat debatable what error should be returned here. when + // initializing a reader with an in-memory buffer it's not + // necessarily a bug if the data is blank; it might just have + // been truncated to zero. for this reason we return the same + // error as if the data was truncated. + mpack_reader_flag_error(reader, mpack_error_io); + mpack_memset(p, 0, count); + return; + } + + // flush what's left of the buffer + if (left > 0) { + mpack_log("flushing %i bytes remaining in buffer\n", (int)left); + mpack_memcpy(p, reader->data, left); + count -= left; + p += left; + reader->data += left; + } + + // if the remaining data needed is some small fraction of the + // buffer size, we'll try to fill the buffer as much as possible + // and copy the needed data out. + if (count <= reader->size / MPACK_READER_SMALL_FRACTION_DENOMINATOR) { + size_t read = mpack_fill_range(reader, reader->buffer, count, reader->size); + if (mpack_reader_error(reader) != mpack_ok) + return; + mpack_memcpy(p, reader->buffer, count); + reader->data = reader->buffer + count; + reader->end = reader->buffer + read; + + // otherwise we read the remaining data directly into the target. + } else { + mpack_log("reading %i additional bytes\n", (int)count); + mpack_fill_range(reader, p, count, count); + } +} + +MPACK_NOINLINE static void mpack_skip_bytes_straddle(mpack_reader_t* reader, size_t count) { + + // we'll need at least a fill function to skip more data. if there's + // no fill function, the buffer should contain an entire MessagePack + // object, so we raise mpack_error_invalid instead of mpack_error_io + // on truncated data. (see mpack_read_native_straddle()) + if (reader->fill == NULL) { + mpack_log("reader has no fill function!\n"); + mpack_reader_flag_error(reader, mpack_error_invalid); + return; + } + + // discard whatever's left in the buffer + size_t left = (size_t)(reader->end - reader->data); + mpack_log("discarding %i bytes still in buffer\n", (int)left); + count -= left; + reader->data = reader->end; + + // use the skip function if we've got one, and if we're trying + // to skip a lot of data. if we only need to skip some tiny + // fraction of the buffer size, it's probably better to just + // fill the buffer and skip from it instead of trying to seek. + if (reader->skip && count > reader->size / 16) { + mpack_log("calling skip function for %i bytes\n", (int)count); + reader->skip(reader, count); + return; + } + + mpack_reader_skip_using_fill(reader, count); +} + +void mpack_skip_bytes(mpack_reader_t* reader, size_t count) { + if (mpack_reader_error(reader) != mpack_ok) + return; + mpack_log("skip requested for %i bytes\n", (int)count); + mpack_reader_track_bytes(reader, count); + + // check if we have enough in the buffer already + size_t left = (size_t)(reader->end - reader->data); + if (left >= count) { + mpack_log("skipping %i bytes still in buffer\n", (int)count); + reader->data += count; + return; + } + + mpack_skip_bytes_straddle(reader, count); +} + +MPACK_NOINLINE static void mpack_reader_skip_using_fill(mpack_reader_t* reader, size_t count) { + mpack_assert(reader->fill != NULL, "missing fill function!"); + mpack_assert(reader->data == reader->end, "there are bytes left in the buffer!"); + mpack_assert(reader->error == mpack_ok, "should not have called this in an error state (%i)", reader->error); + mpack_log("skip using fill for %i bytes\n", (int)count); + + // fill and discard multiples of the buffer size + while (count > reader->size) { + mpack_log("filling and discarding buffer of %i bytes\n", (int)reader->size); + if (mpack_fill_range(reader, reader->buffer, reader->size, reader->size) < reader->size) { + mpack_reader_flag_error(reader, mpack_error_io); + return; + } + count -= reader->size; + } + + // fill the buffer as much as possible + reader->data = reader->buffer; + size_t read = mpack_fill_range(reader, reader->buffer, count, reader->size); + if (read < count) { + mpack_reader_flag_error(reader, mpack_error_io); + return; + } + reader->end = reader->data + read; + mpack_log("filled %i bytes into buffer; discarding %i bytes\n", (int)read, (int)count); + reader->data += count; +} + +void mpack_read_bytes(mpack_reader_t* reader, char* p, size_t count) { + mpack_assert(p != NULL, "destination for read of %i bytes is NULL", (int)count); + mpack_reader_track_bytes(reader, count); + mpack_read_native(reader, p, count); +} + +void mpack_read_utf8(mpack_reader_t* reader, char* p, size_t byte_count) { + mpack_assert(p != NULL, "destination for read of %i bytes is NULL", (int)byte_count); + mpack_reader_track_str_bytes_all(reader, byte_count); + mpack_read_native(reader, p, byte_count); + + if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check(p, byte_count)) + mpack_reader_flag_error(reader, mpack_error_type); +} + +static void mpack_read_cstr_unchecked(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) { + mpack_assert(buf != NULL, "destination for read of %i bytes is NULL", (int)byte_count); + mpack_assert(buffer_size >= 1, "buffer size is zero; you must have room for at least a null-terminator"); + + if (mpack_reader_error(reader)) { + buf[0] = 0; + return; + } + + if (byte_count > buffer_size - 1) { + mpack_reader_flag_error(reader, mpack_error_too_big); + buf[0] = 0; + return; + } + + mpack_reader_track_str_bytes_all(reader, byte_count); + mpack_read_native(reader, buf, byte_count); + buf[byte_count] = 0; +} + +void mpack_read_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) { + mpack_read_cstr_unchecked(reader, buf, buffer_size, byte_count); + + // check for null bytes + if (mpack_reader_error(reader) == mpack_ok && !mpack_str_check_no_null(buf, byte_count)) { + buf[0] = 0; + mpack_reader_flag_error(reader, mpack_error_type); + } +} + +void mpack_read_utf8_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) { + mpack_read_cstr_unchecked(reader, buf, buffer_size, byte_count); + + // check encoding + if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check_no_null(buf, byte_count)) { + buf[0] = 0; + mpack_reader_flag_error(reader, mpack_error_type); + } +} + +#ifdef MPACK_MALLOC +// Reads native bytes with error callback disabled. This allows MPack reader functions +// to hold an allocated buffer and read native data into it without leaking it in +// case of a non-local jump (longjmp, throw) out of an error handler. +static void mpack_read_native_noerrorfn(mpack_reader_t* reader, char* p, size_t count) { + mpack_assert(reader->error == mpack_ok, "cannot call if an error is already flagged!"); + mpack_reader_error_t error_fn = reader->error_fn; + reader->error_fn = NULL; + mpack_read_native(reader, p, count); + reader->error_fn = error_fn; +} + +char* mpack_read_bytes_alloc_impl(mpack_reader_t* reader, size_t count, bool null_terminated) { + + // track the bytes first in case it jumps + mpack_reader_track_bytes(reader, count); + if (mpack_reader_error(reader) != mpack_ok) + return NULL; + + // cannot allocate zero bytes. this is not an error. + if (count == 0 && null_terminated == false) + return NULL; + + // allocate data + char* data = (char*)MPACK_MALLOC(count + (null_terminated ? 1 : 0)); // TODO: can this overflow? + if (data == NULL) { + mpack_reader_flag_error(reader, mpack_error_memory); + return NULL; + } + + // read with error callback disabled so we don't leak our buffer + mpack_read_native_noerrorfn(reader, data, count); + + // report flagged errors + if (mpack_reader_error(reader) != mpack_ok) { + MPACK_FREE(data); + if (reader->error_fn) + reader->error_fn(reader, mpack_reader_error(reader)); + return NULL; + } + + if (null_terminated) + data[count] = '\0'; + return data; +} +#endif + +// read inplace without tracking (since there are different +// tracking modes for different inplace readers) +static const char* mpack_read_bytes_inplace_notrack(mpack_reader_t* reader, size_t count) { + if (mpack_reader_error(reader) != mpack_ok) + return NULL; + + // if we have enough bytes already in the buffer, we can return it directly. + if ((size_t)(reader->end - reader->data) >= count) { + const char* bytes = reader->data; + reader->data += count; + return bytes; + } + + if (!mpack_reader_ensure(reader, count)) + return NULL; + + const char* bytes = reader->data; + reader->data += count; + return bytes; +} + +const char* mpack_read_bytes_inplace(mpack_reader_t* reader, size_t count) { + mpack_reader_track_bytes(reader, count); + return mpack_read_bytes_inplace_notrack(reader, count); +} + +const char* mpack_read_utf8_inplace(mpack_reader_t* reader, size_t count) { + mpack_reader_track_str_bytes_all(reader, count); + const char* str = mpack_read_bytes_inplace_notrack(reader, count); + + if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check(str, count)) { + mpack_reader_flag_error(reader, mpack_error_type); + return NULL; + } + + return str; +} + +static size_t mpack_parse_tag(mpack_reader_t* reader, mpack_tag_t* tag) { + mpack_assert(reader->error == mpack_ok, "reader cannot be in an error state!"); + + if (!mpack_reader_ensure(reader, 1)) + return 0; + uint8_t type = mpack_load_u8(reader->data); + + // unfortunately, by far the fastest way to parse a tag is to switch + // on the first byte, and to explicitly list every possible byte. so for + // infix types, the list of cases is quite large. + // + // in size-optimized builds, we switch on the top four bits first to + // handle most infix types with a smaller jump table to save space. + + #if MPACK_OPTIMIZE_FOR_SIZE + switch (type >> 4) { + + // positive fixnum + case 0x0: case 0x1: case 0x2: case 0x3: + case 0x4: case 0x5: case 0x6: case 0x7: + *tag = mpack_tag_make_uint(type); + return 1; + + // negative fixnum + case 0xe: case 0xf: + *tag = mpack_tag_make_int((int8_t)type); + return 1; + + // fixmap + case 0x8: + *tag = mpack_tag_make_map(type & ~0xf0u); + return 1; + + // fixarray + case 0x9: + *tag = mpack_tag_make_array(type & ~0xf0u); + return 1; + + // fixstr + case 0xa: case 0xb: + *tag = mpack_tag_make_str(type & ~0xe0u); + return 1; + + // not one of the common infix types + default: + break; + + } + #endif + + // handle individual type tags + switch (type) { + + #if !MPACK_OPTIMIZE_FOR_SIZE + // positive fixnum + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: + case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: + case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: + case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f: + case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: + case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f: + case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: + case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f: + case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: + case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: + case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: + case 0x68: case 0x69: case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f: + case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: + case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f: + *tag = mpack_tag_make_uint(type); + return 1; + + // negative fixnum + case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7: + case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef: + case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7: + case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff: + *tag = mpack_tag_make_int((int8_t)type); + return 1; + + // fixmap + case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: + case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: + *tag = mpack_tag_make_map(type & ~0xf0u); + return 1; + + // fixarray + case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: + case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f: + *tag = mpack_tag_make_array(type & ~0xf0u); + return 1; + + // fixstr + case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7: + case 0xa8: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf: + case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7: + case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: + *tag = mpack_tag_make_str(type & ~0xe0u); + return 1; + #endif + + // nil + case 0xc0: + *tag = mpack_tag_make_nil(); + return 1; + + // bool + case 0xc2: case 0xc3: + *tag = mpack_tag_make_bool((bool)(type & 1)); + return 1; + + // bin8 + case 0xc4: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_BIN8)) + return 0; + *tag = mpack_tag_make_bin(mpack_load_u8(reader->data + 1)); + return MPACK_TAG_SIZE_BIN8; + + // bin16 + case 0xc5: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_BIN16)) + return 0; + *tag = mpack_tag_make_bin(mpack_load_u16(reader->data + 1)); + return MPACK_TAG_SIZE_BIN16; + + // bin32 + case 0xc6: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_BIN32)) + return 0; + *tag = mpack_tag_make_bin(mpack_load_u32(reader->data + 1)); + return MPACK_TAG_SIZE_BIN32; + + #if MPACK_EXTENSIONS + // ext8 + case 0xc7: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT8)) + return 0; + *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 2), mpack_load_u8(reader->data + 1)); + return MPACK_TAG_SIZE_EXT8; + + // ext16 + case 0xc8: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT16)) + return 0; + *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 3), mpack_load_u16(reader->data + 1)); + return MPACK_TAG_SIZE_EXT16; + + // ext32 + case 0xc9: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT32)) + return 0; + *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 5), mpack_load_u32(reader->data + 1)); + return MPACK_TAG_SIZE_EXT32; + #endif + + // float + case 0xca: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FLOAT)) + return 0; + *tag = mpack_tag_make_float(mpack_load_float(reader->data + 1)); + return MPACK_TAG_SIZE_FLOAT; + + // double + case 0xcb: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_DOUBLE)) + return 0; + *tag = mpack_tag_make_double(mpack_load_double(reader->data + 1)); + return MPACK_TAG_SIZE_DOUBLE; + + // uint8 + case 0xcc: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U8)) + return 0; + *tag = mpack_tag_make_uint(mpack_load_u8(reader->data + 1)); + return MPACK_TAG_SIZE_U8; + + // uint16 + case 0xcd: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U16)) + return 0; + *tag = mpack_tag_make_uint(mpack_load_u16(reader->data + 1)); + return MPACK_TAG_SIZE_U16; + + // uint32 + case 0xce: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U32)) + return 0; + *tag = mpack_tag_make_uint(mpack_load_u32(reader->data + 1)); + return MPACK_TAG_SIZE_U32; + + // uint64 + case 0xcf: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U64)) + return 0; + *tag = mpack_tag_make_uint(mpack_load_u64(reader->data + 1)); + return MPACK_TAG_SIZE_U64; + + // int8 + case 0xd0: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I8)) + return 0; + *tag = mpack_tag_make_int(mpack_load_i8(reader->data + 1)); + return MPACK_TAG_SIZE_I8; + + // int16 + case 0xd1: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I16)) + return 0; + *tag = mpack_tag_make_int(mpack_load_i16(reader->data + 1)); + return MPACK_TAG_SIZE_I16; + + // int32 + case 0xd2: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I32)) + return 0; + *tag = mpack_tag_make_int(mpack_load_i32(reader->data + 1)); + return MPACK_TAG_SIZE_I32; + + // int64 + case 0xd3: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I64)) + return 0; + *tag = mpack_tag_make_int(mpack_load_i64(reader->data + 1)); + return MPACK_TAG_SIZE_I64; + + #if MPACK_EXTENSIONS + // fixext1 + case 0xd4: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT1)) + return 0; + *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 1); + return MPACK_TAG_SIZE_FIXEXT1; + + // fixext2 + case 0xd5: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT2)) + return 0; + *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 2); + return MPACK_TAG_SIZE_FIXEXT2; + + // fixext4 + case 0xd6: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT4)) + return 0; + *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 4); + return 2; + + // fixext8 + case 0xd7: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT8)) + return 0; + *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 8); + return MPACK_TAG_SIZE_FIXEXT8; + + // fixext16 + case 0xd8: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT16)) + return 0; + *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 16); + return MPACK_TAG_SIZE_FIXEXT16; + #endif + + // str8 + case 0xd9: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_STR8)) + return 0; + *tag = mpack_tag_make_str(mpack_load_u8(reader->data + 1)); + return MPACK_TAG_SIZE_STR8; + + // str16 + case 0xda: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_STR16)) + return 0; + *tag = mpack_tag_make_str(mpack_load_u16(reader->data + 1)); + return MPACK_TAG_SIZE_STR16; + + // str32 + case 0xdb: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_STR32)) + return 0; + *tag = mpack_tag_make_str(mpack_load_u32(reader->data + 1)); + return MPACK_TAG_SIZE_STR32; + + // array16 + case 0xdc: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_ARRAY16)) + return 0; + *tag = mpack_tag_make_array(mpack_load_u16(reader->data + 1)); + return MPACK_TAG_SIZE_ARRAY16; + + // array32 + case 0xdd: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_ARRAY32)) + return 0; + *tag = mpack_tag_make_array(mpack_load_u32(reader->data + 1)); + return MPACK_TAG_SIZE_ARRAY32; + + // map16 + case 0xde: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_MAP16)) + return 0; + *tag = mpack_tag_make_map(mpack_load_u16(reader->data + 1)); + return MPACK_TAG_SIZE_MAP16; + + // map32 + case 0xdf: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_MAP32)) + return 0; + *tag = mpack_tag_make_map(mpack_load_u32(reader->data + 1)); + return MPACK_TAG_SIZE_MAP32; + + // reserved + case 0xc1: + mpack_reader_flag_error(reader, mpack_error_invalid); + return 0; + + #if !MPACK_EXTENSIONS + // ext + case 0xc7: // fallthrough + case 0xc8: // fallthrough + case 0xc9: // fallthrough + // fixext + case 0xd4: // fallthrough + case 0xd5: // fallthrough + case 0xd6: // fallthrough + case 0xd7: // fallthrough + case 0xd8: + mpack_reader_flag_error(reader, mpack_error_unsupported); + return 0; + #endif + + #if MPACK_OPTIMIZE_FOR_SIZE + // any other bytes should have been handled by the infix switch + default: + break; + #endif + } + + mpack_assert(0, "unreachable"); + return 0; +} + +mpack_tag_t mpack_read_tag(mpack_reader_t* reader) { + mpack_log("reading tag\n"); + + // make sure we can read a tag + if (mpack_reader_error(reader) != mpack_ok) + return mpack_tag_nil(); + if (mpack_reader_track_element(reader) != mpack_ok) + return mpack_tag_nil(); + + mpack_tag_t tag = MPACK_TAG_ZERO; + size_t count = mpack_parse_tag(reader, &tag); + if (count == 0) + return mpack_tag_nil(); + + #if MPACK_READ_TRACKING + mpack_error_t track_error = mpack_ok; + + switch (tag.type) { + case mpack_type_map: + case mpack_type_array: + track_error = mpack_track_push(&reader->track, tag.type, tag.v.n); + break; + #if MPACK_EXTENSIONS + case mpack_type_ext: + #endif + case mpack_type_str: + case mpack_type_bin: + track_error = mpack_track_push(&reader->track, tag.type, tag.v.l); + break; + default: + break; + } + + if (track_error != mpack_ok) { + mpack_reader_flag_error(reader, track_error); + return mpack_tag_nil(); + } + #endif + + reader->data += count; + return tag; +} + +mpack_tag_t mpack_peek_tag(mpack_reader_t* reader) { + mpack_log("peeking tag\n"); + + // make sure we can peek a tag + if (mpack_reader_error(reader) != mpack_ok) + return mpack_tag_nil(); + if (mpack_reader_track_peek_element(reader) != mpack_ok) + return mpack_tag_nil(); + + mpack_tag_t tag = MPACK_TAG_ZERO; + if (mpack_parse_tag(reader, &tag) == 0) + return mpack_tag_nil(); + return tag; +} + +void mpack_discard(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (mpack_reader_error(reader)) + return; + switch (var.type) { + case mpack_type_str: + mpack_skip_bytes(reader, var.v.l); + mpack_done_str(reader); + break; + case mpack_type_bin: + mpack_skip_bytes(reader, var.v.l); + mpack_done_bin(reader); + break; + #if MPACK_EXTENSIONS + case mpack_type_ext: + mpack_skip_bytes(reader, var.v.l); + mpack_done_ext(reader); + break; + #endif + case mpack_type_array: { + for (; var.v.n > 0; --var.v.n) { + mpack_discard(reader); + if (mpack_reader_error(reader)) + break; + } + mpack_done_array(reader); + break; + } + case mpack_type_map: { + for (; var.v.n > 0; --var.v.n) { + mpack_discard(reader); + mpack_discard(reader); + if (mpack_reader_error(reader)) + break; + } + mpack_done_map(reader); + break; + } + default: + break; + } +} + +#if MPACK_EXTENSIONS +mpack_timestamp_t mpack_read_timestamp(mpack_reader_t* reader, size_t size) { + mpack_timestamp_t timestamp = {0, 0}; + + if (size != 4 && size != 8 && size != 12) { + mpack_reader_flag_error(reader, mpack_error_invalid); + return timestamp; + } + + char buf[12]; + mpack_read_bytes(reader, buf, size); + mpack_done_ext(reader); + if (mpack_reader_error(reader) != mpack_ok) + return timestamp; + + switch (size) { + case 4: + timestamp.seconds = (int64_t)(uint64_t)mpack_load_u32(buf); + break; + + case 8: { + uint64_t packed = mpack_load_u64(buf); + timestamp.seconds = (int64_t)(packed & ((UINT64_C(1) << 34) - 1)); + timestamp.nanoseconds = (uint32_t)(packed >> 34); + break; + } + + case 12: + timestamp.nanoseconds = mpack_load_u32(buf); + timestamp.seconds = mpack_load_i64(buf + 4); + break; + + default: + mpack_assert(false, "unreachable"); + break; + } + + if (timestamp.nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) { + mpack_reader_flag_error(reader, mpack_error_invalid); + mpack_timestamp_t zero = {0, 0}; + return zero; + } + + return timestamp; +} +#endif + +#if MPACK_READ_TRACKING +void mpack_done_type(mpack_reader_t* reader, mpack_type_t type) { + if (mpack_reader_error(reader) == mpack_ok) + mpack_reader_flag_if_error(reader, mpack_track_pop(&reader->track, type)); +} +#endif + +#if MPACK_DEBUG && MPACK_STDIO +static size_t mpack_print_read_prefix(mpack_reader_t* reader, size_t length, char* buffer, size_t buffer_size) { + if (length == 0) + return 0; + + size_t read = (length < buffer_size) ? length : buffer_size; + mpack_read_bytes(reader, buffer, read); + if (mpack_reader_error(reader) != mpack_ok) + return 0; + + mpack_skip_bytes(reader, length - read); + return read; +} + +static void mpack_print_element(mpack_reader_t* reader, mpack_print_t* print, size_t depth) { + mpack_tag_t val = mpack_read_tag(reader); + if (mpack_reader_error(reader) != mpack_ok) + return; + + // We read some bytes from bin and ext so we can print its prefix in hex. + char buffer[MPACK_PRINT_BYTE_COUNT]; + size_t count = 0; + + switch (val.type) { + case mpack_type_str: + mpack_print_append_cstr(print, "\""); + for (size_t i = 0; i < val.v.l; ++i) { + char c; + mpack_read_bytes(reader, &c, 1); + if (mpack_reader_error(reader) != mpack_ok) + return; + switch (c) { + case '\n': mpack_print_append_cstr(print, "\\n"); break; + case '\\': mpack_print_append_cstr(print, "\\\\"); break; + case '"': mpack_print_append_cstr(print, "\\\""); break; + default: mpack_print_append(print, &c, 1); break; + } + } + mpack_print_append_cstr(print, "\""); + mpack_done_str(reader); + return; + + case mpack_type_array: + mpack_print_append_cstr(print, "[\n"); + for (size_t i = 0; i < val.v.n; ++i) { + for (size_t j = 0; j < depth + 1; ++j) + mpack_print_append_cstr(print, " "); + mpack_print_element(reader, print, depth + 1); + if (mpack_reader_error(reader) != mpack_ok) + return; + if (i != val.v.n - 1) + mpack_print_append_cstr(print, ","); + mpack_print_append_cstr(print, "\n"); + } + for (size_t i = 0; i < depth; ++i) + mpack_print_append_cstr(print, " "); + mpack_print_append_cstr(print, "]"); + mpack_done_array(reader); + return; + + case mpack_type_map: + mpack_print_append_cstr(print, "{\n"); + for (size_t i = 0; i < val.v.n; ++i) { + for (size_t j = 0; j < depth + 1; ++j) + mpack_print_append_cstr(print, " "); + mpack_print_element(reader, print, depth + 1); + if (mpack_reader_error(reader) != mpack_ok) + return; + mpack_print_append_cstr(print, ": "); + mpack_print_element(reader, print, depth + 1); + if (mpack_reader_error(reader) != mpack_ok) + return; + if (i != val.v.n - 1) + mpack_print_append_cstr(print, ","); + mpack_print_append_cstr(print, "\n"); + } + for (size_t i = 0; i < depth; ++i) + mpack_print_append_cstr(print, " "); + mpack_print_append_cstr(print, "}"); + mpack_done_map(reader); + return; + + // The above cases return so as not to print a pseudo-json value. The + // below cases break and print pseudo-json. + + case mpack_type_bin: + count = mpack_print_read_prefix(reader, mpack_tag_bin_length(&val), buffer, sizeof(buffer)); + mpack_done_bin(reader); + break; + + #if MPACK_EXTENSIONS + case mpack_type_ext: + count = mpack_print_read_prefix(reader, mpack_tag_ext_length(&val), buffer, sizeof(buffer)); + mpack_done_ext(reader); + break; + #endif + + default: + break; + } + + char buf[256]; + mpack_tag_debug_pseudo_json(val, buf, sizeof(buf), buffer, count); + mpack_print_append_cstr(print, buf); +} + +static void mpack_print_and_destroy(mpack_reader_t* reader, mpack_print_t* print, size_t depth) { + for (size_t i = 0; i < depth; ++i) + mpack_print_append_cstr(print, " "); + mpack_print_element(reader, print, depth); + + size_t remaining = mpack_reader_remaining(reader, NULL); + + char buf[256]; + if (mpack_reader_destroy(reader) != mpack_ok) { + mpack_snprintf(buf, sizeof(buf), "\n<mpack parsing error %s>", mpack_error_to_string(mpack_reader_error(reader))); + buf[sizeof(buf) - 1] = '\0'; + mpack_print_append_cstr(print, buf); + } else if (remaining > 0) { + mpack_snprintf(buf, sizeof(buf), "\n<%i extra bytes at end of message>", (int)remaining); + buf[sizeof(buf) - 1] = '\0'; + mpack_print_append_cstr(print, buf); + } +} + +static void mpack_print_data(const char* data, size_t len, mpack_print_t* print, size_t depth) { + mpack_reader_t reader; + mpack_reader_init_data(&reader, data, len); + mpack_print_and_destroy(&reader, print, depth); +} + +void mpack_print_data_to_buffer(const char* data, size_t data_size, char* buffer, size_t buffer_size) { + if (buffer_size == 0) { + mpack_assert(false, "buffer size is zero!"); + return; + } + + mpack_print_t print; + mpack_memset(&print, 0, sizeof(print)); + print.buffer = buffer; + print.size = buffer_size; + mpack_print_data(data, data_size, &print, 0); + mpack_print_append(&print, "", 1); // null-terminator + mpack_print_flush(&print); + + // we always make sure there's a null-terminator at the end of the buffer + // in case we ran out of space. + print.buffer[print.size - 1] = '\0'; +} + +void mpack_print_data_to_callback(const char* data, size_t size, mpack_print_callback_t callback, void* context) { + char buffer[1024]; + mpack_print_t print; + mpack_memset(&print, 0, sizeof(print)); + print.buffer = buffer; + print.size = sizeof(buffer); + print.callback = callback; + print.context = context; + mpack_print_data(data, size, &print, 0); + mpack_print_flush(&print); +} + +void mpack_print_data_to_file(const char* data, size_t len, FILE* file) { + mpack_assert(data != NULL, "data is NULL"); + mpack_assert(file != NULL, "file is NULL"); + + char buffer[1024]; + mpack_print_t print; + mpack_memset(&print, 0, sizeof(print)); + print.buffer = buffer; + print.size = sizeof(buffer); + print.callback = &mpack_print_file_callback; + print.context = file; + + mpack_print_data(data, len, &print, 2); + mpack_print_append_cstr(&print, "\n"); + mpack_print_flush(&print); +} + +void mpack_print_stdfile_to_callback(FILE* file, mpack_print_callback_t callback, void* context) { + char buffer[1024]; + mpack_print_t print; + mpack_memset(&print, 0, sizeof(print)); + print.buffer = buffer; + print.size = sizeof(buffer); + print.callback = callback; + print.context = context; + + mpack_reader_t reader; + mpack_reader_init_stdfile(&reader, file, false); + mpack_print_and_destroy(&reader, &print, 0); + mpack_print_flush(&print); +} +#endif + +#endif + +/* mpack/mpack-expect.c.c */ + +#define MPACK_INTERNAL 1 + +/* #include "mpack-expect.h" */ + +#if MPACK_EXPECT + + +// Helpers + +MPACK_STATIC_INLINE uint8_t mpack_expect_native_u8(mpack_reader_t* reader) { + if (mpack_reader_error(reader) != mpack_ok) + return 0; + uint8_t type; + if (!mpack_reader_ensure(reader, sizeof(type))) + return 0; + type = mpack_load_u8(reader->data); + reader->data += sizeof(type); + return type; +} + +#if !MPACK_OPTIMIZE_FOR_SIZE +MPACK_STATIC_INLINE uint16_t mpack_expect_native_u16(mpack_reader_t* reader) { + if (mpack_reader_error(reader) != mpack_ok) + return 0; + uint16_t type; + if (!mpack_reader_ensure(reader, sizeof(type))) + return 0; + type = mpack_load_u16(reader->data); + reader->data += sizeof(type); + return type; +} + +MPACK_STATIC_INLINE uint32_t mpack_expect_native_u32(mpack_reader_t* reader) { + if (mpack_reader_error(reader) != mpack_ok) + return 0; + uint32_t type; + if (!mpack_reader_ensure(reader, sizeof(type))) + return 0; + type = mpack_load_u32(reader->data); + reader->data += sizeof(type); + return type; +} +#endif + +MPACK_STATIC_INLINE uint8_t mpack_expect_type_byte(mpack_reader_t* reader) { + mpack_reader_track_element(reader); + return mpack_expect_native_u8(reader); +} + + +// Basic Number Functions + +uint8_t mpack_expect_u8(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_uint) { + if (var.v.u <= UINT8_MAX) + return (uint8_t)var.v.u; + } else if (var.type == mpack_type_int) { + if (var.v.i >= 0 && var.v.i <= UINT8_MAX) + return (uint8_t)var.v.i; + } + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} + +uint16_t mpack_expect_u16(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_uint) { + if (var.v.u <= UINT16_MAX) + return (uint16_t)var.v.u; + } else if (var.type == mpack_type_int) { + if (var.v.i >= 0 && var.v.i <= UINT16_MAX) + return (uint16_t)var.v.i; + } + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} + +uint32_t mpack_expect_u32(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_uint) { + if (var.v.u <= UINT32_MAX) + return (uint32_t)var.v.u; + } else if (var.type == mpack_type_int) { + if (var.v.i >= 0 && var.v.i <= UINT32_MAX) + return (uint32_t)var.v.i; + } + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} + +uint64_t mpack_expect_u64(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_uint) { + return var.v.u; + } else if (var.type == mpack_type_int) { + if (var.v.i >= 0) + return (uint64_t)var.v.i; + } + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} + +int8_t mpack_expect_i8(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_uint) { + if (var.v.u <= INT8_MAX) + return (int8_t)var.v.u; + } else if (var.type == mpack_type_int) { + if (var.v.i >= INT8_MIN && var.v.i <= INT8_MAX) + return (int8_t)var.v.i; + } + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} + +int16_t mpack_expect_i16(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_uint) { + if (var.v.u <= INT16_MAX) + return (int16_t)var.v.u; + } else if (var.type == mpack_type_int) { + if (var.v.i >= INT16_MIN && var.v.i <= INT16_MAX) + return (int16_t)var.v.i; + } + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} + +int32_t mpack_expect_i32(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_uint) { + if (var.v.u <= INT32_MAX) + return (int32_t)var.v.u; + } else if (var.type == mpack_type_int) { + if (var.v.i >= INT32_MIN && var.v.i <= INT32_MAX) + return (int32_t)var.v.i; + } + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} + +int64_t mpack_expect_i64(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_uint) { + if (var.v.u <= INT64_MAX) + return (int64_t)var.v.u; + } else if (var.type == mpack_type_int) { + return var.v.i; + } + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} + +float mpack_expect_float(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_uint) + return (float)var.v.u; + else if (var.type == mpack_type_int) + return (float)var.v.i; + else if (var.type == mpack_type_float) + return var.v.f; + else if (var.type == mpack_type_double) + return (float)var.v.d; + mpack_reader_flag_error(reader, mpack_error_type); + return 0.0f; +} + +double mpack_expect_double(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_uint) + return (double)var.v.u; + else if (var.type == mpack_type_int) + return (double)var.v.i; + else if (var.type == mpack_type_float) + return (double)var.v.f; + else if (var.type == mpack_type_double) + return var.v.d; + mpack_reader_flag_error(reader, mpack_error_type); + return 0.0; +} + +float mpack_expect_float_strict(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_float) + return var.v.f; + mpack_reader_flag_error(reader, mpack_error_type); + return 0.0f; +} + +double mpack_expect_double_strict(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_float) + return (double)var.v.f; + else if (var.type == mpack_type_double) + return var.v.d; + mpack_reader_flag_error(reader, mpack_error_type); + return 0.0; +} + + +// Ranged Number Functions +// +// All ranged functions are identical other than the type, so we +// define their content with a macro. The prototypes are still written +// out in full to support ctags/IDE tools. + +#define MPACK_EXPECT_RANGE_IMPL(name, type_t) \ + \ + /* make sure the range is sensible */ \ + mpack_assert(min_value <= max_value, \ + "min_value %i must be less than or equal to max_value %i", \ + min_value, max_value); \ + \ + /* read the value */ \ + type_t val = mpack_expect_##name(reader); \ + if (mpack_reader_error(reader) != mpack_ok) \ + return min_value; \ + \ + /* make sure it fits */ \ + if (val < min_value || val > max_value) { \ + mpack_reader_flag_error(reader, mpack_error_type); \ + return min_value; \ + } \ + \ + return val; + +uint8_t mpack_expect_u8_range(mpack_reader_t* reader, uint8_t min_value, uint8_t max_value) {MPACK_EXPECT_RANGE_IMPL(u8, uint8_t)} +uint16_t mpack_expect_u16_range(mpack_reader_t* reader, uint16_t min_value, uint16_t max_value) {MPACK_EXPECT_RANGE_IMPL(u16, uint16_t)} +uint32_t mpack_expect_u32_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value) {MPACK_EXPECT_RANGE_IMPL(u32, uint32_t)} +uint64_t mpack_expect_u64_range(mpack_reader_t* reader, uint64_t min_value, uint64_t max_value) {MPACK_EXPECT_RANGE_IMPL(u64, uint64_t)} + +int8_t mpack_expect_i8_range(mpack_reader_t* reader, int8_t min_value, int8_t max_value) {MPACK_EXPECT_RANGE_IMPL(i8, int8_t)} +int16_t mpack_expect_i16_range(mpack_reader_t* reader, int16_t min_value, int16_t max_value) {MPACK_EXPECT_RANGE_IMPL(i16, int16_t)} +int32_t mpack_expect_i32_range(mpack_reader_t* reader, int32_t min_value, int32_t max_value) {MPACK_EXPECT_RANGE_IMPL(i32, int32_t)} +int64_t mpack_expect_i64_range(mpack_reader_t* reader, int64_t min_value, int64_t max_value) {MPACK_EXPECT_RANGE_IMPL(i64, int64_t)} + +float mpack_expect_float_range(mpack_reader_t* reader, float min_value, float max_value) {MPACK_EXPECT_RANGE_IMPL(float, float)} +double mpack_expect_double_range(mpack_reader_t* reader, double min_value, double max_value) {MPACK_EXPECT_RANGE_IMPL(double, double)} + +uint32_t mpack_expect_map_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value) {MPACK_EXPECT_RANGE_IMPL(map, uint32_t)} +uint32_t mpack_expect_array_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value) {MPACK_EXPECT_RANGE_IMPL(array, uint32_t)} + + +// Matching Number Functions + +void mpack_expect_uint_match(mpack_reader_t* reader, uint64_t value) { + if (mpack_expect_u64(reader) != value) + mpack_reader_flag_error(reader, mpack_error_type); +} + +void mpack_expect_int_match(mpack_reader_t* reader, int64_t value) { + if (mpack_expect_i64(reader) != value) + mpack_reader_flag_error(reader, mpack_error_type); +} + + +// Other Basic Types + +void mpack_expect_nil(mpack_reader_t* reader) { + if (mpack_expect_type_byte(reader) != 0xc0) + mpack_reader_flag_error(reader, mpack_error_type); +} + +bool mpack_expect_bool(mpack_reader_t* reader) { + uint8_t type = mpack_expect_type_byte(reader); + if ((type & ~1) != 0xc2) + mpack_reader_flag_error(reader, mpack_error_type); + return (bool)(type & 1); +} + +void mpack_expect_true(mpack_reader_t* reader) { + if (mpack_expect_bool(reader) != true) + mpack_reader_flag_error(reader, mpack_error_type); +} + +void mpack_expect_false(mpack_reader_t* reader) { + if (mpack_expect_bool(reader) != false) + mpack_reader_flag_error(reader, mpack_error_type); +} + +#if MPACK_EXTENSIONS +mpack_timestamp_t mpack_expect_timestamp(mpack_reader_t* reader) { + mpack_timestamp_t zero = {0, 0}; + + mpack_tag_t tag = mpack_read_tag(reader); + if (tag.type != mpack_type_ext) { + mpack_reader_flag_error(reader, mpack_error_type); + return zero; + } + if (mpack_tag_ext_exttype(&tag) != MPACK_EXTTYPE_TIMESTAMP) { + mpack_reader_flag_error(reader, mpack_error_type); + return zero; + } + + return mpack_read_timestamp(reader, mpack_tag_ext_length(&tag)); +} + +int64_t mpack_expect_timestamp_truncate(mpack_reader_t* reader) { + return mpack_expect_timestamp(reader).seconds; +} +#endif + + +// Compound Types + +uint32_t mpack_expect_map(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_map) + return var.v.n; + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} + +void mpack_expect_map_match(mpack_reader_t* reader, uint32_t count) { + if (mpack_expect_map(reader) != count) + mpack_reader_flag_error(reader, mpack_error_type); +} + +bool mpack_expect_map_or_nil(mpack_reader_t* reader, uint32_t* count) { + mpack_assert(count != NULL, "count cannot be NULL"); + + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_nil) { + *count = 0; + return false; + } + if (var.type == mpack_type_map) { + *count = var.v.n; + return true; + } + mpack_reader_flag_error(reader, mpack_error_type); + *count = 0; + return false; +} + +bool mpack_expect_map_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count) { + mpack_assert(count != NULL, "count cannot be NULL"); + + bool has_map = mpack_expect_map_or_nil(reader, count); + if (has_map && *count > max_count) { + *count = 0; + mpack_reader_flag_error(reader, mpack_error_type); + return false; + } + return has_map; +} + +uint32_t mpack_expect_array(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_array) + return var.v.n; + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} + +void mpack_expect_array_match(mpack_reader_t* reader, uint32_t count) { + if (mpack_expect_array(reader) != count) + mpack_reader_flag_error(reader, mpack_error_type); +} + +bool mpack_expect_array_or_nil(mpack_reader_t* reader, uint32_t* count) { + mpack_assert(count != NULL, "count cannot be NULL"); + + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_nil) { + *count = 0; + return false; + } + if (var.type == mpack_type_array) { + *count = var.v.n; + return true; + } + mpack_reader_flag_error(reader, mpack_error_type); + *count = 0; + return false; +} + +bool mpack_expect_array_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count) { + mpack_assert(count != NULL, "count cannot be NULL"); + + bool has_array = mpack_expect_array_or_nil(reader, count); + if (has_array && *count > max_count) { + *count = 0; + mpack_reader_flag_error(reader, mpack_error_type); + return false; + } + return has_array; +} + +#ifdef MPACK_MALLOC +void* mpack_expect_array_alloc_impl(mpack_reader_t* reader, size_t element_size, uint32_t max_count, uint32_t* out_count, bool allow_nil) { + mpack_assert(out_count != NULL, "out_count cannot be NULL"); + *out_count = 0; + + uint32_t count; + bool has_array = true; + if (allow_nil) + has_array = mpack_expect_array_max_or_nil(reader, max_count, &count); + else + count = mpack_expect_array_max(reader, max_count); + if (mpack_reader_error(reader)) + return NULL; + + // size 0 is not an error; we return NULL for no elements. + if (count == 0) { + // we call mpack_done_array() automatically ONLY if we are using + // the _or_nil variant. this is the only way to allow nil and empty + // to work the same way. + if (allow_nil && has_array) + mpack_done_array(reader); + return NULL; + } + + void* p = MPACK_MALLOC(element_size * count); + if (p == NULL) { + mpack_reader_flag_error(reader, mpack_error_memory); + return NULL; + } + + *out_count = count; + return p; +} +#endif + + +// Str, Bin and Ext Functions + +uint32_t mpack_expect_str(mpack_reader_t* reader) { + #if MPACK_OPTIMIZE_FOR_SIZE + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_str) + return var.v.l; + mpack_reader_flag_error(reader, mpack_error_type); + return 0; + #else + uint8_t type = mpack_expect_type_byte(reader); + uint32_t count; + + if ((type >> 5) == 5) { + count = type & (uint8_t)~0xe0; + } else if (type == 0xd9) { + count = mpack_expect_native_u8(reader); + } else if (type == 0xda) { + count = mpack_expect_native_u16(reader); + } else if (type == 0xdb) { + count = mpack_expect_native_u32(reader); + } else { + mpack_reader_flag_error(reader, mpack_error_type); + return 0; + } + + #if MPACK_READ_TRACKING + mpack_reader_flag_if_error(reader, mpack_track_push(&reader->track, mpack_type_str, count)); + #endif + return count; + #endif +} + +size_t mpack_expect_str_buf(mpack_reader_t* reader, char* buf, size_t bufsize) { + mpack_assert(buf != NULL, "buf cannot be NULL"); + + size_t length = mpack_expect_str(reader); + if (mpack_reader_error(reader)) + return 0; + + if (length > bufsize) { + mpack_reader_flag_error(reader, mpack_error_too_big); + return 0; + } + + mpack_read_bytes(reader, buf, length); + if (mpack_reader_error(reader)) + return 0; + + mpack_done_str(reader); + return length; +} + +size_t mpack_expect_utf8(mpack_reader_t* reader, char* buf, size_t size) { + mpack_assert(buf != NULL, "buf cannot be NULL"); + + size_t length = mpack_expect_str_buf(reader, buf, size); + + if (!mpack_utf8_check(buf, length)) { + mpack_reader_flag_error(reader, mpack_error_type); + return 0; + } + + return length; +} + +uint32_t mpack_expect_bin(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_bin) + return var.v.l; + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} + +size_t mpack_expect_bin_buf(mpack_reader_t* reader, char* buf, size_t bufsize) { + mpack_assert(buf != NULL, "buf cannot be NULL"); + + size_t binsize = mpack_expect_bin(reader); + if (mpack_reader_error(reader)) + return 0; + if (binsize > bufsize) { + mpack_reader_flag_error(reader, mpack_error_too_big); + return 0; + } + mpack_read_bytes(reader, buf, binsize); + if (mpack_reader_error(reader)) + return 0; + mpack_done_bin(reader); + return binsize; +} + +#if MPACK_EXTENSIONS +uint32_t mpack_expect_ext(mpack_reader_t* reader, int8_t* type) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_ext) { + *type = mpack_tag_ext_exttype(&var); + return mpack_tag_ext_length(&var); + } + *type = 0; + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} + +size_t mpack_expect_ext_buf(mpack_reader_t* reader, int8_t* type, char* buf, size_t bufsize) { + mpack_assert(buf != NULL, "buf cannot be NULL"); + + size_t extsize = mpack_expect_ext(reader, type); + if (mpack_reader_error(reader)) + return 0; + if (extsize > bufsize) { + *type = 0; + mpack_reader_flag_error(reader, mpack_error_too_big); + return 0; + } + mpack_read_bytes(reader, buf, extsize); + if (mpack_reader_error(reader)) { + *type = 0; + return 0; + } + mpack_done_ext(reader); + return extsize; +} +#endif + +void mpack_expect_cstr(mpack_reader_t* reader, char* buf, size_t bufsize) { + uint32_t length = mpack_expect_str(reader); + mpack_read_cstr(reader, buf, bufsize, length); + mpack_done_str(reader); +} + +void mpack_expect_utf8_cstr(mpack_reader_t* reader, char* buf, size_t bufsize) { + uint32_t length = mpack_expect_str(reader); + mpack_read_utf8_cstr(reader, buf, bufsize, length); + mpack_done_str(reader); +} + +#ifdef MPACK_MALLOC +static char* mpack_expect_cstr_alloc_unchecked(mpack_reader_t* reader, size_t maxsize, size_t* out_length) { + mpack_assert(out_length != NULL, "out_length cannot be NULL"); + *out_length = 0; + + // make sure argument makes sense + if (maxsize < 1) { + mpack_break("maxsize is zero; you must have room for at least a null-terminator"); + mpack_reader_flag_error(reader, mpack_error_bug); + return NULL; + } + + if (maxsize > UINT32_MAX) + maxsize = UINT32_MAX; + + size_t length = mpack_expect_str_max(reader, (uint32_t)maxsize - 1); + char* str = mpack_read_bytes_alloc_impl(reader, length, true); + mpack_done_str(reader); + + if (str) + *out_length = length; + return str; +} + +char* mpack_expect_cstr_alloc(mpack_reader_t* reader, size_t maxsize) { + size_t length; + char* str = mpack_expect_cstr_alloc_unchecked(reader, maxsize, &length); + + if (str && !mpack_str_check_no_null(str, length)) { + MPACK_FREE(str); + mpack_reader_flag_error(reader, mpack_error_type); + return NULL; + } + + return str; +} + +char* mpack_expect_utf8_cstr_alloc(mpack_reader_t* reader, size_t maxsize) { + size_t length; + char* str = mpack_expect_cstr_alloc_unchecked(reader, maxsize, &length); + + if (str && !mpack_utf8_check_no_null(str, length)) { + MPACK_FREE(str); + mpack_reader_flag_error(reader, mpack_error_type); + return NULL; + } + + return str; +} +#endif + +void mpack_expect_str_match(mpack_reader_t* reader, const char* str, size_t len) { + mpack_assert(str != NULL, "str cannot be NULL"); + + // expect a str the correct length + if (len > UINT32_MAX) + mpack_reader_flag_error(reader, mpack_error_type); + mpack_expect_str_length(reader, (uint32_t)len); + if (mpack_reader_error(reader)) + return; + mpack_reader_track_bytes(reader, len); + + // check each byte one by one (matched strings are likely to be very small) + for (; len > 0; --len) { + if (mpack_expect_native_u8(reader) != *str++) { + mpack_reader_flag_error(reader, mpack_error_type); + return; + } + } + + mpack_done_str(reader); +} + +void mpack_expect_tag(mpack_reader_t* reader, mpack_tag_t expected) { + mpack_tag_t actual = mpack_read_tag(reader); + if (!mpack_tag_equal(actual, expected)) + mpack_reader_flag_error(reader, mpack_error_type); +} + +#ifdef MPACK_MALLOC +char* mpack_expect_bin_alloc(mpack_reader_t* reader, size_t maxsize, size_t* size) { + mpack_assert(size != NULL, "size cannot be NULL"); + *size = 0; + + if (maxsize > UINT32_MAX) + maxsize = UINT32_MAX; + + size_t length = mpack_expect_bin_max(reader, (uint32_t)maxsize); + if (mpack_reader_error(reader)) + return NULL; + + char* data = mpack_read_bytes_alloc(reader, length); + mpack_done_bin(reader); + + if (data) + *size = length; + return data; +} +#endif + +#if MPACK_EXTENSIONS && defined(MPACK_MALLOC) +char* mpack_expect_ext_alloc(mpack_reader_t* reader, int8_t* type, size_t maxsize, size_t* size) { + mpack_assert(size != NULL, "size cannot be NULL"); + *size = 0; + + if (maxsize > UINT32_MAX) + maxsize = UINT32_MAX; + + size_t length = mpack_expect_ext_max(reader, type, (uint32_t)maxsize); + if (mpack_reader_error(reader)) + return NULL; + + char* data = mpack_read_bytes_alloc(reader, length); + mpack_done_ext(reader); + + if (data) { + *size = length; + } else { + *type = 0; + } + return data; +} +#endif + +size_t mpack_expect_enum(mpack_reader_t* reader, const char* strings[], size_t count) { + + // read the string in-place + size_t keylen = mpack_expect_str(reader); + const char* key = mpack_read_bytes_inplace(reader, keylen); + mpack_done_str(reader); + if (mpack_reader_error(reader) != mpack_ok) + return count; + + // find what key it matches + for (size_t i = 0; i < count; ++i) { + const char* other = strings[i]; + size_t otherlen = mpack_strlen(other); + if (keylen == otherlen && mpack_memcmp(key, other, keylen) == 0) + return i; + } + + // no matches + mpack_reader_flag_error(reader, mpack_error_type); + return count; +} + +size_t mpack_expect_enum_optional(mpack_reader_t* reader, const char* strings[], size_t count) { + if (mpack_reader_error(reader) != mpack_ok) + return count; + + mpack_assert(count != 0, "count cannot be zero; no strings are valid!"); + mpack_assert(strings != NULL, "strings cannot be NULL"); + + // the key is only recognized if it is a string + if (mpack_peek_tag(reader).type != mpack_type_str) { + mpack_discard(reader); + return count; + } + + // read the string in-place + size_t keylen = mpack_expect_str(reader); + const char* key = mpack_read_bytes_inplace(reader, keylen); + mpack_done_str(reader); + if (mpack_reader_error(reader) != mpack_ok) + return count; + + // find what key it matches + for (size_t i = 0; i < count; ++i) { + const char* other = strings[i]; + size_t otherlen = mpack_strlen(other); + if (keylen == otherlen && mpack_memcmp(key, other, keylen) == 0) + return i; + } + + // no matches + return count; +} + +size_t mpack_expect_key_uint(mpack_reader_t* reader, bool found[], size_t count) { + if (mpack_reader_error(reader) != mpack_ok) + return count; + + if (count == 0) { + mpack_break("count cannot be zero; no keys are valid!"); + mpack_reader_flag_error(reader, mpack_error_bug); + return count; + } + mpack_assert(found != NULL, "found cannot be NULL"); + + // the key is only recognized if it is an unsigned int + if (mpack_peek_tag(reader).type != mpack_type_uint) { + mpack_discard(reader); + return count; + } + + // read the key + uint64_t value = mpack_expect_u64(reader); + if (mpack_reader_error(reader) != mpack_ok) + return count; + + // unrecognized keys are fine, we just return count + if (value >= count) + return count; + + // check if this key is a duplicate + if (found[value]) { + mpack_reader_flag_error(reader, mpack_error_invalid); + return count; + } + + found[value] = true; + return (size_t)value; +} + +size_t mpack_expect_key_cstr(mpack_reader_t* reader, const char* keys[], bool found[], size_t count) { + size_t i = mpack_expect_enum_optional(reader, keys, count); + + // unrecognized keys are fine, we just return count + if (i == count) + return count; + + // check if this key is a duplicate + mpack_assert(found != NULL, "found cannot be NULL"); + if (found[i]) { + mpack_reader_flag_error(reader, mpack_error_invalid); + return count; + } + + found[i] = true; + return i; +} + +#endif + + +/* mpack/mpack-node.c.c */ + +#define MPACK_INTERNAL 1 + +/* #include "mpack-node.h" */ + +#if MPACK_NODE + +MPACK_STATIC_INLINE const char* mpack_node_data_unchecked(mpack_node_t node) { + mpack_assert(mpack_node_error(node) == mpack_ok, "tree is in an error state!"); + + mpack_type_t type = node.data->type; + MPACK_UNUSED(type); + #if MPACK_EXTENSIONS + mpack_assert(type == mpack_type_str || type == mpack_type_bin || type == mpack_type_ext, + "node of type %i (%s) is not a data type!", type, mpack_type_to_string(type)); + #else + mpack_assert(type == mpack_type_str || type == mpack_type_bin, + "node of type %i (%s) is not a data type!", type, mpack_type_to_string(type)); + #endif + + return node.tree->data + node.data->value.offset; +} + +#if MPACK_EXTENSIONS +MPACK_STATIC_INLINE int8_t mpack_node_exttype_unchecked(mpack_node_t node) { + mpack_assert(mpack_node_error(node) == mpack_ok, "tree is in an error state!"); + + mpack_type_t type = node.data->type; + MPACK_UNUSED(type); + mpack_assert(type == mpack_type_ext, "node of type %i (%s) is not an ext type!", + type, mpack_type_to_string(type)); + + // the exttype of an ext node is stored in the byte preceding the data + return mpack_load_i8(mpack_node_data_unchecked(node) - 1); +} +#endif + + + +/* + * Tree Parsing + */ + +#ifdef MPACK_MALLOC + +// fix up the alloc size to make sure it exactly fits the +// maximum number of nodes it can contain (the allocator will +// waste it back anyway, but we round it down just in case) + +#define MPACK_NODES_PER_PAGE \ + ((MPACK_NODE_PAGE_SIZE - sizeof(mpack_tree_page_t)) / sizeof(mpack_node_data_t) + 1) + +#define MPACK_PAGE_ALLOC_SIZE \ + (sizeof(mpack_tree_page_t) + sizeof(mpack_node_data_t) * (MPACK_NODES_PER_PAGE - 1)) + +#endif + +#ifdef MPACK_MALLOC +/* + * Fills the tree until we have at least enough bytes for the current node. + */ +static bool mpack_tree_reserve_fill(mpack_tree_t* tree) { + mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress); + + size_t bytes = tree->parser.current_node_reserved; + mpack_assert(bytes > tree->parser.possible_nodes_left, + "there are already enough bytes! call mpack_tree_ensure() instead."); + mpack_log("filling to reserve %i bytes\n", (int)bytes); + + // if the necessary bytes would put us over the maximum tree + // size, fail right away. + // TODO: check for overflow? + if (tree->data_length + bytes > tree->max_size) { + mpack_tree_flag_error(tree, mpack_error_too_big); + return false; + } + + // we'll need a read function to fetch more data. if there's + // no read function, the data should contain an entire message + // (or messages), so we flag it as invalid. + if (tree->read_fn == NULL) { + mpack_log("tree has no read function!\n"); + mpack_tree_flag_error(tree, mpack_error_invalid); + return false; + } + + // expand the buffer if needed + if (tree->data_length + bytes > tree->buffer_capacity) { + + // TODO: check for overflow? + size_t new_capacity = (tree->buffer_capacity == 0) ? MPACK_BUFFER_SIZE : tree->buffer_capacity; + while (new_capacity < tree->data_length + bytes) + new_capacity *= 2; + if (new_capacity > tree->max_size) + new_capacity = tree->max_size; + + mpack_log("expanding buffer from %i to %i\n", (int)tree->buffer_capacity, (int)new_capacity); + + char* new_buffer; + if (tree->buffer == NULL) + new_buffer = (char*)MPACK_MALLOC(new_capacity); + else + new_buffer = (char*)mpack_realloc(tree->buffer, tree->data_length, new_capacity); + + if (new_buffer == NULL) { + mpack_tree_flag_error(tree, mpack_error_memory); + return false; + } + + tree->data = new_buffer; + tree->buffer = new_buffer; + tree->buffer_capacity = new_capacity; + } + + // request as much data as possible, looping until we have + // all the data we need + do { + size_t read = tree->read_fn(tree, tree->buffer + tree->data_length, tree->buffer_capacity - tree->data_length); + + // If the fill function encounters an error, it should flag an error on + // the tree. + if (mpack_tree_error(tree) != mpack_ok) + return false; + + // We guard against fill functions that return -1 just in case. + if (read == (size_t)(-1)) { + mpack_tree_flag_error(tree, mpack_error_io); + return false; + } + + // If the fill function returns 0, the data is not available yet. We + // return false to stop parsing the current node. + if (read == 0) { + mpack_log("not enough data.\n"); + return false; + } + + mpack_log("read %u more bytes\n", (uint32_t)read); + tree->data_length += read; + tree->parser.possible_nodes_left += read; + } while (tree->parser.possible_nodes_left < bytes); + + return true; +} +#endif + +/* + * Ensures there are enough additional bytes in the tree for the current node + * (including reserved bytes for the children of this node, and in addition to + * the reserved bytes for children of previous compound nodes), reading more + * data if needed. + * + * extra_bytes is the number of additional bytes to reserve for the current + * node beyond the type byte (since one byte is already reserved for each node + * by its parent array or map.) + * + * This may reallocate the tree, which means the tree->data pointer may change! + * + * Returns false if not enough bytes could be read. + */ +MPACK_STATIC_INLINE bool mpack_tree_reserve_bytes(mpack_tree_t* tree, size_t extra_bytes) { + mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress); + + // We guard against overflow here. A compound type could declare more than + // UINT32_MAX contents which overflows SIZE_MAX on 32-bit platforms. We + // flag mpack_error_invalid instead of mpack_error_too_big since it's far + // more likely that the message is corrupt than that the data is valid but + // not parseable on this architecture (see test_read_node_possible() in + // test-node.c .) + if ((uint64_t)tree->parser.current_node_reserved + (uint64_t)extra_bytes > SIZE_MAX) { + mpack_tree_flag_error(tree, mpack_error_invalid); + return false; + } + + tree->parser.current_node_reserved += extra_bytes; + + // Note that possible_nodes_left already accounts for reserved bytes for + // children of previous compound nodes. So even if there are hundreds of + // bytes left in the buffer, we might need to read anyway. + if (tree->parser.current_node_reserved <= tree->parser.possible_nodes_left) + return true; + + #ifdef MPACK_MALLOC + return mpack_tree_reserve_fill(tree); + #else + return false; + #endif +} + +MPACK_STATIC_INLINE size_t mpack_tree_parser_stack_capacity(mpack_tree_t* tree) { + #ifdef MPACK_MALLOC + return tree->parser.stack_capacity; + #else + return sizeof(tree->parser.stack) / sizeof(tree->parser.stack[0]); + #endif +} + +static bool mpack_tree_push_stack(mpack_tree_t* tree, mpack_node_data_t* first_child, size_t total) { + mpack_tree_parser_t* parser = &tree->parser; + mpack_assert(parser->state == mpack_tree_parse_state_in_progress); + + // No need to push empty containers + if (total == 0) + return true; + + // Make sure we have enough room in the stack + if (parser->level + 1 == mpack_tree_parser_stack_capacity(tree)) { + #ifdef MPACK_MALLOC + size_t new_capacity = parser->stack_capacity * 2; + mpack_log("growing parse stack to capacity %i\n", (int)new_capacity); + + // Replace the stack-allocated parsing stack + if (!parser->stack_owned) { + mpack_level_t* new_stack = (mpack_level_t*)MPACK_MALLOC(sizeof(mpack_level_t) * new_capacity); + if (!new_stack) { + mpack_tree_flag_error(tree, mpack_error_memory); + return false; + } + mpack_memcpy(new_stack, parser->stack, sizeof(mpack_level_t) * parser->stack_capacity); + parser->stack = new_stack; + parser->stack_owned = true; + + // Realloc the allocated parsing stack + } else { + mpack_level_t* new_stack = (mpack_level_t*)mpack_realloc(parser->stack, + sizeof(mpack_level_t) * parser->stack_capacity, sizeof(mpack_level_t) * new_capacity); + if (!new_stack) { + mpack_tree_flag_error(tree, mpack_error_memory); + return false; + } + parser->stack = new_stack; + } + parser->stack_capacity = new_capacity; + #else + mpack_tree_flag_error(tree, mpack_error_too_big); + return false; + #endif + } + + // Push the contents of this node onto the parsing stack + ++parser->level; + parser->stack[parser->level].child = first_child; + parser->stack[parser->level].left = total; + return true; +} + +static bool mpack_tree_parse_children(mpack_tree_t* tree, mpack_node_data_t* node) { + mpack_tree_parser_t* parser = &tree->parser; + mpack_assert(parser->state == mpack_tree_parse_state_in_progress); + + mpack_type_t type = node->type; + size_t total = node->len; + + // Calculate total elements to read + if (type == mpack_type_map) { + if ((uint64_t)total * 2 > SIZE_MAX) { + mpack_tree_flag_error(tree, mpack_error_too_big); + return false; + } + total *= 2; + } + + // Make sure we are under our total node limit (TODO can this overflow?) + tree->node_count += total; + if (tree->node_count > tree->max_nodes) { + mpack_tree_flag_error(tree, mpack_error_too_big); + return false; + } + + // Each node is at least one byte. Count these bytes now to make + // sure there is enough data left. + if (!mpack_tree_reserve_bytes(tree, total)) + return false; + + // If there are enough nodes left in the current page, no need to grow + if (total <= parser->nodes_left) { + node->value.children = parser->nodes; + parser->nodes += total; + parser->nodes_left -= total; + + } else { + + #ifdef MPACK_MALLOC + + // We can't grow if we're using a fixed pool (i.e. we didn't start with a page) + if (!tree->next) { + mpack_tree_flag_error(tree, mpack_error_too_big); + return false; + } + + // Otherwise we need to grow, and the node's children need to be contiguous. + // This is a heuristic to decide whether we should waste the remaining space + // in the current page and start a new one, or give the children their + // own page. With a fraction of 1/8, this causes at most 12% additional + // waste. Note that reducing this too much causes less cache coherence and + // more malloc() overhead due to smaller allocations, so there's a tradeoff + // here. This heuristic could use some improvement, especially with custom + // page sizes. + + mpack_tree_page_t* page; + + if (total > MPACK_NODES_PER_PAGE || parser->nodes_left > MPACK_NODES_PER_PAGE / 8) { + // TODO: this should check for overflow + page = (mpack_tree_page_t*)MPACK_MALLOC( + sizeof(mpack_tree_page_t) + sizeof(mpack_node_data_t) * (total - 1)); + if (page == NULL) { + mpack_tree_flag_error(tree, mpack_error_memory); + return false; + } + mpack_log("allocated seperate page %p for %i children, %i left in page of %i total\n", + page, (int)total, (int)parser->nodes_left, (int)MPACK_NODES_PER_PAGE); + + node->value.children = page->nodes; + + } else { + page = (mpack_tree_page_t*)MPACK_MALLOC(MPACK_PAGE_ALLOC_SIZE); + if (page == NULL) { + mpack_tree_flag_error(tree, mpack_error_memory); + return false; + } + mpack_log("allocated new page %p for %i children, wasting %i in page of %i total\n", + page, (int)total, (int)parser->nodes_left, (int)MPACK_NODES_PER_PAGE); + + node->value.children = page->nodes; + parser->nodes = page->nodes + total; + parser->nodes_left = MPACK_NODES_PER_PAGE - total; + } + + page->next = tree->next; + tree->next = page; + + #else + // We can't grow if we don't have an allocator + mpack_tree_flag_error(tree, mpack_error_too_big); + return false; + #endif + } + + return mpack_tree_push_stack(tree, node->value.children, total); +} + +static bool mpack_tree_parse_bytes(mpack_tree_t* tree, mpack_node_data_t* node) { + node->value.offset = tree->size + tree->parser.current_node_reserved + 1; + return mpack_tree_reserve_bytes(tree, node->len); +} + +#if MPACK_EXTENSIONS +static bool mpack_tree_parse_ext(mpack_tree_t* tree, mpack_node_data_t* node) { + // reserve space for exttype + tree->parser.current_node_reserved += sizeof(int8_t); + node->type = mpack_type_ext; + return mpack_tree_parse_bytes(tree, node); +} +#endif + +static bool mpack_tree_parse_node_contents(mpack_tree_t* tree, mpack_node_data_t* node) { + mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress); + mpack_assert(node != NULL, "null node?"); + + // read the type. we've already accounted for this byte in + // possible_nodes_left, so we already know it is in bounds, and we don't + // need to reserve it for this node. + mpack_assert(tree->data_length > tree->size); + uint8_t type = mpack_load_u8(tree->data + tree->size); + mpack_log("node type %x\n", type); + tree->parser.current_node_reserved = 0; + + // as with mpack_read_tag(), the fastest way to parse a node is to switch + // on the first byte, and to explicitly list every possible byte. we switch + // on the first four bits in size-optimized builds. + + #if MPACK_OPTIMIZE_FOR_SIZE + switch (type >> 4) { + + // positive fixnum + case 0x0: case 0x1: case 0x2: case 0x3: + case 0x4: case 0x5: case 0x6: case 0x7: + node->type = mpack_type_uint; + node->value.u = type; + return true; + + // negative fixnum + case 0xe: case 0xf: + node->type = mpack_type_int; + node->value.i = (int8_t)type; + return true; + + // fixmap + case 0x8: + node->type = mpack_type_map; + node->len = (uint32_t)(type & ~0xf0); + return mpack_tree_parse_children(tree, node); + + // fixarray + case 0x9: + node->type = mpack_type_array; + node->len = (uint32_t)(type & ~0xf0); + return mpack_tree_parse_children(tree, node); + + // fixstr + case 0xa: case 0xb: + node->type = mpack_type_str; + node->len = (uint32_t)(type & ~0xe0); + return mpack_tree_parse_bytes(tree, node); + + // not one of the common infix types + default: + break; + } + #endif + + switch (type) { + + #if !MPACK_OPTIMIZE_FOR_SIZE + // positive fixnum + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: + case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: + case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: + case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f: + case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: + case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f: + case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: + case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f: + case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: + case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: + case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: + case 0x68: case 0x69: case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f: + case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: + case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f: + node->type = mpack_type_uint; + node->value.u = type; + return true; + + // negative fixnum + case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7: + case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef: + case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7: + case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff: + node->type = mpack_type_int; + node->value.i = (int8_t)type; + return true; + + // fixmap + case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: + case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: + node->type = mpack_type_map; + node->len = (uint32_t)(type & ~0xf0); + return mpack_tree_parse_children(tree, node); + + // fixarray + case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: + case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f: + node->type = mpack_type_array; + node->len = (uint32_t)(type & ~0xf0); + return mpack_tree_parse_children(tree, node); + + // fixstr + case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7: + case 0xa8: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf: + case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7: + case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: + node->type = mpack_type_str; + node->len = (uint32_t)(type & ~0xe0); + return mpack_tree_parse_bytes(tree, node); + #endif + + // nil + case 0xc0: + node->type = mpack_type_nil; + return true; + + // bool + case 0xc2: case 0xc3: + node->type = mpack_type_bool; + node->value.b = type & 1; + return true; + + // bin8 + case 0xc4: + node->type = mpack_type_bin; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t))) + return false; + node->len = mpack_load_u8(tree->data + tree->size + 1); + return mpack_tree_parse_bytes(tree, node); + + // bin16 + case 0xc5: + node->type = mpack_type_bin; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t))) + return false; + node->len = mpack_load_u16(tree->data + tree->size + 1); + return mpack_tree_parse_bytes(tree, node); + + // bin32 + case 0xc6: + node->type = mpack_type_bin; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t))) + return false; + node->len = mpack_load_u32(tree->data + tree->size + 1); + return mpack_tree_parse_bytes(tree, node); + + #if MPACK_EXTENSIONS + // ext8 + case 0xc7: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t))) + return false; + node->len = mpack_load_u8(tree->data + tree->size + 1); + return mpack_tree_parse_ext(tree, node); + + // ext16 + case 0xc8: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t))) + return false; + node->len = mpack_load_u16(tree->data + tree->size + 1); + return mpack_tree_parse_ext(tree, node); + + // ext32 + case 0xc9: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t))) + return false; + node->len = mpack_load_u32(tree->data + tree->size + 1); + return mpack_tree_parse_ext(tree, node); + #endif + + // float + case 0xca: + if (!mpack_tree_reserve_bytes(tree, sizeof(float))) + return false; + node->value.f = mpack_load_float(tree->data + tree->size + 1); + node->type = mpack_type_float; + return true; + + // double + case 0xcb: + if (!mpack_tree_reserve_bytes(tree, sizeof(double))) + return false; + node->value.d = mpack_load_double(tree->data + tree->size + 1); + node->type = mpack_type_double; + return true; + + // uint8 + case 0xcc: + node->type = mpack_type_uint; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t))) + return false; + node->value.u = mpack_load_u8(tree->data + tree->size + 1); + return true; + + // uint16 + case 0xcd: + node->type = mpack_type_uint; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t))) + return false; + node->value.u = mpack_load_u16(tree->data + tree->size + 1); + return true; + + // uint32 + case 0xce: + node->type = mpack_type_uint; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t))) + return false; + node->value.u = mpack_load_u32(tree->data + tree->size + 1); + return true; + + // uint64 + case 0xcf: + node->type = mpack_type_uint; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint64_t))) + return false; + node->value.u = mpack_load_u64(tree->data + tree->size + 1); + return true; + + // int8 + case 0xd0: + node->type = mpack_type_int; + if (!mpack_tree_reserve_bytes(tree, sizeof(int8_t))) + return false; + node->value.i = mpack_load_i8(tree->data + tree->size + 1); + return true; + + // int16 + case 0xd1: + node->type = mpack_type_int; + if (!mpack_tree_reserve_bytes(tree, sizeof(int16_t))) + return false; + node->value.i = mpack_load_i16(tree->data + tree->size + 1); + return true; + + // int32 + case 0xd2: + node->type = mpack_type_int; + if (!mpack_tree_reserve_bytes(tree, sizeof(int32_t))) + return false; + node->value.i = mpack_load_i32(tree->data + tree->size + 1); + return true; + + // int64 + case 0xd3: + node->type = mpack_type_int; + if (!mpack_tree_reserve_bytes(tree, sizeof(int64_t))) + return false; + node->value.i = mpack_load_i64(tree->data + tree->size + 1); + return true; + + #if MPACK_EXTENSIONS + // fixext1 + case 0xd4: + node->len = 1; + return mpack_tree_parse_ext(tree, node); + + // fixext2 + case 0xd5: + node->len = 2; + return mpack_tree_parse_ext(tree, node); + + // fixext4 + case 0xd6: + node->len = 4; + return mpack_tree_parse_ext(tree, node); + + // fixext8 + case 0xd7: + node->len = 8; + return mpack_tree_parse_ext(tree, node); + + // fixext16 + case 0xd8: + node->len = 16; + return mpack_tree_parse_ext(tree, node); + #endif + + // str8 + case 0xd9: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t))) + return false; + node->len = mpack_load_u8(tree->data + tree->size + 1); + node->type = mpack_type_str; + return mpack_tree_parse_bytes(tree, node); + + // str16 + case 0xda: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t))) + return false; + node->len = mpack_load_u16(tree->data + tree->size + 1); + node->type = mpack_type_str; + return mpack_tree_parse_bytes(tree, node); + + // str32 + case 0xdb: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t))) + return false; + node->len = mpack_load_u32(tree->data + tree->size + 1); + node->type = mpack_type_str; + return mpack_tree_parse_bytes(tree, node); + + // array16 + case 0xdc: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t))) + return false; + node->len = mpack_load_u16(tree->data + tree->size + 1); + node->type = mpack_type_array; + return mpack_tree_parse_children(tree, node); + + // array32 + case 0xdd: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t))) + return false; + node->len = mpack_load_u32(tree->data + tree->size + 1); + node->type = mpack_type_array; + return mpack_tree_parse_children(tree, node); + + // map16 + case 0xde: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t))) + return false; + node->len = mpack_load_u16(tree->data + tree->size + 1); + node->type = mpack_type_map; + return mpack_tree_parse_children(tree, node); + + // map32 + case 0xdf: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t))) + return false; + node->len = mpack_load_u32(tree->data + tree->size + 1); + node->type = mpack_type_map; + return mpack_tree_parse_children(tree, node); + + // reserved + case 0xc1: + mpack_tree_flag_error(tree, mpack_error_invalid); + return false; + + #if !MPACK_EXTENSIONS + // ext + case 0xc7: // fallthrough + case 0xc8: // fallthrough + case 0xc9: // fallthrough + // fixext + case 0xd4: // fallthrough + case 0xd5: // fallthrough + case 0xd6: // fallthrough + case 0xd7: // fallthrough + case 0xd8: + mpack_tree_flag_error(tree, mpack_error_unsupported); + return false; + #endif + + #if MPACK_OPTIMIZE_FOR_SIZE + // any other bytes should have been handled by the infix switch + default: + break; + #endif + } + + mpack_assert(0, "unreachable"); + return false; +} + +static bool mpack_tree_parse_node(mpack_tree_t* tree, mpack_node_data_t* node) { + mpack_log("parsing a node at position %i in level %i\n", + (int)tree->size, (int)tree->parser.level); + + if (!mpack_tree_parse_node_contents(tree, node)) { + mpack_log("node parsing returned false\n"); + return false; + } + + tree->parser.possible_nodes_left -= tree->parser.current_node_reserved; + + // The reserve for the current node does not include the initial byte + // previously reserved as part of its parent. + size_t node_size = tree->parser.current_node_reserved + 1; + + // If the parsed type is a map or array, the reserve includes one byte for + // each child. We want to subtract these out of possible_nodes_left, but + // not out of the current size of the tree. + if (node->type == mpack_type_array) + node_size -= node->len; + else if (node->type == mpack_type_map) + node_size -= node->len * 2; + tree->size += node_size; + + mpack_log("parsed a node of type %s of %i bytes and " + "%i additional bytes reserved for children.\n", + mpack_type_to_string(node->type), (int)node_size, + (int)tree->parser.current_node_reserved + 1 - (int)node_size); + + return true; +} + +/* + * We read nodes in a loop instead of recursively for maximum performance. The + * stack holds the amount of children left to read in each level of the tree. + * Parsing can pause and resume when more data becomes available. + */ +static bool mpack_tree_continue_parsing(mpack_tree_t* tree) { + if (mpack_tree_error(tree) != mpack_ok) + return false; + + mpack_tree_parser_t* parser = &tree->parser; + mpack_assert(parser->state == mpack_tree_parse_state_in_progress); + mpack_log("parsing tree elements, %i bytes in buffer\n", (int)tree->data_length); + + // we loop parsing nodes until the parse stack is empty. we break + // by returning out of the function. + while (true) { + mpack_node_data_t* node = parser->stack[parser->level].child; + size_t level = parser->level; + if (!mpack_tree_parse_node(tree, node)) + return false; + --parser->stack[level].left; + ++parser->stack[level].child; + + mpack_assert(mpack_tree_error(tree) == mpack_ok, + "mpack_tree_parse_node() should have returned false due to error!"); + + // pop empty stack levels, exiting the outer loop when the stack is empty. + // (we could tail-optimize containers by pre-emptively popping empty + // stack levels before reading the new element, this way we wouldn't + // have to loop. but we eventually want to use the parse stack to give + // better error messages that contain the location of the error, so + // it needs to be complete.) + while (parser->stack[parser->level].left == 0) { + if (parser->level == 0) + return true; + --parser->level; + } + } +} + +static void mpack_tree_cleanup(mpack_tree_t* tree) { + MPACK_UNUSED(tree); + + #ifdef MPACK_MALLOC + if (tree->parser.stack_owned) { + MPACK_FREE(tree->parser.stack); + tree->parser.stack = NULL; + tree->parser.stack_owned = false; + } + + mpack_tree_page_t* page = tree->next; + while (page != NULL) { + mpack_tree_page_t* next = page->next; + mpack_log("freeing page %p\n", page); + MPACK_FREE(page); + page = next; + } + tree->next = NULL; + #endif +} + +static bool mpack_tree_parse_start(mpack_tree_t* tree) { + if (mpack_tree_error(tree) != mpack_ok) + return false; + + mpack_tree_parser_t* parser = &tree->parser; + mpack_assert(parser->state != mpack_tree_parse_state_in_progress, + "previous parsing was not finished!"); + + if (parser->state == mpack_tree_parse_state_parsed) + mpack_tree_cleanup(tree); + + mpack_log("starting parse\n"); + tree->parser.state = mpack_tree_parse_state_in_progress; + tree->parser.current_node_reserved = 0; + + // check if we previously parsed a tree + if (tree->size > 0) { + #ifdef MPACK_MALLOC + // if we're buffered, move the remaining data back to the + // start of the buffer + // TODO: This is not ideal performance-wise. We should only move data + // when we need to call the fill function. + // TODO: We could consider shrinking the buffer here, especially if we + // determine that the fill function is providing less than a quarter of + // the buffer size or if messages take up less than a quarter of the + // buffer size. Maybe this should be configurable. + if (tree->buffer != NULL) { + mpack_memmove(tree->buffer, tree->buffer + tree->size, tree->data_length - tree->size); + } + else + #endif + // otherwise advance past the parsed data + { + tree->data += tree->size; + } + tree->data_length -= tree->size; + tree->size = 0; + tree->node_count = 0; + } + + // make sure we have at least one byte available before allocating anything + parser->possible_nodes_left = tree->data_length; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t))) { + tree->parser.state = mpack_tree_parse_state_not_started; + return false; + } + mpack_log("parsing tree at %p starting with byte %x\n", tree->data, (uint8_t)tree->data[0]); + parser->possible_nodes_left -= 1; + tree->node_count = 1; + + #ifdef MPACK_MALLOC + parser->stack = parser->stack_local; + parser->stack_owned = false; + parser->stack_capacity = sizeof(parser->stack_local) / sizeof(*parser->stack_local); + + if (tree->pool == NULL) { + + // allocate first page + mpack_tree_page_t* page = (mpack_tree_page_t*)MPACK_MALLOC(MPACK_PAGE_ALLOC_SIZE); + mpack_log("allocated initial page %p of size %i count %i\n", + page, (int)MPACK_PAGE_ALLOC_SIZE, (int)MPACK_NODES_PER_PAGE); + if (page == NULL) { + tree->error = mpack_error_memory; + return false; + } + page->next = NULL; + tree->next = page; + + parser->nodes = page->nodes; + parser->nodes_left = MPACK_NODES_PER_PAGE; + } + else + #endif + { + // otherwise use the provided pool + mpack_assert(tree->pool != NULL, "no pool provided?"); + parser->nodes = tree->pool; + parser->nodes_left = tree->pool_count; + } + + tree->root = parser->nodes; + ++parser->nodes; + --parser->nodes_left; + + parser->level = 0; + parser->stack[0].child = tree->root; + parser->stack[0].left = 1; + + return true; +} + +void mpack_tree_parse(mpack_tree_t* tree) { + if (mpack_tree_error(tree) != mpack_ok) + return; + + if (tree->parser.state != mpack_tree_parse_state_in_progress) { + if (!mpack_tree_parse_start(tree)) { + mpack_tree_flag_error(tree, (tree->read_fn == NULL) ? + mpack_error_invalid : mpack_error_io); + return; + } + } + + if (!mpack_tree_continue_parsing(tree)) { + if (mpack_tree_error(tree) != mpack_ok) + return; + + // We're parsing synchronously on a blocking fill function. If we + // didn't completely finish parsing the tree, it's an error. + mpack_log("tree parsing incomplete. flagging error.\n"); + mpack_tree_flag_error(tree, (tree->read_fn == NULL) ? + mpack_error_invalid : mpack_error_io); + return; + } + + mpack_assert(mpack_tree_error(tree) == mpack_ok); + mpack_assert(tree->parser.level == 0); + tree->parser.state = mpack_tree_parse_state_parsed; + mpack_log("parsed tree of %i bytes, %i bytes left\n", (int)tree->size, (int)tree->parser.possible_nodes_left); + mpack_log("%i nodes in final page\n", (int)tree->parser.nodes_left); +} + +bool mpack_tree_try_parse(mpack_tree_t* tree) { + if (mpack_tree_error(tree) != mpack_ok) + return false; + + if (tree->parser.state != mpack_tree_parse_state_in_progress) + if (!mpack_tree_parse_start(tree)) + return false; + + if (!mpack_tree_continue_parsing(tree)) + return false; + + mpack_assert(mpack_tree_error(tree) == mpack_ok); + mpack_assert(tree->parser.level == 0); + tree->parser.state = mpack_tree_parse_state_parsed; + return true; +} + + + +/* + * Tree functions + */ + +mpack_node_t mpack_tree_root(mpack_tree_t* tree) { + if (mpack_tree_error(tree) != mpack_ok) + return mpack_tree_nil_node(tree); + + // We check that a tree was parsed successfully and assert if not. You must + // call mpack_tree_parse() (or mpack_tree_try_parse() with a success + // result) in order to access the root node. + if (tree->parser.state != mpack_tree_parse_state_parsed) { + mpack_break("Tree has not been parsed! " + "Did you call mpack_tree_parse() or mpack_tree_try_parse()?"); + mpack_tree_flag_error(tree, mpack_error_bug); + return mpack_tree_nil_node(tree); + } + + return mpack_node(tree, tree->root); +} + +static void mpack_tree_init_clear(mpack_tree_t* tree) { + mpack_memset(tree, 0, sizeof(*tree)); + tree->nil_node.type = mpack_type_nil; + tree->missing_node.type = mpack_type_missing; + tree->max_size = SIZE_MAX; + tree->max_nodes = SIZE_MAX; +} + +#ifdef MPACK_MALLOC +void mpack_tree_init_data(mpack_tree_t* tree, const char* data, size_t length) { + mpack_tree_init_clear(tree); + + MPACK_STATIC_ASSERT(MPACK_NODE_PAGE_SIZE >= sizeof(mpack_tree_page_t), + "MPACK_NODE_PAGE_SIZE is too small"); + + MPACK_STATIC_ASSERT(MPACK_PAGE_ALLOC_SIZE <= MPACK_NODE_PAGE_SIZE, + "incorrect page rounding?"); + + tree->data = data; + tree->data_length = length; + tree->pool = NULL; + tree->pool_count = 0; + tree->next = NULL; + + mpack_log("===========================\n"); + mpack_log("initializing tree with data of size %i\n", (int)length); +} +#endif + +void mpack_tree_init_pool(mpack_tree_t* tree, const char* data, size_t length, + mpack_node_data_t* node_pool, size_t node_pool_count) +{ + mpack_tree_init_clear(tree); + #ifdef MPACK_MALLOC + tree->next = NULL; + #endif + + if (node_pool_count == 0) { + mpack_break("initial page has no nodes!"); + mpack_tree_flag_error(tree, mpack_error_bug); + return; + } + + tree->data = data; + tree->data_length = length; + tree->pool = node_pool; + tree->pool_count = node_pool_count; + + mpack_log("===========================\n"); + mpack_log("initializing tree with data of size %i and pool of count %i\n", + (int)length, (int)node_pool_count); +} + +void mpack_tree_init_error(mpack_tree_t* tree, mpack_error_t error) { + mpack_tree_init_clear(tree); + tree->error = error; + + mpack_log("===========================\n"); + mpack_log("initializing tree error state %i\n", (int)error); +} + +#ifdef MPACK_MALLOC +void mpack_tree_init_stream(mpack_tree_t* tree, mpack_tree_read_t read_fn, void* context, + size_t max_message_size, size_t max_message_nodes) { + mpack_tree_init_clear(tree); + + tree->read_fn = read_fn; + tree->context = context; + + mpack_tree_set_limits(tree, max_message_size, max_message_nodes); + tree->max_size = max_message_size; + tree->max_nodes = max_message_nodes; + + mpack_log("===========================\n"); + mpack_log("initializing tree with stream, max size %i max nodes %i\n", + (int)max_message_size, (int)max_message_nodes); +} +#endif + +void mpack_tree_set_limits(mpack_tree_t* tree, size_t max_message_size, size_t max_message_nodes) { + mpack_assert(max_message_size > 0); + mpack_assert(max_message_nodes > 0); + tree->max_size = max_message_size; + tree->max_nodes = max_message_nodes; +} + +#if MPACK_STDIO +typedef struct mpack_file_tree_t { + char* data; + size_t size; + char buffer[MPACK_BUFFER_SIZE]; +} mpack_file_tree_t; + +static void mpack_file_tree_teardown(mpack_tree_t* tree) { + mpack_file_tree_t* file_tree = (mpack_file_tree_t*)tree->context; + MPACK_FREE(file_tree->data); + MPACK_FREE(file_tree); +} + +static bool mpack_file_tree_read(mpack_tree_t* tree, mpack_file_tree_t* file_tree, FILE* file, size_t max_bytes) { + + // get the file size + errno = 0; + int error = 0; + fseek(file, 0, SEEK_END); + error |= errno; + long size = ftell(file); + error |= errno; + fseek(file, 0, SEEK_SET); + error |= errno; + + // check for errors + if (error != 0 || size < 0) { + mpack_tree_init_error(tree, mpack_error_io); + return false; + } + if (size == 0) { + mpack_tree_init_error(tree, mpack_error_invalid); + return false; + } + + // make sure the size is less than max_bytes + // (this mess exists to safely convert between long and size_t regardless of their widths) + if (max_bytes != 0 && (((uint64_t)LONG_MAX > (uint64_t)SIZE_MAX && size > (long)SIZE_MAX) || (size_t)size > max_bytes)) { + mpack_tree_init_error(tree, mpack_error_too_big); + return false; + } + + // allocate data + file_tree->data = (char*)MPACK_MALLOC((size_t)size); + if (file_tree->data == NULL) { + mpack_tree_init_error(tree, mpack_error_memory); + return false; + } + + // read the file + long total = 0; + while (total < size) { + size_t read = fread(file_tree->data + total, 1, (size_t)(size - total), file); + if (read <= 0) { + mpack_tree_init_error(tree, mpack_error_io); + MPACK_FREE(file_tree->data); + return false; + } + total += (long)read; + } + + file_tree->size = (size_t)size; + return true; +} + +static bool mpack_tree_file_check_max_bytes(mpack_tree_t* tree, size_t max_bytes) { + + // the C STDIO family of file functions use long (e.g. ftell) + if (max_bytes > LONG_MAX) { + mpack_break("max_bytes of %" PRIu64 " is invalid, maximum is LONG_MAX", (uint64_t)max_bytes); + mpack_tree_init_error(tree, mpack_error_bug); + return false; + } + + return true; +} + +static void mpack_tree_init_stdfile_noclose(mpack_tree_t* tree, FILE* stdfile, size_t max_bytes) { + + // allocate file tree + mpack_file_tree_t* file_tree = (mpack_file_tree_t*) MPACK_MALLOC(sizeof(mpack_file_tree_t)); + if (file_tree == NULL) { + mpack_tree_init_error(tree, mpack_error_memory); + return; + } + + // read all data + if (!mpack_file_tree_read(tree, file_tree, stdfile, max_bytes)) { + MPACK_FREE(file_tree); + return; + } + + mpack_tree_init_data(tree, file_tree->data, file_tree->size); + mpack_tree_set_context(tree, file_tree); + mpack_tree_set_teardown(tree, mpack_file_tree_teardown); +} + +void mpack_tree_init_stdfile(mpack_tree_t* tree, FILE* stdfile, size_t max_bytes, bool close_when_done) { + if (!mpack_tree_file_check_max_bytes(tree, max_bytes)) + return; + + mpack_tree_init_stdfile_noclose(tree, stdfile, max_bytes); + + if (close_when_done) + fclose(stdfile); +} + +void mpack_tree_init_filename(mpack_tree_t* tree, const char* filename, size_t max_bytes) { + if (!mpack_tree_file_check_max_bytes(tree, max_bytes)) + return; + + // open the file + FILE* file = fopen(filename, "rb"); + if (!file) { + mpack_tree_init_error(tree, mpack_error_io); + return; + } + + mpack_tree_init_stdfile(tree, file, max_bytes, true); +} +#endif + +mpack_error_t mpack_tree_destroy(mpack_tree_t* tree) { + mpack_tree_cleanup(tree); + + #ifdef MPACK_MALLOC + if (tree->buffer) + MPACK_FREE(tree->buffer); + #endif + + if (tree->teardown) + tree->teardown(tree); + tree->teardown = NULL; + + return tree->error; +} + +void mpack_tree_flag_error(mpack_tree_t* tree, mpack_error_t error) { + if (tree->error == mpack_ok) { + mpack_log("tree %p setting error %i: %s\n", tree, (int)error, mpack_error_to_string(error)); + tree->error = error; + if (tree->error_fn) + tree->error_fn(tree, error); + } + +} + + + +/* + * Node misc functions + */ + +void mpack_node_flag_error(mpack_node_t node, mpack_error_t error) { + mpack_tree_flag_error(node.tree, error); +} + +mpack_tag_t mpack_node_tag(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return mpack_tag_nil(); + + mpack_tag_t tag = MPACK_TAG_ZERO; + + tag.type = node.data->type; + switch (node.data->type) { + case mpack_type_missing: + // If a node is missing, I don't know if it makes sense to ask for + // a tag for it. We'll return a missing tag to match the missing + // node I guess, but attempting to use the tag for anything (like + // writing it for example) will flag mpack_error_bug. + break; + case mpack_type_nil: break; + case mpack_type_bool: tag.v.b = node.data->value.b; break; + case mpack_type_float: tag.v.f = node.data->value.f; break; + case mpack_type_double: tag.v.d = node.data->value.d; break; + case mpack_type_int: tag.v.i = node.data->value.i; break; + case mpack_type_uint: tag.v.u = node.data->value.u; break; + + case mpack_type_str: tag.v.l = node.data->len; break; + case mpack_type_bin: tag.v.l = node.data->len; break; + + #if MPACK_EXTENSIONS + case mpack_type_ext: + tag.v.l = node.data->len; + tag.exttype = mpack_node_exttype_unchecked(node); + break; + #endif + + case mpack_type_array: tag.v.n = node.data->len; break; + case mpack_type_map: tag.v.n = node.data->len; break; + + default: + mpack_assert(0, "unrecognized type %i", (int)node.data->type); + break; + } + return tag; +} + +#if MPACK_DEBUG && MPACK_STDIO +static void mpack_node_print_element(mpack_node_t node, mpack_print_t* print, size_t depth) { + mpack_node_data_t* data = node.data; + switch (data->type) { + case mpack_type_str: + { + mpack_print_append_cstr(print, "\""); + const char* bytes = mpack_node_data_unchecked(node); + for (size_t i = 0; i < data->len; ++i) { + char c = bytes[i]; + switch (c) { + case '\n': mpack_print_append_cstr(print, "\\n"); break; + case '\\': mpack_print_append_cstr(print, "\\\\"); break; + case '"': mpack_print_append_cstr(print, "\\\""); break; + default: mpack_print_append(print, &c, 1); break; + } + } + mpack_print_append_cstr(print, "\""); + } + break; + + case mpack_type_array: + mpack_print_append_cstr(print, "[\n"); + for (size_t i = 0; i < data->len; ++i) { + for (size_t j = 0; j < depth + 1; ++j) + mpack_print_append_cstr(print, " "); + mpack_node_print_element(mpack_node_array_at(node, i), print, depth + 1); + if (i != data->len - 1) + mpack_print_append_cstr(print, ","); + mpack_print_append_cstr(print, "\n"); + } + for (size_t i = 0; i < depth; ++i) + mpack_print_append_cstr(print, " "); + mpack_print_append_cstr(print, "]"); + break; + + case mpack_type_map: + mpack_print_append_cstr(print, "{\n"); + for (size_t i = 0; i < data->len; ++i) { + for (size_t j = 0; j < depth + 1; ++j) + mpack_print_append_cstr(print, " "); + mpack_node_print_element(mpack_node_map_key_at(node, i), print, depth + 1); + mpack_print_append_cstr(print, ": "); + mpack_node_print_element(mpack_node_map_value_at(node, i), print, depth + 1); + if (i != data->len - 1) + mpack_print_append_cstr(print, ","); + mpack_print_append_cstr(print, "\n"); + } + for (size_t i = 0; i < depth; ++i) + mpack_print_append_cstr(print, " "); + mpack_print_append_cstr(print, "}"); + break; + + default: + { + const char* prefix = NULL; + size_t prefix_length = 0; + if (mpack_node_type(node) == mpack_type_bin + #if MPACK_EXTENSIONS + || mpack_node_type(node) == mpack_type_ext + #endif + ) { + prefix = mpack_node_data(node); + prefix_length = mpack_node_data_len(node); + } + + char buf[256]; + mpack_tag_t tag = mpack_node_tag(node); + mpack_tag_debug_pseudo_json(tag, buf, sizeof(buf), prefix, prefix_length); + mpack_print_append_cstr(print, buf); + } + break; + } +} + +void mpack_node_print_to_buffer(mpack_node_t node, char* buffer, size_t buffer_size) { + if (buffer_size == 0) { + mpack_assert(false, "buffer size is zero!"); + return; + } + + mpack_print_t print; + mpack_memset(&print, 0, sizeof(print)); + print.buffer = buffer; + print.size = buffer_size; + mpack_node_print_element(node, &print, 0); + mpack_print_append(&print, "", 1); // null-terminator + mpack_print_flush(&print); + + // we always make sure there's a null-terminator at the end of the buffer + // in case we ran out of space. + print.buffer[print.size - 1] = '\0'; +} + +void mpack_node_print_to_callback(mpack_node_t node, mpack_print_callback_t callback, void* context) { + char buffer[1024]; + mpack_print_t print; + mpack_memset(&print, 0, sizeof(print)); + print.buffer = buffer; + print.size = sizeof(buffer); + print.callback = callback; + print.context = context; + mpack_node_print_element(node, &print, 0); + mpack_print_flush(&print); +} + +void mpack_node_print_to_file(mpack_node_t node, FILE* file) { + mpack_assert(file != NULL, "file is NULL"); + + char buffer[1024]; + mpack_print_t print; + mpack_memset(&print, 0, sizeof(print)); + print.buffer = buffer; + print.size = sizeof(buffer); + print.callback = &mpack_print_file_callback; + print.context = file; + + size_t depth = 2; + for (size_t i = 0; i < depth; ++i) + mpack_print_append_cstr(&print, " "); + mpack_node_print_element(node, &print, depth); + mpack_print_append_cstr(&print, "\n"); + mpack_print_flush(&print); +} +#endif + + + +/* + * Node Value Functions + */ + +#if MPACK_EXTENSIONS +mpack_timestamp_t mpack_node_timestamp(mpack_node_t node) { + mpack_timestamp_t timestamp = {0, 0}; + + // we'll let mpack_node_exttype() do most checks + if (mpack_node_exttype(node) != MPACK_EXTTYPE_TIMESTAMP) { + mpack_log("exttype %i\n", mpack_node_exttype(node)); + mpack_node_flag_error(node, mpack_error_type); + return timestamp; + } + + const char* p = mpack_node_data_unchecked(node); + + switch (node.data->len) { + case 4: + timestamp.nanoseconds = 0; + timestamp.seconds = mpack_load_u32(p); + break; + + case 8: { + uint64_t value = mpack_load_u64(p); + timestamp.nanoseconds = (uint32_t)(value >> 34); + timestamp.seconds = value & ((UINT64_C(1) << 34) - 1); + break; + } + + case 12: + timestamp.nanoseconds = mpack_load_u32(p); + timestamp.seconds = mpack_load_i64(p + 4); + break; + + default: + mpack_tree_flag_error(node.tree, mpack_error_invalid); + return timestamp; + } + + if (timestamp.nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) { + mpack_tree_flag_error(node.tree, mpack_error_invalid); + mpack_timestamp_t zero = {0, 0}; + return zero; + } + + return timestamp; +} + +int64_t mpack_node_timestamp_seconds(mpack_node_t node) { + return mpack_node_timestamp(node).seconds; +} + +uint32_t mpack_node_timestamp_nanoseconds(mpack_node_t node) { + return mpack_node_timestamp(node).nanoseconds; +} +#endif + + + +/* + * Node Data Functions + */ + +void mpack_node_check_utf8(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return; + mpack_node_data_t* data = node.data; + if (data->type != mpack_type_str || !mpack_utf8_check(mpack_node_data_unchecked(node), data->len)) + mpack_node_flag_error(node, mpack_error_type); +} + +void mpack_node_check_utf8_cstr(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return; + mpack_node_data_t* data = node.data; + if (data->type != mpack_type_str || !mpack_utf8_check_no_null(mpack_node_data_unchecked(node), data->len)) + mpack_node_flag_error(node, mpack_error_type); +} + +size_t mpack_node_copy_data(mpack_node_t node, char* buffer, size_t bufsize) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + mpack_assert(bufsize == 0 || buffer != NULL, "buffer is NULL for maximum of %i bytes", (int)bufsize); + + mpack_type_t type = node.data->type; + if (type != mpack_type_str && type != mpack_type_bin + #if MPACK_EXTENSIONS + && type != mpack_type_ext + #endif + ) { + mpack_node_flag_error(node, mpack_error_type); + return 0; + } + + if (node.data->len > bufsize) { + mpack_node_flag_error(node, mpack_error_too_big); + return 0; + } + + mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len); + return (size_t)node.data->len; +} + +size_t mpack_node_copy_utf8(mpack_node_t node, char* buffer, size_t bufsize) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + mpack_assert(bufsize == 0 || buffer != NULL, "buffer is NULL for maximum of %i bytes", (int)bufsize); + + mpack_type_t type = node.data->type; + if (type != mpack_type_str) { + mpack_node_flag_error(node, mpack_error_type); + return 0; + } + + if (node.data->len > bufsize) { + mpack_node_flag_error(node, mpack_error_too_big); + return 0; + } + + if (!mpack_utf8_check(mpack_node_data_unchecked(node), node.data->len)) { + mpack_node_flag_error(node, mpack_error_type); + return 0; + } + + mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len); + return (size_t)node.data->len; +} + +void mpack_node_copy_cstr(mpack_node_t node, char* buffer, size_t bufsize) { + + // we can't break here because the error isn't recoverable; we + // have to add a null-terminator. + mpack_assert(buffer != NULL, "buffer is NULL"); + mpack_assert(bufsize >= 1, "buffer size is zero; you must have room for at least a null-terminator"); + + if (mpack_node_error(node) != mpack_ok) { + buffer[0] = '\0'; + return; + } + + if (node.data->type != mpack_type_str) { + buffer[0] = '\0'; + mpack_node_flag_error(node, mpack_error_type); + return; + } + + if (node.data->len > bufsize - 1) { + buffer[0] = '\0'; + mpack_node_flag_error(node, mpack_error_too_big); + return; + } + + if (!mpack_str_check_no_null(mpack_node_data_unchecked(node), node.data->len)) { + buffer[0] = '\0'; + mpack_node_flag_error(node, mpack_error_type); + return; + } + + mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len); + buffer[node.data->len] = '\0'; +} + +void mpack_node_copy_utf8_cstr(mpack_node_t node, char* buffer, size_t bufsize) { + + // we can't break here because the error isn't recoverable; we + // have to add a null-terminator. + mpack_assert(buffer != NULL, "buffer is NULL"); + mpack_assert(bufsize >= 1, "buffer size is zero; you must have room for at least a null-terminator"); + + if (mpack_node_error(node) != mpack_ok) { + buffer[0] = '\0'; + return; + } + + if (node.data->type != mpack_type_str) { + buffer[0] = '\0'; + mpack_node_flag_error(node, mpack_error_type); + return; + } + + if (node.data->len > bufsize - 1) { + buffer[0] = '\0'; + mpack_node_flag_error(node, mpack_error_too_big); + return; + } + + if (!mpack_utf8_check_no_null(mpack_node_data_unchecked(node), node.data->len)) { + buffer[0] = '\0'; + mpack_node_flag_error(node, mpack_error_type); + return; + } + + mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len); + buffer[node.data->len] = '\0'; +} + +#ifdef MPACK_MALLOC +char* mpack_node_data_alloc(mpack_node_t node, size_t maxlen) { + if (mpack_node_error(node) != mpack_ok) + return NULL; + + // make sure this is a valid data type + mpack_type_t type = node.data->type; + if (type != mpack_type_str && type != mpack_type_bin + #if MPACK_EXTENSIONS + && type != mpack_type_ext + #endif + ) { + mpack_node_flag_error(node, mpack_error_type); + return NULL; + } + + if (node.data->len > maxlen) { + mpack_node_flag_error(node, mpack_error_too_big); + return NULL; + } + + char* ret = (char*) MPACK_MALLOC((size_t)node.data->len); + if (ret == NULL) { + mpack_node_flag_error(node, mpack_error_memory); + return NULL; + } + + mpack_memcpy(ret, mpack_node_data_unchecked(node), node.data->len); + return ret; +} + +char* mpack_node_cstr_alloc(mpack_node_t node, size_t maxlen) { + if (mpack_node_error(node) != mpack_ok) + return NULL; + + // make sure maxlen makes sense + if (maxlen < 1) { + mpack_break("maxlen is zero; you must have room for at least a null-terminator"); + mpack_node_flag_error(node, mpack_error_bug); + return NULL; + } + + if (node.data->type != mpack_type_str) { + mpack_node_flag_error(node, mpack_error_type); + return NULL; + } + + if (node.data->len > maxlen - 1) { + mpack_node_flag_error(node, mpack_error_too_big); + return NULL; + } + + if (!mpack_str_check_no_null(mpack_node_data_unchecked(node), node.data->len)) { + mpack_node_flag_error(node, mpack_error_type); + return NULL; + } + + char* ret = (char*) MPACK_MALLOC((size_t)(node.data->len + 1)); + if (ret == NULL) { + mpack_node_flag_error(node, mpack_error_memory); + return NULL; + } + + mpack_memcpy(ret, mpack_node_data_unchecked(node), node.data->len); + ret[node.data->len] = '\0'; + return ret; +} + +char* mpack_node_utf8_cstr_alloc(mpack_node_t node, size_t maxlen) { + if (mpack_node_error(node) != mpack_ok) + return NULL; + + // make sure maxlen makes sense + if (maxlen < 1) { + mpack_break("maxlen is zero; you must have room for at least a null-terminator"); + mpack_node_flag_error(node, mpack_error_bug); + return NULL; + } + + if (node.data->type != mpack_type_str) { + mpack_node_flag_error(node, mpack_error_type); + return NULL; + } + + if (node.data->len > maxlen - 1) { + mpack_node_flag_error(node, mpack_error_too_big); + return NULL; + } + + if (!mpack_utf8_check_no_null(mpack_node_data_unchecked(node), node.data->len)) { + mpack_node_flag_error(node, mpack_error_type); + return NULL; + } + + char* ret = (char*) MPACK_MALLOC((size_t)(node.data->len + 1)); + if (ret == NULL) { + mpack_node_flag_error(node, mpack_error_memory); + return NULL; + } + + mpack_memcpy(ret, mpack_node_data_unchecked(node), node.data->len); + ret[node.data->len] = '\0'; + return ret; +} +#endif + + +/* + * Compound Node Functions + */ + +static mpack_node_data_t* mpack_node_map_int_impl(mpack_node_t node, int64_t num) { + if (mpack_node_error(node) != mpack_ok) + return NULL; + + if (node.data->type != mpack_type_map) { + mpack_node_flag_error(node, mpack_error_type); + return NULL; + } + + mpack_node_data_t* found = NULL; + + for (size_t i = 0; i < node.data->len; ++i) { + mpack_node_data_t* key = mpack_node_child(node, i * 2); + + if ((key->type == mpack_type_int && key->value.i == num) || + (key->type == mpack_type_uint && num >= 0 && key->value.u == (uint64_t)num)) + { + if (found) { + mpack_node_flag_error(node, mpack_error_data); + return NULL; + } + found = mpack_node_child(node, i * 2 + 1); + } + } + + if (found) + return found; + + return NULL; +} + +static mpack_node_data_t* mpack_node_map_uint_impl(mpack_node_t node, uint64_t num) { + if (mpack_node_error(node) != mpack_ok) + return NULL; + + if (node.data->type != mpack_type_map) { + mpack_node_flag_error(node, mpack_error_type); + return NULL; + } + + mpack_node_data_t* found = NULL; + + for (size_t i = 0; i < node.data->len; ++i) { + mpack_node_data_t* key = mpack_node_child(node, i * 2); + + if ((key->type == mpack_type_uint && key->value.u == num) || + (key->type == mpack_type_int && key->value.i >= 0 && (uint64_t)key->value.i == num)) + { + if (found) { + mpack_node_flag_error(node, mpack_error_data); + return NULL; + } + found = mpack_node_child(node, i * 2 + 1); + } + } + + if (found) + return found; + + return NULL; +} + +static mpack_node_data_t* mpack_node_map_str_impl(mpack_node_t node, const char* str, size_t length) { + if (mpack_node_error(node) != mpack_ok) + return NULL; + + mpack_assert(length == 0 || str != NULL, "str of length %i is NULL", (int)length); + + if (node.data->type != mpack_type_map) { + mpack_node_flag_error(node, mpack_error_type); + return NULL; + } + + mpack_tree_t* tree = node.tree; + mpack_node_data_t* found = NULL; + + for (size_t i = 0; i < node.data->len; ++i) { + mpack_node_data_t* key = mpack_node_child(node, i * 2); + + if (key->type == mpack_type_str && key->len == length && + mpack_memcmp(str, mpack_node_data_unchecked(mpack_node(tree, key)), length) == 0) { + if (found) { + mpack_node_flag_error(node, mpack_error_data); + return NULL; + } + found = mpack_node_child(node, i * 2 + 1); + } + } + + if (found) + return found; + + return NULL; +} + +static mpack_node_t mpack_node_wrap_lookup(mpack_tree_t* tree, mpack_node_data_t* data) { + if (!data) { + if (tree->error == mpack_ok) + mpack_tree_flag_error(tree, mpack_error_data); + return mpack_tree_nil_node(tree); + } + return mpack_node(tree, data); +} + +static mpack_node_t mpack_node_wrap_lookup_optional(mpack_tree_t* tree, mpack_node_data_t* data) { + if (!data) { + if (tree->error == mpack_ok) + return mpack_tree_missing_node(tree); + return mpack_tree_nil_node(tree); + } + return mpack_node(tree, data); +} + +mpack_node_t mpack_node_map_int(mpack_node_t node, int64_t num) { + return mpack_node_wrap_lookup(node.tree, mpack_node_map_int_impl(node, num)); +} + +mpack_node_t mpack_node_map_int_optional(mpack_node_t node, int64_t num) { + return mpack_node_wrap_lookup_optional(node.tree, mpack_node_map_int_impl(node, num)); +} + +mpack_node_t mpack_node_map_uint(mpack_node_t node, uint64_t num) { + return mpack_node_wrap_lookup(node.tree, mpack_node_map_uint_impl(node, num)); +} + +mpack_node_t mpack_node_map_uint_optional(mpack_node_t node, uint64_t num) { + return mpack_node_wrap_lookup_optional(node.tree, mpack_node_map_uint_impl(node, num)); +} + +mpack_node_t mpack_node_map_str(mpack_node_t node, const char* str, size_t length) { + return mpack_node_wrap_lookup(node.tree, mpack_node_map_str_impl(node, str, length)); +} + +mpack_node_t mpack_node_map_str_optional(mpack_node_t node, const char* str, size_t length) { + return mpack_node_wrap_lookup_optional(node.tree, mpack_node_map_str_impl(node, str, length)); +} + +mpack_node_t mpack_node_map_cstr(mpack_node_t node, const char* cstr) { + mpack_assert(cstr != NULL, "cstr is NULL"); + return mpack_node_map_str(node, cstr, mpack_strlen(cstr)); +} + +mpack_node_t mpack_node_map_cstr_optional(mpack_node_t node, const char* cstr) { + mpack_assert(cstr != NULL, "cstr is NULL"); + return mpack_node_map_str_optional(node, cstr, mpack_strlen(cstr)); +} + +bool mpack_node_map_contains_int(mpack_node_t node, int64_t num) { + return mpack_node_map_int_impl(node, num) != NULL; +} + +bool mpack_node_map_contains_uint(mpack_node_t node, uint64_t num) { + return mpack_node_map_uint_impl(node, num) != NULL; +} + +bool mpack_node_map_contains_str(mpack_node_t node, const char* str, size_t length) { + return mpack_node_map_str_impl(node, str, length) != NULL; +} + +bool mpack_node_map_contains_cstr(mpack_node_t node, const char* cstr) { + mpack_assert(cstr != NULL, "cstr is NULL"); + return mpack_node_map_contains_str(node, cstr, mpack_strlen(cstr)); +} + +size_t mpack_node_enum_optional(mpack_node_t node, const char* strings[], size_t count) { + if (mpack_node_error(node) != mpack_ok) + return count; + + // the value is only recognized if it is a string + if (mpack_node_type(node) != mpack_type_str) + return count; + + // fetch the string + const char* key = mpack_node_str(node); + size_t keylen = mpack_node_strlen(node); + mpack_assert(mpack_node_error(node) == mpack_ok, "these should not fail"); + + // find what key it matches + for (size_t i = 0; i < count; ++i) { + const char* other = strings[i]; + size_t otherlen = mpack_strlen(other); + if (keylen == otherlen && mpack_memcmp(key, other, keylen) == 0) + return i; + } + + // no matches + return count; +} + +size_t mpack_node_enum(mpack_node_t node, const char* strings[], size_t count) { + size_t value = mpack_node_enum_optional(node, strings, count); + if (value == count) + mpack_node_flag_error(node, mpack_error_type); + return value; +} + +mpack_type_t mpack_node_type(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return mpack_type_nil; + return node.data->type; +} + +bool mpack_node_is_nil(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) { + // All nodes are treated as nil nodes when we are in error. + return true; + } + return node.data->type == mpack_type_nil; +} + +bool mpack_node_is_missing(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) { + // errors still return nil nodes, not missing nodes. + return false; + } + return node.data->type == mpack_type_missing; +} + +void mpack_node_nil(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return; + if (node.data->type != mpack_type_nil) + mpack_node_flag_error(node, mpack_error_type); +} + +void mpack_node_missing(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return; + if (node.data->type != mpack_type_missing) + mpack_node_flag_error(node, mpack_error_type); +} + +bool mpack_node_bool(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return false; + + if (node.data->type == mpack_type_bool) + return node.data->value.b; + + mpack_node_flag_error(node, mpack_error_type); + return false; +} + +void mpack_node_true(mpack_node_t node) { + if (mpack_node_bool(node) != true) + mpack_node_flag_error(node, mpack_error_type); +} + +void mpack_node_false(mpack_node_t node) { + if (mpack_node_bool(node) != false) + mpack_node_flag_error(node, mpack_error_type); +} + +uint8_t mpack_node_u8(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type == mpack_type_uint) { + if (node.data->value.u <= UINT8_MAX) + return (uint8_t)node.data->value.u; + } else if (node.data->type == mpack_type_int) { + if (node.data->value.i >= 0 && node.data->value.i <= UINT8_MAX) + return (uint8_t)node.data->value.i; + } + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +int8_t mpack_node_i8(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type == mpack_type_uint) { + if (node.data->value.u <= INT8_MAX) + return (int8_t)node.data->value.u; + } else if (node.data->type == mpack_type_int) { + if (node.data->value.i >= INT8_MIN && node.data->value.i <= INT8_MAX) + return (int8_t)node.data->value.i; + } + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +uint16_t mpack_node_u16(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type == mpack_type_uint) { + if (node.data->value.u <= UINT16_MAX) + return (uint16_t)node.data->value.u; + } else if (node.data->type == mpack_type_int) { + if (node.data->value.i >= 0 && node.data->value.i <= UINT16_MAX) + return (uint16_t)node.data->value.i; + } + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +int16_t mpack_node_i16(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type == mpack_type_uint) { + if (node.data->value.u <= INT16_MAX) + return (int16_t)node.data->value.u; + } else if (node.data->type == mpack_type_int) { + if (node.data->value.i >= INT16_MIN && node.data->value.i <= INT16_MAX) + return (int16_t)node.data->value.i; + } + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +uint32_t mpack_node_u32(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type == mpack_type_uint) { + if (node.data->value.u <= UINT32_MAX) + return (uint32_t)node.data->value.u; + } else if (node.data->type == mpack_type_int) { + if (node.data->value.i >= 0 && node.data->value.i <= UINT32_MAX) + return (uint32_t)node.data->value.i; + } + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +int32_t mpack_node_i32(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type == mpack_type_uint) { + if (node.data->value.u <= INT32_MAX) + return (int32_t)node.data->value.u; + } else if (node.data->type == mpack_type_int) { + if (node.data->value.i >= INT32_MIN && node.data->value.i <= INT32_MAX) + return (int32_t)node.data->value.i; + } + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +uint64_t mpack_node_u64(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type == mpack_type_uint) { + return node.data->value.u; + } else if (node.data->type == mpack_type_int) { + if (node.data->value.i >= 0) + return (uint64_t)node.data->value.i; + } + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +int64_t mpack_node_i64(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type == mpack_type_uint) { + if (node.data->value.u <= (uint64_t)INT64_MAX) + return (int64_t)node.data->value.u; + } else if (node.data->type == mpack_type_int) { + return node.data->value.i; + } + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +unsigned int mpack_node_uint(mpack_node_t node) { + + // This should be true at compile-time, so this just wraps the 32-bit function. + if (sizeof(unsigned int) == 4) + return (unsigned int)mpack_node_u32(node); + + // Otherwise we use u64 and check the range. + uint64_t val = mpack_node_u64(node); + if (val <= UINT_MAX) + return (unsigned int)val; + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +int mpack_node_int(mpack_node_t node) { + + // This should be true at compile-time, so this just wraps the 32-bit function. + if (sizeof(int) == 4) + return (int)mpack_node_i32(node); + + // Otherwise we use i64 and check the range. + int64_t val = mpack_node_i64(node); + if (val >= INT_MIN && val <= INT_MAX) + return (int)val; + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +float mpack_node_float(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0.0f; + + if (node.data->type == mpack_type_uint) + return (float)node.data->value.u; + else if (node.data->type == mpack_type_int) + return (float)node.data->value.i; + else if (node.data->type == mpack_type_float) + return node.data->value.f; + else if (node.data->type == mpack_type_double) + return (float)node.data->value.d; + + mpack_node_flag_error(node, mpack_error_type); + return 0.0f; +} + +double mpack_node_double(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0.0; + + if (node.data->type == mpack_type_uint) + return (double)node.data->value.u; + else if (node.data->type == mpack_type_int) + return (double)node.data->value.i; + else if (node.data->type == mpack_type_float) + return (double)node.data->value.f; + else if (node.data->type == mpack_type_double) + return node.data->value.d; + + mpack_node_flag_error(node, mpack_error_type); + return 0.0; +} + +float mpack_node_float_strict(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0.0f; + + if (node.data->type == mpack_type_float) + return node.data->value.f; + + mpack_node_flag_error(node, mpack_error_type); + return 0.0f; +} + +double mpack_node_double_strict(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0.0; + + if (node.data->type == mpack_type_float) + return (double)node.data->value.f; + else if (node.data->type == mpack_type_double) + return node.data->value.d; + + mpack_node_flag_error(node, mpack_error_type); + return 0.0; +} + +#if MPACK_EXTENSIONS +int8_t mpack_node_exttype(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type == mpack_type_ext) + return mpack_node_exttype_unchecked(node); + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} +#endif + +uint32_t mpack_node_data_len(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + mpack_type_t type = node.data->type; + if (type == mpack_type_str || type == mpack_type_bin + #if MPACK_EXTENSIONS + || type == mpack_type_ext + #endif + ) + return (uint32_t)node.data->len; + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +size_t mpack_node_strlen(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type == mpack_type_str) + return (size_t)node.data->len; + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +const char* mpack_node_str(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return NULL; + + mpack_type_t type = node.data->type; + if (type == mpack_type_str) + return mpack_node_data_unchecked(node); + + mpack_node_flag_error(node, mpack_error_type); + return NULL; +} + +const char* mpack_node_data(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return NULL; + + mpack_type_t type = node.data->type; + if (type == mpack_type_str || type == mpack_type_bin + #if MPACK_EXTENSIONS + || type == mpack_type_ext + #endif + ) + return mpack_node_data_unchecked(node); + + mpack_node_flag_error(node, mpack_error_type); + return NULL; +} + +const char* mpack_node_bin_data(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return NULL; + + if (node.data->type == mpack_type_bin) + return mpack_node_data_unchecked(node); + + mpack_node_flag_error(node, mpack_error_type); + return NULL; +} + +size_t mpack_node_bin_size(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type == mpack_type_bin) + return (size_t)node.data->len; + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +size_t mpack_node_array_length(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type != mpack_type_array) { + mpack_node_flag_error(node, mpack_error_type); + return 0; + } + + return (size_t)node.data->len; +} + +mpack_node_t mpack_node_array_at(mpack_node_t node, size_t index) { + if (mpack_node_error(node) != mpack_ok) + return mpack_tree_nil_node(node.tree); + + if (node.data->type != mpack_type_array) { + mpack_node_flag_error(node, mpack_error_type); + return mpack_tree_nil_node(node.tree); + } + + if (index >= node.data->len) { + mpack_node_flag_error(node, mpack_error_data); + return mpack_tree_nil_node(node.tree); + } + + return mpack_node(node.tree, mpack_node_child(node, index)); +} + +size_t mpack_node_map_count(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type != mpack_type_map) { + mpack_node_flag_error(node, mpack_error_type); + return 0; + } + + return node.data->len; +} + +// internal node map lookup +static mpack_node_t mpack_node_map_at(mpack_node_t node, size_t index, size_t offset) { + if (mpack_node_error(node) != mpack_ok) + return mpack_tree_nil_node(node.tree); + + if (node.data->type != mpack_type_map) { + mpack_node_flag_error(node, mpack_error_type); + return mpack_tree_nil_node(node.tree); + } + + if (index >= node.data->len) { + mpack_node_flag_error(node, mpack_error_data); + return mpack_tree_nil_node(node.tree); + } + + return mpack_node(node.tree, mpack_node_child(node, index * 2 + offset)); +} + +mpack_node_t mpack_node_map_key_at(mpack_node_t node, size_t index) { + return mpack_node_map_at(node, index, 0); +} + +mpack_node_t mpack_node_map_value_at(mpack_node_t node, size_t index) { + return mpack_node_map_at(node, index, 1); +} + +#endif diff --git a/src/lib/mpmalloc.cc b/src/lib/mpmalloc.cc new file mode 100644 index 0000000..2483a3a --- /dev/null +++ b/src/lib/mpmalloc.cc @@ -0,0 +1,42 @@ +#include <stdlib.h> +#include "driver/stdout.h" +#include "lib/mpmalloc.h" + +void* mpcalloc(size_t nmemb, size_t size) +{ + void* ret = calloc(nmemb, size); +#ifdef ADDR_20BIT + kout << "calloc:" << dec << (uint32_t)nmemb << "x" << (uint32_t)size << "@" << ret << endl; +#else + kout << "calloc:" << dec << nmemb << "x" << size << "@" << ret << endl; +#endif + return ret; +} + +void* mpmalloc(size_t size) +{ + void* ret = malloc(size); +#ifdef ADDR_20BIT + kout << "malloc:" << dec << (uint32_t)size << "@" << ret << endl; +#else + kout << "malloc:" << dec << size << "@" << ret << endl; +#endif + return ret; +} + +void* mprealloc(void* addr, size_t size) +{ + void* ret = realloc(addr, size); +#ifdef ADDR_20BIT + kout << "realloc:" << addr << ":" << dec << (uint32_t)size << "@" << ret << endl; +#else + kout << "realloc:" << addr << ":" << dec << size << "@" << ret << endl; +#endif + return ret; +} + +void mpfree(void* addr) +{ + kout << "free:" << addr << endl; + free(addr); +} diff --git a/src/lib/nanopb/pb_common.cc b/src/lib/nanopb/pb_common.cc new file mode 100644 index 0000000..4fb7186 --- /dev/null +++ b/src/lib/nanopb/pb_common.cc @@ -0,0 +1,97 @@ +/* pb_common.c: Common support functions for pb_encode.c and pb_decode.c. + * + * 2014 Petteri Aimonen <jpa@kapsi.fi> + */ + +#include "pb_common.h" + +bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_field_t *fields, void *dest_struct) +{ + iter->start = fields; + iter->pos = fields; + iter->required_field_index = 0; + iter->dest_struct = dest_struct; + iter->pData = (char*)dest_struct + iter->pos->data_offset; + iter->pSize = (char*)iter->pData + iter->pos->size_offset; + + return (iter->pos->tag != 0); +} + +bool pb_field_iter_next(pb_field_iter_t *iter) +{ + const pb_field_t *prev_field = iter->pos; + + if (prev_field->tag == 0) + { + /* Handle empty message types, where the first field is already the terminator. + * In other cases, the iter->pos never points to the terminator. */ + return false; + } + + iter->pos++; + + if (iter->pos->tag == 0) + { + /* Wrapped back to beginning, reinitialize */ + (void)pb_field_iter_begin(iter, iter->start, iter->dest_struct); + return false; + } + else + { + /* Increment the pointers based on previous field size */ + size_t prev_size = prev_field->data_size; + + if (PB_HTYPE(prev_field->type) == PB_HTYPE_ONEOF && + PB_HTYPE(iter->pos->type) == PB_HTYPE_ONEOF && + iter->pos->data_offset == PB_SIZE_MAX) + { + /* Don't advance pointers inside unions */ + return true; + } + else if (PB_ATYPE(prev_field->type) == PB_ATYPE_STATIC && + PB_HTYPE(prev_field->type) == PB_HTYPE_REPEATED) + { + /* In static arrays, the data_size tells the size of a single entry and + * array_size is the number of entries */ + prev_size *= prev_field->array_size; + } + else if (PB_ATYPE(prev_field->type) == PB_ATYPE_POINTER) + { + /* Pointer fields always have a constant size in the main structure. + * The data_size only applies to the dynamically allocated area. */ + prev_size = sizeof(void*); + } + + if (PB_HTYPE(prev_field->type) == PB_HTYPE_REQUIRED) + { + /* Count the required fields, in order to check their presence in the + * decoder. */ + iter->required_field_index++; + } + + iter->pData = (char*)iter->pData + prev_size + iter->pos->data_offset; + iter->pSize = (char*)iter->pData + iter->pos->size_offset; + return true; + } +} + +bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag) +{ + const pb_field_t *start = iter->pos; + + do { + if (iter->pos->tag == tag && + PB_LTYPE(iter->pos->type) != PB_LTYPE_EXTENSION) + { + /* Found the wanted field */ + return true; + } + + (void)pb_field_iter_next(iter); + } while (iter->pos != start); + + /* Searched all the way back to start, and found nothing. */ + return false; +} + + diff --git a/src/lib/nanopb/pb_decode.cc b/src/lib/nanopb/pb_decode.cc new file mode 100644 index 0000000..d08259c --- /dev/null +++ b/src/lib/nanopb/pb_decode.cc @@ -0,0 +1,1528 @@ +/* pb_decode.c -- decode a protobuf using minimal resources + * + * 2011 Petteri Aimonen <jpa@kapsi.fi> + */ + +/* Use the GCC warn_unused_result attribute to check that all return values + * are propagated correctly. On other compilers and gcc before 3.4.0 just + * ignore the annotation. + */ +#if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4) + #define checkreturn +#else + #define checkreturn __attribute__((warn_unused_result)) +#endif + +#include "pb.h" +#include "pb_decode.h" +#include "pb_common.h" + +/************************************** + * Declarations internal to this file * + **************************************/ + +typedef bool (*pb_decoder_t)(pb_istream_t *stream, const pb_field_t *field, void *dest) checkreturn; + +static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count); +static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, pb_byte_t *buf, size_t *size); +static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter); +static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter); +static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter); +static void iter_from_extension(pb_field_iter_t *iter, pb_extension_t *extension); +static bool checkreturn default_extension_decoder(pb_istream_t *stream, pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type); +static bool checkreturn decode_extension(pb_istream_t *stream, uint32_t tag, pb_wire_type_t wire_type, pb_field_iter_t *iter); +static bool checkreturn find_extension_field(pb_field_iter_t *iter); +static void pb_field_set_to_default(pb_field_iter_t *iter); +static void pb_message_set_to_defaults(const pb_field_t fields[], void *dest_struct); +static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_t *field, void *dest); +static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof); +static bool checkreturn pb_dec_uvarint(pb_istream_t *stream, const pb_field_t *field, void *dest); +static bool checkreturn pb_dec_svarint(pb_istream_t *stream, const pb_field_t *field, void *dest); +static bool checkreturn pb_dec_fixed32(pb_istream_t *stream, const pb_field_t *field, void *dest); +static bool checkreturn pb_dec_fixed64(pb_istream_t *stream, const pb_field_t *field, void *dest); +static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest); +static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_t *field, void *dest); +static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_t *field, void *dest); +static bool checkreturn pb_dec_fixed_length_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest); +static bool checkreturn pb_skip_varint(pb_istream_t *stream); +static bool checkreturn pb_skip_string(pb_istream_t *stream); + +#ifdef PB_ENABLE_MALLOC +static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t data_size, size_t array_size); +static bool checkreturn pb_release_union_field(pb_istream_t *stream, pb_field_iter_t *iter); +static void pb_release_single_field(const pb_field_iter_t *iter); +#endif + +#ifdef PB_WITHOUT_64BIT +#define pb_int64_t int32_t +#define pb_uint64_t uint32_t +#else +#define pb_int64_t int64_t +#define pb_uint64_t uint64_t +#endif + +/* --- Function pointers to field decoders --- + * Order in the array must match pb_action_t LTYPE numbering. + */ +static const pb_decoder_t PB_DECODERS[PB_LTYPES_COUNT] = { + &pb_dec_varint, + &pb_dec_uvarint, + &pb_dec_svarint, + &pb_dec_fixed32, + &pb_dec_fixed64, + + &pb_dec_bytes, + &pb_dec_string, + &pb_dec_submessage, + NULL, /* extensions */ + &pb_dec_fixed_length_bytes +}; + +/******************************* + * pb_istream_t implementation * + *******************************/ + +static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count) +{ + size_t i; + const pb_byte_t *source = (const pb_byte_t*)stream->state; + stream->state = (pb_byte_t*)stream->state + count; + + if (buf != NULL) + { + for (i = 0; i < count; i++) + buf[i] = source[i]; + } + + return true; +} + +bool checkreturn pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count) +{ +#ifndef PB_BUFFER_ONLY + if (buf == NULL && stream->callback != buf_read) + { + /* Skip input bytes */ + pb_byte_t tmp[16]; + while (count > 16) + { + if (!pb_read(stream, tmp, 16)) + return false; + + count -= 16; + } + + return pb_read(stream, tmp, count); + } +#endif + + if (stream->bytes_left < count) + PB_RETURN_ERROR(stream, "end-of-stream"); + +#ifndef PB_BUFFER_ONLY + if (!stream->callback(stream, buf, count)) + PB_RETURN_ERROR(stream, "io error"); +#else + if (!buf_read(stream, buf, count)) + return false; +#endif + + stream->bytes_left -= count; + return true; +} + +/* Read a single byte from input stream. buf may not be NULL. + * This is an optimization for the varint decoding. */ +static bool checkreturn pb_readbyte(pb_istream_t *stream, pb_byte_t *buf) +{ + if (stream->bytes_left == 0) + PB_RETURN_ERROR(stream, "end-of-stream"); + +#ifndef PB_BUFFER_ONLY + if (!stream->callback(stream, buf, 1)) + PB_RETURN_ERROR(stream, "io error"); +#else + *buf = *(const pb_byte_t*)stream->state; + stream->state = (pb_byte_t*)stream->state + 1; +#endif + + stream->bytes_left--; + + return true; +} + +pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t bufsize) +{ + pb_istream_t stream; + /* Cast away the const from buf without a compiler error. We are + * careful to use it only in a const manner in the callbacks. + */ + union { + void *state; + const void *c_state; + } state; +#ifdef PB_BUFFER_ONLY + stream.callback = NULL; +#else + stream.callback = &buf_read; +#endif + state.c_state = buf; + stream.state = state.state; + stream.bytes_left = bufsize; +#ifndef PB_NO_ERRMSG + stream.errmsg = NULL; +#endif + return stream; +} + +/******************** + * Helper functions * + ********************/ + +static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof) +{ + pb_byte_t byte; + uint32_t result; + + if (!pb_readbyte(stream, &byte)) + { + if (stream->bytes_left == 0) + { + if (eof) + { + *eof = true; + } + } + + return false; + } + + if ((byte & 0x80) == 0) + { + /* Quick case, 1 byte value */ + result = byte; + } + else + { + /* Multibyte case */ + uint_fast8_t bitpos = 7; + result = byte & 0x7F; + + do + { + if (!pb_readbyte(stream, &byte)) + return false; + + if (bitpos >= 32) + { + /* Note: The varint could have trailing 0x80 bytes, or 0xFF for negative. */ + uint8_t sign_extension = (bitpos < 63) ? 0xFF : 0x01; + + if ((byte & 0x7F) != 0x00 && ((result >> 31) == 0 || byte != sign_extension)) + { + PB_RETURN_ERROR(stream, "varint overflow"); + } + } + else + { + result |= (uint32_t)(byte & 0x7F) << bitpos; + } + bitpos = (uint_fast8_t)(bitpos + 7); + } while (byte & 0x80); + + if (bitpos == 35 && (byte & 0x70) != 0) + { + /* The last byte was at bitpos=28, so only bottom 4 bits fit. */ + PB_RETURN_ERROR(stream, "varint overflow"); + } + } + + *dest = result; + return true; +} + +bool checkreturn pb_decode_varint32(pb_istream_t *stream, uint32_t *dest) +{ + return pb_decode_varint32_eof(stream, dest, NULL); +} + +#ifndef PB_WITHOUT_64BIT +bool checkreturn pb_decode_varint(pb_istream_t *stream, uint64_t *dest) +{ + pb_byte_t byte; + uint_fast8_t bitpos = 0; + uint64_t result = 0; + + do + { + if (bitpos >= 64) + PB_RETURN_ERROR(stream, "varint overflow"); + + if (!pb_readbyte(stream, &byte)) + return false; + + result |= (uint64_t)(byte & 0x7F) << bitpos; + bitpos = (uint_fast8_t)(bitpos + 7); + } while (byte & 0x80); + + *dest = result; + return true; +} +#endif + +bool checkreturn pb_skip_varint(pb_istream_t *stream) +{ + pb_byte_t byte; + do + { + if (!pb_read(stream, &byte, 1)) + return false; + } while (byte & 0x80); + return true; +} + +bool checkreturn pb_skip_string(pb_istream_t *stream) +{ + uint32_t length; + if (!pb_decode_varint32(stream, &length)) + return false; + + return pb_read(stream, NULL, length); +} + +bool checkreturn pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof) +{ + uint32_t temp; + *eof = false; + *wire_type = (pb_wire_type_t) 0; + *tag = 0; + + if (!pb_decode_varint32_eof(stream, &temp, eof)) + { + return false; + } + + if (temp == 0) + { + *eof = true; /* Special feature: allow 0-terminated messages. */ + return false; + } + + *tag = temp >> 3; + *wire_type = (pb_wire_type_t)(temp & 7); + return true; +} + +bool checkreturn pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type) +{ + switch (wire_type) + { + case PB_WT_VARINT: return pb_skip_varint(stream); + case PB_WT_64BIT: return pb_read(stream, NULL, 8); + case PB_WT_STRING: return pb_skip_string(stream); + case PB_WT_32BIT: return pb_read(stream, NULL, 4); + default: PB_RETURN_ERROR(stream, "invalid wire_type"); + } +} + +/* Read a raw value to buffer, for the purpose of passing it to callback as + * a substream. Size is maximum size on call, and actual size on return. + */ +static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, pb_byte_t *buf, size_t *size) +{ + size_t max_size = *size; + switch (wire_type) + { + case PB_WT_VARINT: + *size = 0; + do + { + (*size)++; + if (*size > max_size) return false; + if (!pb_read(stream, buf, 1)) return false; + } while (*buf++ & 0x80); + return true; + + case PB_WT_64BIT: + *size = 8; + return pb_read(stream, buf, 8); + + case PB_WT_32BIT: + *size = 4; + return pb_read(stream, buf, 4); + + case PB_WT_STRING: + /* Calling read_raw_value with a PB_WT_STRING is an error. + * Explicitly handle this case and fallthrough to default to avoid + * compiler warnings. + */ + + default: PB_RETURN_ERROR(stream, "invalid wire_type"); + } +} + +/* Decode string length from stream and return a substream with limited length. + * Remember to close the substream using pb_close_string_substream(). + */ +bool checkreturn pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream) +{ + uint32_t size; + if (!pb_decode_varint32(stream, &size)) + return false; + + *substream = *stream; + if (substream->bytes_left < size) + PB_RETURN_ERROR(stream, "parent stream too short"); + + substream->bytes_left = size; + stream->bytes_left -= size; + return true; +} + +bool checkreturn pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream) +{ + if (substream->bytes_left) { + if (!pb_read(substream, NULL, substream->bytes_left)) + return false; + } + + stream->state = substream->state; + +#ifndef PB_NO_ERRMSG + stream->errmsg = substream->errmsg; +#endif + return true; +} + +/************************* + * Decode a single field * + *************************/ + +static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter) +{ + pb_type_t type; + pb_decoder_t func; + + type = iter->pos->type; + func = PB_DECODERS[PB_LTYPE(type)]; + + switch (PB_HTYPE(type)) + { + case PB_HTYPE_REQUIRED: + return func(stream, iter->pos, iter->pData); + + case PB_HTYPE_OPTIONAL: + if (iter->pSize != iter->pData) + *(bool*)iter->pSize = true; + return func(stream, iter->pos, iter->pData); + + case PB_HTYPE_REPEATED: + if (wire_type == PB_WT_STRING + && PB_LTYPE(type) <= PB_LTYPE_LAST_PACKABLE) + { + /* Packed array */ + bool status = true; + pb_size_t *size = (pb_size_t*)iter->pSize; + + pb_istream_t substream; + if (!pb_make_string_substream(stream, &substream)) + return false; + + while (substream.bytes_left > 0 && *size < iter->pos->array_size) + { + void *pItem = (char*)iter->pData + iter->pos->data_size * (*size); + if (!func(&substream, iter->pos, pItem)) + { + status = false; + break; + } + (*size)++; + } + + if (substream.bytes_left != 0) + PB_RETURN_ERROR(stream, "array overflow"); + if (!pb_close_string_substream(stream, &substream)) + return false; + + return status; + } + else + { + /* Repeated field */ + pb_size_t *size = (pb_size_t*)iter->pSize; + char *pItem = (char*)iter->pData + iter->pos->data_size * (*size); + + if ((*size)++ >= iter->pos->array_size) + PB_RETURN_ERROR(stream, "array overflow"); + + return func(stream, iter->pos, pItem); + } + + case PB_HTYPE_ONEOF: + *(pb_size_t*)iter->pSize = iter->pos->tag; + if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE) + { + /* We memset to zero so that any callbacks are set to NULL. + * Then set any default values. */ + memset(iter->pData, 0, iter->pos->data_size); + pb_message_set_to_defaults((const pb_field_t*)iter->pos->ptr, iter->pData); + } + return func(stream, iter->pos, iter->pData); + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +} + +#ifdef PB_ENABLE_MALLOC +/* Allocate storage for the field and store the pointer at iter->pData. + * array_size is the number of entries to reserve in an array. + * Zero size is not allowed, use pb_free() for releasing. + */ +static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t data_size, size_t array_size) +{ + void *ptr = *(void**)pData; + + if (data_size == 0 || array_size == 0) + PB_RETURN_ERROR(stream, "invalid size"); + + /* Check for multiplication overflows. + * This code avoids the costly division if the sizes are small enough. + * Multiplication is safe as long as only half of bits are set + * in either multiplicand. + */ + { + const size_t check_limit = (size_t)1 << (sizeof(size_t) * 4); + if (data_size >= check_limit || array_size >= check_limit) + { + const size_t size_max = (size_t)-1; + if (size_max / array_size < data_size) + { + PB_RETURN_ERROR(stream, "size too large"); + } + } + } + + /* Allocate new or expand previous allocation */ + /* Note: on failure the old pointer will remain in the structure, + * the message must be freed by caller also on error return. */ + ptr = pb_realloc(ptr, array_size * data_size); + if (ptr == NULL) + PB_RETURN_ERROR(stream, "realloc failed"); + + *(void**)pData = ptr; + return true; +} + +/* Clear a newly allocated item in case it contains a pointer, or is a submessage. */ +static void initialize_pointer_field(void *pItem, pb_field_iter_t *iter) +{ + if (PB_LTYPE(iter->pos->type) == PB_LTYPE_STRING || + PB_LTYPE(iter->pos->type) == PB_LTYPE_BYTES) + { + *(void**)pItem = NULL; + } + else if (PB_LTYPE(iter->pos->type) == PB_LTYPE_SUBMESSAGE) + { + /* We memset to zero so that any callbacks are set to NULL. + * Then set any default values. */ + memset(pItem, 0, iter->pos->data_size); + pb_message_set_to_defaults((const pb_field_t *) iter->pos->ptr, pItem); + } +} +#endif + +static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter) +{ +#ifndef PB_ENABLE_MALLOC + PB_UNUSED(wire_type); + PB_UNUSED(iter); + PB_RETURN_ERROR(stream, "no malloc support"); +#else + pb_type_t type; + pb_decoder_t func; + + type = iter->pos->type; + func = PB_DECODERS[PB_LTYPE(type)]; + + switch (PB_HTYPE(type)) + { + case PB_HTYPE_REQUIRED: + case PB_HTYPE_OPTIONAL: + case PB_HTYPE_ONEOF: + if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE && + *(void**)iter->pData != NULL) + { + /* Duplicate field, have to release the old allocation first. */ + pb_release_single_field(iter); + } + + if (PB_HTYPE(type) == PB_HTYPE_ONEOF) + { + *(pb_size_t*)iter->pSize = iter->pos->tag; + } + + if (PB_LTYPE(type) == PB_LTYPE_STRING || + PB_LTYPE(type) == PB_LTYPE_BYTES) + { + return func(stream, iter->pos, iter->pData); + } + else + { + if (!allocate_field(stream, iter->pData, iter->pos->data_size, 1)) + return false; + + initialize_pointer_field(*(void**)iter->pData, iter); + return func(stream, iter->pos, *(void**)iter->pData); + } + + case PB_HTYPE_REPEATED: + if (wire_type == PB_WT_STRING + && PB_LTYPE(type) <= PB_LTYPE_LAST_PACKABLE) + { + /* Packed array, multiple items come in at once. */ + bool status = true; + pb_size_t *size = (pb_size_t*)iter->pSize; + size_t allocated_size = *size; + void *pItem; + pb_istream_t substream; + + if (!pb_make_string_substream(stream, &substream)) + return false; + + while (substream.bytes_left) + { + if ((size_t)*size + 1 > allocated_size) + { + /* Allocate more storage. This tries to guess the + * number of remaining entries. Round the division + * upwards. */ + allocated_size += (substream.bytes_left - 1) / iter->pos->data_size + 1; + + if (!allocate_field(&substream, iter->pData, iter->pos->data_size, allocated_size)) + { + status = false; + break; + } + } + + /* Decode the array entry */ + pItem = *(char**)iter->pData + iter->pos->data_size * (*size); + initialize_pointer_field(pItem, iter); + if (!func(&substream, iter->pos, pItem)) + { + status = false; + break; + } + + if (*size == PB_SIZE_MAX) + { +#ifndef PB_NO_ERRMSG + stream->errmsg = "too many array entries"; +#endif + status = false; + break; + } + + (*size)++; + } + if (!pb_close_string_substream(stream, &substream)) + return false; + + return status; + } + else + { + /* Normal repeated field, i.e. only one item at a time. */ + pb_size_t *size = (pb_size_t*)iter->pSize; + void *pItem; + + if (*size == PB_SIZE_MAX) + PB_RETURN_ERROR(stream, "too many array entries"); + + (*size)++; + if (!allocate_field(stream, iter->pData, iter->pos->data_size, *size)) + return false; + + pItem = *(char**)iter->pData + iter->pos->data_size * (*size - 1); + initialize_pointer_field(pItem, iter); + return func(stream, iter->pos, pItem); + } + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +#endif +} + +static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter) +{ + pb_callback_t *pCallback = (pb_callback_t*)iter->pData; +#ifdef PB_OLD_CALLBACK_STYLE + void *arg; +#else + void **arg; +#endif + + if (pCallback == NULL || pCallback->funcs.decode == NULL) + return pb_skip_field(stream, wire_type); + +#ifdef PB_OLD_CALLBACK_STYLE + arg = pCallback->arg; +#else + arg = &(pCallback->arg); +#endif + + if (wire_type == PB_WT_STRING) + { + pb_istream_t substream; + + if (!pb_make_string_substream(stream, &substream)) + return false; + + do + { + if (!pCallback->funcs.decode(&substream, iter->pos, arg)) + PB_RETURN_ERROR(stream, "callback failed"); + } while (substream.bytes_left); + + if (!pb_close_string_substream(stream, &substream)) + return false; + + return true; + } + else + { + /* Copy the single scalar value to stack. + * This is required so that we can limit the stream length, + * which in turn allows to use same callback for packed and + * not-packed fields. */ + pb_istream_t substream; + pb_byte_t buffer[10]; + size_t size = sizeof(buffer); + + if (!read_raw_value(stream, wire_type, buffer, &size)) + return false; + substream = pb_istream_from_buffer(buffer, size); + + return pCallback->funcs.decode(&substream, iter->pos, arg); + } +} + +static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter) +{ +#ifdef PB_ENABLE_MALLOC + /* When decoding an oneof field, check if there is old data that must be + * released first. */ + if (PB_HTYPE(iter->pos->type) == PB_HTYPE_ONEOF) + { + if (!pb_release_union_field(stream, iter)) + return false; + } +#endif + + switch (PB_ATYPE(iter->pos->type)) + { + case PB_ATYPE_STATIC: + return decode_static_field(stream, wire_type, iter); + + case PB_ATYPE_POINTER: + return decode_pointer_field(stream, wire_type, iter); + + case PB_ATYPE_CALLBACK: + return decode_callback_field(stream, wire_type, iter); + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +} + +static void iter_from_extension(pb_field_iter_t *iter, pb_extension_t *extension) +{ + /* Fake a field iterator for the extension field. + * It is not actually safe to advance this iterator, but decode_field + * will not even try to. */ + const pb_field_t *field = (const pb_field_t*)extension->type->arg; + (void)pb_field_iter_begin(iter, field, extension->dest); + iter->pData = extension->dest; + iter->pSize = &extension->found; + + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) + { + /* For pointer extensions, the pointer is stored directly + * in the extension structure. This avoids having an extra + * indirection. */ + iter->pData = &extension->dest; + } +} + +/* Default handler for extension fields. Expects a pb_field_t structure + * in extension->type->arg. */ +static bool checkreturn default_extension_decoder(pb_istream_t *stream, + pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type) +{ + const pb_field_t *field = (const pb_field_t*)extension->type->arg; + pb_field_iter_t iter; + + if (field->tag != tag) + return true; + + iter_from_extension(&iter, extension); + extension->found = true; + return decode_field(stream, wire_type, &iter); +} + +/* Try to decode an unknown field as an extension field. Tries each extension + * decoder in turn, until one of them handles the field or loop ends. */ +static bool checkreturn decode_extension(pb_istream_t *stream, + uint32_t tag, pb_wire_type_t wire_type, pb_field_iter_t *iter) +{ + pb_extension_t *extension = *(pb_extension_t* const *)iter->pData; + size_t pos = stream->bytes_left; + + while (extension != NULL && pos == stream->bytes_left) + { + bool status; + if (extension->type->decode) + status = extension->type->decode(stream, extension, tag, wire_type); + else + status = default_extension_decoder(stream, extension, tag, wire_type); + + if (!status) + return false; + + extension = extension->next; + } + + return true; +} + +/* Step through the iterator until an extension field is found or until all + * entries have been checked. There can be only one extension field per + * message. Returns false if no extension field is found. */ +static bool checkreturn find_extension_field(pb_field_iter_t *iter) +{ + const pb_field_t *start = iter->pos; + + do { + if (PB_LTYPE(iter->pos->type) == PB_LTYPE_EXTENSION) + return true; + (void)pb_field_iter_next(iter); + } while (iter->pos != start); + + return false; +} + +/* Initialize message fields to default values, recursively */ +static void pb_field_set_to_default(pb_field_iter_t *iter) +{ + pb_type_t type; + type = iter->pos->type; + + if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) + { + pb_extension_t *ext = *(pb_extension_t* const *)iter->pData; + while (ext != NULL) + { + pb_field_iter_t ext_iter; + ext->found = false; + iter_from_extension(&ext_iter, ext); + pb_field_set_to_default(&ext_iter); + ext = ext->next; + } + } + else if (PB_ATYPE(type) == PB_ATYPE_STATIC) + { + bool init_data = true; + if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && iter->pSize != iter->pData) + { + /* Set has_field to false. Still initialize the optional field + * itself also. */ + *(bool*)iter->pSize = false; + } + else if (PB_HTYPE(type) == PB_HTYPE_REPEATED || + PB_HTYPE(type) == PB_HTYPE_ONEOF) + { + /* REPEATED: Set array count to 0, no need to initialize contents. + ONEOF: Set which_field to 0. */ + *(pb_size_t*)iter->pSize = 0; + init_data = false; + } + + if (init_data) + { + if (PB_LTYPE(iter->pos->type) == PB_LTYPE_SUBMESSAGE) + { + /* Initialize submessage to defaults */ + pb_message_set_to_defaults((const pb_field_t *) iter->pos->ptr, iter->pData); + } + else if (iter->pos->ptr != NULL) + { + /* Initialize to default value */ + memcpy(iter->pData, iter->pos->ptr, iter->pos->data_size); + } + else + { + /* Initialize to zeros */ + memset(iter->pData, 0, iter->pos->data_size); + } + } + } + else if (PB_ATYPE(type) == PB_ATYPE_POINTER) + { + /* Initialize the pointer to NULL. */ + *(void**)iter->pData = NULL; + + /* Initialize array count to 0. */ + if (PB_HTYPE(type) == PB_HTYPE_REPEATED || + PB_HTYPE(type) == PB_HTYPE_ONEOF) + { + *(pb_size_t*)iter->pSize = 0; + } + } + else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK) + { + /* Don't overwrite callback */ + } +} + +static void pb_message_set_to_defaults(const pb_field_t fields[], void *dest_struct) +{ + pb_field_iter_t iter; + + if (!pb_field_iter_begin(&iter, fields, dest_struct)) + return; /* Empty message type */ + + do + { + pb_field_set_to_default(&iter); + } while (pb_field_iter_next(&iter)); +} + +/********************* + * Decode all fields * + *********************/ + +bool checkreturn pb_decode_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct) +{ + uint32_t fields_seen[(PB_MAX_REQUIRED_FIELDS + 31) / 32] = {0, 0}; + const uint32_t allbits = ~(uint32_t)0; + uint32_t extension_range_start = 0; + pb_field_iter_t iter; + + /* 'fixed_count_field' and 'fixed_count_size' track position of a repeated fixed + * count field. This can only handle _one_ repeated fixed count field that + * is unpacked and unordered among other (non repeated fixed count) fields. + */ + const pb_field_t *fixed_count_field = NULL; + pb_size_t fixed_count_size = 0; + + /* Return value ignored, as empty message types will be correctly handled by + * pb_field_iter_find() anyway. */ + (void)pb_field_iter_begin(&iter, fields, dest_struct); + + while (stream->bytes_left) + { + uint32_t tag; + pb_wire_type_t wire_type; + bool eof; + + if (!pb_decode_tag(stream, &wire_type, &tag, &eof)) + { + if (eof) + break; + else + return false; + } + + if (!pb_field_iter_find(&iter, tag)) + { + /* No match found, check if it matches an extension. */ + if (tag >= extension_range_start) + { + if (!find_extension_field(&iter)) + extension_range_start = (uint32_t)-1; + else + extension_range_start = iter.pos->tag; + + if (tag >= extension_range_start) + { + size_t pos = stream->bytes_left; + + if (!decode_extension(stream, tag, wire_type, &iter)) + return false; + + if (pos != stream->bytes_left) + { + /* The field was handled */ + continue; + } + } + } + + /* No match found, skip data */ + if (!pb_skip_field(stream, wire_type)) + return false; + continue; + } + + /* If a repeated fixed count field was found, get size from + * 'fixed_count_field' as there is no counter contained in the struct. + */ + if (PB_HTYPE(iter.pos->type) == PB_HTYPE_REPEATED + && iter.pSize == iter.pData) + { + if (fixed_count_field != iter.pos) { + /* If the new fixed count field does not match the previous one, + * check that the previous one is NULL or that it finished + * receiving all the expected data. + */ + if (fixed_count_field != NULL && + fixed_count_size != fixed_count_field->array_size) + { + PB_RETURN_ERROR(stream, "wrong size for fixed count field"); + } + + fixed_count_field = iter.pos; + fixed_count_size = 0; + } + + iter.pSize = &fixed_count_size; + } + + if (PB_HTYPE(iter.pos->type) == PB_HTYPE_REQUIRED + && iter.required_field_index < PB_MAX_REQUIRED_FIELDS) + { + uint32_t tmp = ((uint32_t)1 << (iter.required_field_index & 31)); + fields_seen[iter.required_field_index >> 5] |= tmp; + } + + if (!decode_field(stream, wire_type, &iter)) + return false; + } + + /* Check that all elements of the last decoded fixed count field were present. */ + if (fixed_count_field != NULL && + fixed_count_size != fixed_count_field->array_size) + { + PB_RETURN_ERROR(stream, "wrong size for fixed count field"); + } + + /* Check that all required fields were present. */ + { + /* First figure out the number of required fields by + * seeking to the end of the field array. Usually we + * are already close to end after decoding. + */ + unsigned req_field_count; + pb_type_t last_type; + unsigned i; + do { + req_field_count = iter.required_field_index; + last_type = iter.pos->type; + } while (pb_field_iter_next(&iter)); + + /* Fixup if last field was also required. */ + if (PB_HTYPE(last_type) == PB_HTYPE_REQUIRED && iter.pos->tag != 0) + req_field_count++; + + if (req_field_count > PB_MAX_REQUIRED_FIELDS) + req_field_count = PB_MAX_REQUIRED_FIELDS; + + if (req_field_count > 0) + { + /* Check the whole words */ + for (i = 0; i < (req_field_count >> 5); i++) + { + if (fields_seen[i] != allbits) + PB_RETURN_ERROR(stream, "missing required field"); + } + + /* Check the remaining bits (if any) */ + if ((req_field_count & 31) != 0) + { + if (fields_seen[req_field_count >> 5] != + (allbits >> (32 - (req_field_count & 31)))) + { + PB_RETURN_ERROR(stream, "missing required field"); + } + } + } + } + + return true; +} + +bool checkreturn pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct) +{ + bool status; + pb_message_set_to_defaults(fields, dest_struct); + status = pb_decode_noinit(stream, fields, dest_struct); + +#ifdef PB_ENABLE_MALLOC + if (!status) + pb_release(fields, dest_struct); +#endif + + return status; +} + +bool pb_decode_delimited_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct) +{ + pb_istream_t substream; + bool status; + + if (!pb_make_string_substream(stream, &substream)) + return false; + + status = pb_decode_noinit(&substream, fields, dest_struct); + + if (!pb_close_string_substream(stream, &substream)) + return false; + return status; +} + +bool pb_decode_delimited(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct) +{ + pb_istream_t substream; + bool status; + + if (!pb_make_string_substream(stream, &substream)) + return false; + + status = pb_decode(&substream, fields, dest_struct); + + if (!pb_close_string_substream(stream, &substream)) + return false; + return status; +} + +bool pb_decode_nullterminated(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct) +{ + /* This behaviour will be separated in nanopb-0.4.0, see issue #278. */ + return pb_decode(stream, fields, dest_struct); +} + +#ifdef PB_ENABLE_MALLOC +/* Given an oneof field, if there has already been a field inside this oneof, + * release it before overwriting with a different one. */ +static bool pb_release_union_field(pb_istream_t *stream, pb_field_iter_t *iter) +{ + pb_size_t old_tag = *(pb_size_t*)iter->pSize; /* Previous which_ value */ + pb_size_t new_tag = iter->pos->tag; /* New which_ value */ + + if (old_tag == 0) + return true; /* Ok, no old data in union */ + + if (old_tag == new_tag) + return true; /* Ok, old data is of same type => merge */ + + /* Release old data. The find can fail if the message struct contains + * invalid data. */ + if (!pb_field_iter_find(iter, old_tag)) + PB_RETURN_ERROR(stream, "invalid union tag"); + + pb_release_single_field(iter); + + /* Restore iterator to where it should be. + * This shouldn't fail unless the pb_field_t structure is corrupted. */ + if (!pb_field_iter_find(iter, new_tag)) + PB_RETURN_ERROR(stream, "iterator error"); + + return true; +} + +static void pb_release_single_field(const pb_field_iter_t *iter) +{ + pb_type_t type; + type = iter->pos->type; + + if (PB_HTYPE(type) == PB_HTYPE_ONEOF) + { + if (*(pb_size_t*)iter->pSize != iter->pos->tag) + return; /* This is not the current field in the union */ + } + + /* Release anything contained inside an extension or submsg. + * This has to be done even if the submsg itself is statically + * allocated. */ + if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) + { + /* Release fields from all extensions in the linked list */ + pb_extension_t *ext = *(pb_extension_t**)iter->pData; + while (ext != NULL) + { + pb_field_iter_t ext_iter; + iter_from_extension(&ext_iter, ext); + pb_release_single_field(&ext_iter); + ext = ext->next; + } + } + else if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE && PB_ATYPE(type) != PB_ATYPE_CALLBACK) + { + /* Release fields in submessage or submsg array */ + void *pItem = iter->pData; + pb_size_t count = 1; + + if (PB_ATYPE(type) == PB_ATYPE_POINTER) + { + pItem = *(void**)iter->pData; + } + + if (PB_HTYPE(type) == PB_HTYPE_REPEATED) + { + if (PB_ATYPE(type) == PB_ATYPE_STATIC && iter->pSize == iter->pData) { + /* No _count field so use size of the array */ + count = iter->pos->array_size; + } else { + count = *(pb_size_t*)iter->pSize; + } + + if (PB_ATYPE(type) == PB_ATYPE_STATIC && count > iter->pos->array_size) + { + /* Protect against corrupted _count fields */ + count = iter->pos->array_size; + } + } + + if (pItem) + { + while (count--) + { + pb_release((const pb_field_t*)iter->pos->ptr, pItem); + pItem = (char*)pItem + iter->pos->data_size; + } + } + } + + if (PB_ATYPE(type) == PB_ATYPE_POINTER) + { + if (PB_HTYPE(type) == PB_HTYPE_REPEATED && + (PB_LTYPE(type) == PB_LTYPE_STRING || + PB_LTYPE(type) == PB_LTYPE_BYTES)) + { + /* Release entries in repeated string or bytes array */ + void **pItem = *(void***)iter->pData; + pb_size_t count = *(pb_size_t*)iter->pSize; + while (count--) + { + pb_free(*pItem); + *pItem++ = NULL; + } + } + + if (PB_HTYPE(type) == PB_HTYPE_REPEATED) + { + /* We are going to release the array, so set the size to 0 */ + *(pb_size_t*)iter->pSize = 0; + } + + /* Release main item */ + pb_free(*(void**)iter->pData); + *(void**)iter->pData = NULL; + } +} + +void pb_release(const pb_field_t fields[], void *dest_struct) +{ + pb_field_iter_t iter; + + if (!dest_struct) + return; /* Ignore NULL pointers, similar to free() */ + + if (!pb_field_iter_begin(&iter, fields, dest_struct)) + return; /* Empty message type */ + + do + { + pb_release_single_field(&iter); + } while (pb_field_iter_next(&iter)); +} +#endif + +/* Field decoders */ + +bool pb_decode_svarint(pb_istream_t *stream, pb_int64_t *dest) +{ + pb_uint64_t value; + if (!pb_decode_varint(stream, &value)) + return false; + + if (value & 1) + *dest = (pb_int64_t)(~(value >> 1)); + else + *dest = (pb_int64_t)(value >> 1); + + return true; +} + +bool pb_decode_fixed32(pb_istream_t *stream, void *dest) +{ + union { + uint32_t fixed32; + pb_byte_t bytes[4]; + } u; + + if (!pb_read(stream, u.bytes, 4)) + return false; + +#if defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN && CHAR_BIT == 8 + /* fast path - if we know that we're on little endian, assign directly */ + *(uint32_t*)dest = u.fixed32; +#else + *(uint32_t*)dest = ((uint32_t)u.bytes[0] << 0) | + ((uint32_t)u.bytes[1] << 8) | + ((uint32_t)u.bytes[2] << 16) | + ((uint32_t)u.bytes[3] << 24); +#endif + return true; +} + +#ifndef PB_WITHOUT_64BIT +bool pb_decode_fixed64(pb_istream_t *stream, void *dest) +{ + union { + uint64_t fixed64; + pb_byte_t bytes[8]; + } u; + + if (!pb_read(stream, u.bytes, 8)) + return false; + +#if defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN && CHAR_BIT == 8 + /* fast path - if we know that we're on little endian, assign directly */ + *(uint64_t*)dest = u.fixed64; +#else + *(uint64_t*)dest = ((uint64_t)u.bytes[0] << 0) | + ((uint64_t)u.bytes[1] << 8) | + ((uint64_t)u.bytes[2] << 16) | + ((uint64_t)u.bytes[3] << 24) | + ((uint64_t)u.bytes[4] << 32) | + ((uint64_t)u.bytes[5] << 40) | + ((uint64_t)u.bytes[6] << 48) | + ((uint64_t)u.bytes[7] << 56); +#endif + return true; +} +#endif + +static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + pb_uint64_t value; + pb_int64_t svalue; + pb_int64_t clamped; + if (!pb_decode_varint(stream, &value)) + return false; + + /* See issue 97: Google's C++ protobuf allows negative varint values to + * be cast as int32_t, instead of the int64_t that should be used when + * encoding. Previous nanopb versions had a bug in encoding. In order to + * not break decoding of such messages, we cast <=32 bit fields to + * int32_t first to get the sign correct. + */ + if (field->data_size == sizeof(pb_int64_t)) + svalue = (pb_int64_t)value; + else + svalue = (int32_t)value; + + /* Cast to the proper field size, while checking for overflows */ + if (field->data_size == sizeof(pb_int64_t)) + clamped = *(pb_int64_t*)dest = svalue; + else if (field->data_size == sizeof(int32_t)) + clamped = *(int32_t*)dest = (int32_t)svalue; + else if (field->data_size == sizeof(int_least16_t)) + clamped = *(int_least16_t*)dest = (int_least16_t)svalue; + else if (field->data_size == sizeof(int_least8_t)) + clamped = *(int_least8_t*)dest = (int_least8_t)svalue; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + + if (clamped != svalue) + PB_RETURN_ERROR(stream, "integer too large"); + + return true; +} + +static bool checkreturn pb_dec_uvarint(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + pb_uint64_t value, clamped; + if (!pb_decode_varint(stream, &value)) + return false; + + /* Cast to the proper field size, while checking for overflows */ + if (field->data_size == sizeof(pb_uint64_t)) + clamped = *(pb_uint64_t*)dest = value; + else if (field->data_size == sizeof(uint32_t)) + clamped = *(uint32_t*)dest = (uint32_t)value; + else if (field->data_size == sizeof(uint_least16_t)) + clamped = *(uint_least16_t*)dest = (uint_least16_t)value; + else if (field->data_size == sizeof(uint_least8_t)) + clamped = *(uint_least8_t*)dest = (uint_least8_t)value; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + + if (clamped != value) + PB_RETURN_ERROR(stream, "integer too large"); + + return true; +} + +static bool checkreturn pb_dec_svarint(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + pb_int64_t value, clamped; + if (!pb_decode_svarint(stream, &value)) + return false; + + /* Cast to the proper field size, while checking for overflows */ + if (field->data_size == sizeof(pb_int64_t)) + clamped = *(pb_int64_t*)dest = value; + else if (field->data_size == sizeof(int32_t)) + clamped = *(int32_t*)dest = (int32_t)value; + else if (field->data_size == sizeof(int_least16_t)) + clamped = *(int_least16_t*)dest = (int_least16_t)value; + else if (field->data_size == sizeof(int_least8_t)) + clamped = *(int_least8_t*)dest = (int_least8_t)value; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + + if (clamped != value) + PB_RETURN_ERROR(stream, "integer too large"); + + return true; +} + +static bool checkreturn pb_dec_fixed32(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + PB_UNUSED(field); + return pb_decode_fixed32(stream, dest); +} + +static bool checkreturn pb_dec_fixed64(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + PB_UNUSED(field); +#ifndef PB_WITHOUT_64BIT + return pb_decode_fixed64(stream, dest); +#else + PB_UNUSED(dest); + PB_RETURN_ERROR(stream, "no 64bit support"); +#endif +} + +static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + uint32_t size; + size_t alloc_size; + pb_bytes_array_t *bdest; + + if (!pb_decode_varint32(stream, &size)) + return false; + + if (size > PB_SIZE_MAX) + PB_RETURN_ERROR(stream, "bytes overflow"); + + alloc_size = PB_BYTES_ARRAY_T_ALLOCSIZE(size); + if (size > alloc_size) + PB_RETURN_ERROR(stream, "size too large"); + + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) + { +#ifndef PB_ENABLE_MALLOC + PB_RETURN_ERROR(stream, "no malloc support"); +#else + if (!allocate_field(stream, dest, alloc_size, 1)) + return false; + bdest = *(pb_bytes_array_t**)dest; +#endif + } + else + { + if (alloc_size > field->data_size) + PB_RETURN_ERROR(stream, "bytes overflow"); + bdest = (pb_bytes_array_t*)dest; + } + + bdest->size = (pb_size_t)size; + return pb_read(stream, bdest->bytes, size); +} + +static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + uint32_t size; + size_t alloc_size; + bool status; + if (!pb_decode_varint32(stream, &size)) + return false; + + /* Space for null terminator */ + alloc_size = size + 1; + + if (alloc_size < size) + PB_RETURN_ERROR(stream, "size too large"); + + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) + { +#ifndef PB_ENABLE_MALLOC + PB_RETURN_ERROR(stream, "no malloc support"); +#else + if (!allocate_field(stream, dest, alloc_size, 1)) + return false; + dest = *(void**)dest; +#endif + } + else + { + if (alloc_size > field->data_size) + PB_RETURN_ERROR(stream, "string overflow"); + } + + status = pb_read(stream, (pb_byte_t*)dest, size); + *((pb_byte_t*)dest + size) = 0; + return status; +} + +static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + bool status; + pb_istream_t substream; + const pb_field_t* submsg_fields = (const pb_field_t*)field->ptr; + + if (!pb_make_string_substream(stream, &substream)) + return false; + + if (field->ptr == NULL) + PB_RETURN_ERROR(stream, "invalid field descriptor"); + + /* New array entries need to be initialized, while required and optional + * submessages have already been initialized in the top-level pb_decode. */ + if (PB_HTYPE(field->type) == PB_HTYPE_REPEATED) + status = pb_decode(&substream, submsg_fields, dest); + else + status = pb_decode_noinit(&substream, submsg_fields, dest); + + if (!pb_close_string_substream(stream, &substream)) + return false; + return status; +} + +static bool checkreturn pb_dec_fixed_length_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + uint32_t size; + + if (!pb_decode_varint32(stream, &size)) + return false; + + if (size > PB_SIZE_MAX) + PB_RETURN_ERROR(stream, "bytes overflow"); + + if (size == 0) + { + /* As a special case, treat empty bytes string as all zeros for fixed_length_bytes. */ + memset(dest, 0, field->data_size); + return true; + } + + if (size != field->data_size) + PB_RETURN_ERROR(stream, "incorrect fixed length bytes size"); + + return pb_read(stream, (pb_byte_t*)dest, field->data_size); +} diff --git a/src/lib/nanopb/pb_encode.cc b/src/lib/nanopb/pb_encode.cc new file mode 100644 index 0000000..f37ceec --- /dev/null +++ b/src/lib/nanopb/pb_encode.cc @@ -0,0 +1,893 @@ +/* pb_encode.c -- encode a protobuf using minimal resources + * + * 2011 Petteri Aimonen <jpa@kapsi.fi> + */ + +#include "pb.h" +#include "pb_encode.h" +#include "pb_common.h" + +/* Use the GCC warn_unused_result attribute to check that all return values + * are propagated correctly. On other compilers and gcc before 3.4.0 just + * ignore the annotation. + */ +#if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4) + #define checkreturn +#else + #define checkreturn __attribute__((warn_unused_result)) +#endif + +/************************************** + * Declarations internal to this file * + **************************************/ +typedef bool (*pb_encoder_t)(pb_ostream_t *stream, const pb_field_t *field, const void *src) checkreturn; + +static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); +static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *field, const void *pData, size_t count, pb_encoder_t func); +static bool checkreturn encode_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData); +static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension); +static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData); +static void *pb_const_cast(const void *p); +static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high); +static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_t *field, const void *src); +static bool checkreturn pb_enc_uvarint(pb_ostream_t *stream, const pb_field_t *field, const void *src); +static bool checkreturn pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *field, const void *src); +static bool checkreturn pb_enc_fixed32(pb_ostream_t *stream, const pb_field_t *field, const void *src); +static bool checkreturn pb_enc_fixed64(pb_ostream_t *stream, const pb_field_t *field, const void *src); +static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src); +static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_t *field, const void *src); +static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_t *field, const void *src); +static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src); + +#ifdef PB_WITHOUT_64BIT +#define pb_int64_t int32_t +#define pb_uint64_t uint32_t +#else +#define pb_int64_t int64_t +#define pb_uint64_t uint64_t +#endif + +/* --- Function pointers to field encoders --- + * Order in the array must match pb_action_t LTYPE numbering. + */ +static const pb_encoder_t PB_ENCODERS[PB_LTYPES_COUNT] = { + &pb_enc_varint, + &pb_enc_uvarint, + &pb_enc_svarint, + &pb_enc_fixed32, + &pb_enc_fixed64, + + &pb_enc_bytes, + &pb_enc_string, + &pb_enc_submessage, + NULL, /* extensions */ + &pb_enc_fixed_length_bytes +}; + +/******************************* + * pb_ostream_t implementation * + *******************************/ + +static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) +{ + size_t i; + pb_byte_t *dest = (pb_byte_t*)stream->state; + stream->state = dest + count; + + for (i = 0; i < count; i++) + dest[i] = buf[i]; + + return true; +} + +pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize) +{ + pb_ostream_t stream; +#ifdef PB_BUFFER_ONLY + stream.callback = (void*)1; /* Just a marker value */ +#else + stream.callback = &buf_write; +#endif + stream.state = buf; + stream.max_size = bufsize; + stream.bytes_written = 0; +#ifndef PB_NO_ERRMSG + stream.errmsg = NULL; +#endif + return stream; +} + +bool checkreturn pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) +{ + if (stream->callback != NULL) + { + if (stream->bytes_written + count > stream->max_size) + PB_RETURN_ERROR(stream, "stream full"); + +#ifdef PB_BUFFER_ONLY + if (!buf_write(stream, buf, count)) + PB_RETURN_ERROR(stream, "io error"); +#else + if (!stream->callback(stream, buf, count)) + PB_RETURN_ERROR(stream, "io error"); +#endif + } + + stream->bytes_written += count; + return true; +} + +/************************* + * Encode a single field * + *************************/ + +/* Encode a static array. Handles the size calculations and possible packing. */ +static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *field, + const void *pData, size_t count, pb_encoder_t func) +{ + size_t i; + const void *p; + size_t size; + + if (count == 0) + return true; + + if (PB_ATYPE(field->type) != PB_ATYPE_POINTER && count > field->array_size) + PB_RETURN_ERROR(stream, "array max size exceeded"); + + /* We always pack arrays if the datatype allows it. */ + if (PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE) + { + if (!pb_encode_tag(stream, PB_WT_STRING, field->tag)) + return false; + + /* Determine the total size of packed array. */ + if (PB_LTYPE(field->type) == PB_LTYPE_FIXED32) + { + size = 4 * count; + } + else if (PB_LTYPE(field->type) == PB_LTYPE_FIXED64) + { + size = 8 * count; + } + else + { + pb_ostream_t sizestream = PB_OSTREAM_SIZING; + p = pData; + for (i = 0; i < count; i++) + { + if (!func(&sizestream, field, p)) + return false; + p = (const char*)p + field->data_size; + } + size = sizestream.bytes_written; + } + + if (!pb_encode_varint(stream, (pb_uint64_t)size)) + return false; + + if (stream->callback == NULL) + return pb_write(stream, NULL, size); /* Just sizing.. */ + + /* Write the data */ + p = pData; + for (i = 0; i < count; i++) + { + if (!func(stream, field, p)) + return false; + p = (const char*)p + field->data_size; + } + } + else + { + p = pData; + for (i = 0; i < count; i++) + { + if (!pb_encode_tag_for_field(stream, field)) + return false; + + /* Normally the data is stored directly in the array entries, but + * for pointer-type string and bytes fields, the array entries are + * actually pointers themselves also. So we have to dereference once + * more to get to the actual data. */ + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER && + (PB_LTYPE(field->type) == PB_LTYPE_STRING || + PB_LTYPE(field->type) == PB_LTYPE_BYTES)) + { + if (!func(stream, field, *(const void* const*)p)) + return false; + } + else + { + if (!func(stream, field, p)) + return false; + } + p = (const char*)p + field->data_size; + } + } + + return true; +} + +/* In proto3, all fields are optional and are only encoded if their value is "non-zero". + * This function implements the check for the zero value. */ +static bool pb_check_proto3_default_value(const pb_field_t *field, const void *pData) +{ + pb_type_t type = field->type; + const void *pSize = (const char*)pData + field->size_offset; + + if (PB_HTYPE(type) == PB_HTYPE_REQUIRED) + { + /* Required proto2 fields inside proto3 submessage, pretty rare case */ + return false; + } + else if (PB_HTYPE(type) == PB_HTYPE_REPEATED) + { + /* Repeated fields inside proto3 submessage: present if count != 0 */ + return *(const pb_size_t*)pSize == 0; + } + else if (PB_HTYPE(type) == PB_HTYPE_ONEOF) + { + /* Oneof fields */ + return *(const pb_size_t*)pSize == 0; + } + else if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && field->size_offset != 0) + { + /* Proto2 optional fields inside proto3 submessage */ + return *(const bool*)pSize == false; + } + + /* Rest is proto3 singular fields */ + + if (PB_ATYPE(type) == PB_ATYPE_STATIC) + { + if (PB_LTYPE(type) == PB_LTYPE_BYTES) + { + const pb_bytes_array_t *bytes = (const pb_bytes_array_t*)pData; + return bytes->size == 0; + } + else if (PB_LTYPE(type) == PB_LTYPE_STRING) + { + return *(const char*)pData == '\0'; + } + else if (PB_LTYPE(type) == PB_LTYPE_FIXED_LENGTH_BYTES) + { + /* Fixed length bytes is only empty if its length is fixed + * as 0. Which would be pretty strange, but we can check + * it anyway. */ + return field->data_size == 0; + } + else if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE) + { + /* Check all fields in the submessage to find if any of them + * are non-zero. The comparison cannot be done byte-per-byte + * because the C struct may contain padding bytes that must + * be skipped. + */ + pb_field_iter_t iter; + if (pb_field_iter_begin(&iter, (const pb_field_t*)field->ptr, pb_const_cast(pData))) + { + do + { + if (!pb_check_proto3_default_value(iter.pos, iter.pData)) + { + return false; + } + } while (pb_field_iter_next(&iter)); + } + return true; + } + } + + { + /* Catch-all branch that does byte-per-byte comparison for zero value. + * + * This is for all pointer fields, and for static PB_LTYPE_VARINT, + * UVARINT, SVARINT, FIXED32, FIXED64, EXTENSION fields, and also + * callback fields. These all have integer or pointer value which + * can be compared with 0. + */ + pb_size_t i; + const char *p = (const char*)pData; + for (i = 0; i < field->data_size; i++) + { + if (p[i] != 0) + { + return false; + } + } + + return true; + } +} + +/* Encode a field with static or pointer allocation, i.e. one whose data + * is available to the encoder directly. */ +static bool checkreturn encode_basic_field(pb_ostream_t *stream, + const pb_field_t *field, const void *pData) +{ + pb_encoder_t func; + bool implicit_has; + const void *pSize = &implicit_has; + + func = PB_ENCODERS[PB_LTYPE(field->type)]; + + if (field->size_offset) + { + /* Static optional, repeated or oneof field */ + pSize = (const char*)pData + field->size_offset; + } + else if (PB_HTYPE(field->type) == PB_HTYPE_OPTIONAL) + { + /* Proto3 style field, optional but without explicit has_ field. */ + implicit_has = !pb_check_proto3_default_value(field, pData); + } + else + { + /* Required field, always present */ + implicit_has = true; + } + + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) + { + /* pData is a pointer to the field, which contains pointer to + * the data. If the 2nd pointer is NULL, it is interpreted as if + * the has_field was false. + */ + pData = *(const void* const*)pData; + implicit_has = (pData != NULL); + } + + switch (PB_HTYPE(field->type)) + { + case PB_HTYPE_REQUIRED: + if (!pData) + PB_RETURN_ERROR(stream, "missing required field"); + if (!pb_encode_tag_for_field(stream, field)) + return false; + if (!func(stream, field, pData)) + return false; + break; + + case PB_HTYPE_OPTIONAL: + if (*(const bool*)pSize) + { + if (!pb_encode_tag_for_field(stream, field)) + return false; + + if (!func(stream, field, pData)) + return false; + } + break; + + case PB_HTYPE_REPEATED: { + pb_size_t count; + if (field->size_offset != 0) { + count = *(const pb_size_t*)pSize; + } else { + count = field->array_size; + } + if (!encode_array(stream, field, pData, count, func)) + return false; + break; + } + + case PB_HTYPE_ONEOF: + if (*(const pb_size_t*)pSize == field->tag) + { + if (!pb_encode_tag_for_field(stream, field)) + return false; + + if (!func(stream, field, pData)) + return false; + } + break; + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } + + return true; +} + +/* Encode a field with callback semantics. This means that a user function is + * called to provide and encode the actual data. */ +static bool checkreturn encode_callback_field(pb_ostream_t *stream, + const pb_field_t *field, const void *pData) +{ + const pb_callback_t *callback = (const pb_callback_t*)pData; + +#ifdef PB_OLD_CALLBACK_STYLE + const void *arg = callback->arg; +#else + void * const *arg = &(callback->arg); +#endif + + if (callback->funcs.encode != NULL) + { + if (!callback->funcs.encode(stream, field, arg)) + PB_RETURN_ERROR(stream, "callback error"); + } + return true; +} + +/* Encode a single field of any callback or static type. */ +static bool checkreturn encode_field(pb_ostream_t *stream, + const pb_field_t *field, const void *pData) +{ + switch (PB_ATYPE(field->type)) + { + case PB_ATYPE_STATIC: + case PB_ATYPE_POINTER: + return encode_basic_field(stream, field, pData); + + case PB_ATYPE_CALLBACK: + return encode_callback_field(stream, field, pData); + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +} + +/* Default handler for extension fields. Expects to have a pb_field_t + * pointer in the extension->type->arg field. */ +static bool checkreturn default_extension_encoder(pb_ostream_t *stream, + const pb_extension_t *extension) +{ + const pb_field_t *field = (const pb_field_t*)extension->type->arg; + + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) + { + /* For pointer extensions, the pointer is stored directly + * in the extension structure. This avoids having an extra + * indirection. */ + return encode_field(stream, field, &extension->dest); + } + else + { + return encode_field(stream, field, extension->dest); + } +} + +/* Walk through all the registered extensions and give them a chance + * to encode themselves. */ +static bool checkreturn encode_extension_field(pb_ostream_t *stream, + const pb_field_t *field, const void *pData) +{ + const pb_extension_t *extension = *(const pb_extension_t* const *)pData; + PB_UNUSED(field); + + while (extension) + { + bool status; + if (extension->type->encode) + status = extension->type->encode(stream, extension); + else + status = default_extension_encoder(stream, extension); + + if (!status) + return false; + + extension = extension->next; + } + + return true; +} + +/********************* + * Encode all fields * + *********************/ + +static void *pb_const_cast(const void *p) +{ + /* Note: this casts away const, in order to use the common field iterator + * logic for both encoding and decoding. */ + union { + void *p1; + const void *p2; + } t; + t.p2 = p; + return t.p1; +} + +bool checkreturn pb_encode(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct) +{ + pb_field_iter_t iter; + if (!pb_field_iter_begin(&iter, fields, pb_const_cast(src_struct))) + return true; /* Empty message type */ + + do { + if (PB_LTYPE(iter.pos->type) == PB_LTYPE_EXTENSION) + { + /* Special case for the extension field placeholder */ + if (!encode_extension_field(stream, iter.pos, iter.pData)) + return false; + } + else + { + /* Regular field */ + if (!encode_field(stream, iter.pos, iter.pData)) + return false; + } + } while (pb_field_iter_next(&iter)); + + return true; +} + +bool pb_encode_delimited(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct) +{ + return pb_encode_submessage(stream, fields, src_struct); +} + +bool pb_encode_nullterminated(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct) +{ + const pb_byte_t zero = 0; + + if (!pb_encode(stream, fields, src_struct)) + return false; + + return pb_write(stream, &zero, 1); +} + +bool pb_get_encoded_size(size_t *size, const pb_field_t fields[], const void *src_struct) +{ + pb_ostream_t stream = PB_OSTREAM_SIZING; + + if (!pb_encode(&stream, fields, src_struct)) + return false; + + *size = stream.bytes_written; + return true; +} + +/******************** + * Helper functions * + ********************/ + +/* This function avoids 64-bit shifts as they are quite slow on many platforms. */ +static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high) +{ + size_t i = 0; + pb_byte_t buffer[10]; + pb_byte_t byte = (pb_byte_t)(low & 0x7F); + low >>= 7; + + while (i < 4 && (low != 0 || high != 0)) + { + byte |= 0x80; + buffer[i++] = byte; + byte = (pb_byte_t)(low & 0x7F); + low >>= 7; + } + + if (high) + { + byte = (pb_byte_t)(byte | ((high & 0x07) << 4)); + high >>= 3; + + while (high) + { + byte |= 0x80; + buffer[i++] = byte; + byte = (pb_byte_t)(high & 0x7F); + high >>= 7; + } + } + + buffer[i++] = byte; + + return pb_write(stream, buffer, i); +} + +bool checkreturn pb_encode_varint(pb_ostream_t *stream, pb_uint64_t value) +{ + if (value <= 0x7F) + { + /* Fast path: single byte */ + pb_byte_t byte = (pb_byte_t)value; + return pb_write(stream, &byte, 1); + } + else + { +#ifdef PB_WITHOUT_64BIT + return pb_encode_varint_32(stream, value, 0); +#else + return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)(value >> 32)); +#endif + } +} + +bool checkreturn pb_encode_svarint(pb_ostream_t *stream, pb_int64_t value) +{ + pb_uint64_t zigzagged; + if (value < 0) + zigzagged = ~((pb_uint64_t)value << 1); + else + zigzagged = (pb_uint64_t)value << 1; + + return pb_encode_varint(stream, zigzagged); +} + +bool checkreturn pb_encode_fixed32(pb_ostream_t *stream, const void *value) +{ + uint32_t val = *(const uint32_t*)value; + pb_byte_t bytes[4]; + bytes[0] = (pb_byte_t)(val & 0xFF); + bytes[1] = (pb_byte_t)((val >> 8) & 0xFF); + bytes[2] = (pb_byte_t)((val >> 16) & 0xFF); + bytes[3] = (pb_byte_t)((val >> 24) & 0xFF); + return pb_write(stream, bytes, 4); +} + +#ifndef PB_WITHOUT_64BIT +bool checkreturn pb_encode_fixed64(pb_ostream_t *stream, const void *value) +{ + uint64_t val = *(const uint64_t*)value; + pb_byte_t bytes[8]; + bytes[0] = (pb_byte_t)(val & 0xFF); + bytes[1] = (pb_byte_t)((val >> 8) & 0xFF); + bytes[2] = (pb_byte_t)((val >> 16) & 0xFF); + bytes[3] = (pb_byte_t)((val >> 24) & 0xFF); + bytes[4] = (pb_byte_t)((val >> 32) & 0xFF); + bytes[5] = (pb_byte_t)((val >> 40) & 0xFF); + bytes[6] = (pb_byte_t)((val >> 48) & 0xFF); + bytes[7] = (pb_byte_t)((val >> 56) & 0xFF); + return pb_write(stream, bytes, 8); +} +#endif + +bool checkreturn pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number) +{ + pb_uint64_t tag = ((pb_uint64_t)field_number << 3) | wiretype; + return pb_encode_varint(stream, tag); +} + +bool checkreturn pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_t *field) +{ + pb_wire_type_t wiretype; + switch (PB_LTYPE(field->type)) + { + case PB_LTYPE_VARINT: + case PB_LTYPE_UVARINT: + case PB_LTYPE_SVARINT: + wiretype = PB_WT_VARINT; + break; + + case PB_LTYPE_FIXED32: + wiretype = PB_WT_32BIT; + break; + + case PB_LTYPE_FIXED64: + wiretype = PB_WT_64BIT; + break; + + case PB_LTYPE_BYTES: + case PB_LTYPE_STRING: + case PB_LTYPE_SUBMESSAGE: + case PB_LTYPE_FIXED_LENGTH_BYTES: + wiretype = PB_WT_STRING; + break; + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } + + return pb_encode_tag(stream, wiretype, field->tag); +} + +bool checkreturn pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size) +{ + if (!pb_encode_varint(stream, (pb_uint64_t)size)) + return false; + + return pb_write(stream, buffer, size); +} + +bool checkreturn pb_encode_submessage(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct) +{ + /* First calculate the message size using a non-writing substream. */ + pb_ostream_t substream = PB_OSTREAM_SIZING; + size_t size; + bool status; + + if (!pb_encode(&substream, fields, src_struct)) + { +#ifndef PB_NO_ERRMSG + stream->errmsg = substream.errmsg; +#endif + return false; + } + + size = substream.bytes_written; + + if (!pb_encode_varint(stream, (pb_uint64_t)size)) + return false; + + if (stream->callback == NULL) + return pb_write(stream, NULL, size); /* Just sizing */ + + if (stream->bytes_written + size > stream->max_size) + PB_RETURN_ERROR(stream, "stream full"); + + /* Use a substream to verify that a callback doesn't write more than + * what it did the first time. */ + substream.callback = stream->callback; + substream.state = stream->state; + substream.max_size = size; + substream.bytes_written = 0; +#ifndef PB_NO_ERRMSG + substream.errmsg = NULL; +#endif + + status = pb_encode(&substream, fields, src_struct); + + stream->bytes_written += substream.bytes_written; + stream->state = substream.state; +#ifndef PB_NO_ERRMSG + stream->errmsg = substream.errmsg; +#endif + + if (substream.bytes_written != size) + PB_RETURN_ERROR(stream, "submsg size changed"); + + return status; +} + +/* Field encoders */ + +static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + pb_int64_t value = 0; + + if (field->data_size == sizeof(int_least8_t)) + value = *(const int_least8_t*)src; + else if (field->data_size == sizeof(int_least16_t)) + value = *(const int_least16_t*)src; + else if (field->data_size == sizeof(int32_t)) + value = *(const int32_t*)src; + else if (field->data_size == sizeof(pb_int64_t)) + value = *(const pb_int64_t*)src; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + +#ifdef PB_WITHOUT_64BIT + if (value < 0) + return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)-1); + else +#endif + return pb_encode_varint(stream, (pb_uint64_t)value); +} + +static bool checkreturn pb_enc_uvarint(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + pb_uint64_t value = 0; + + if (field->data_size == sizeof(uint_least8_t)) + value = *(const uint_least8_t*)src; + else if (field->data_size == sizeof(uint_least16_t)) + value = *(const uint_least16_t*)src; + else if (field->data_size == sizeof(uint32_t)) + value = *(const uint32_t*)src; + else if (field->data_size == sizeof(pb_uint64_t)) + value = *(const pb_uint64_t*)src; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + + return pb_encode_varint(stream, value); +} + +static bool checkreturn pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + pb_int64_t value = 0; + + if (field->data_size == sizeof(int_least8_t)) + value = *(const int_least8_t*)src; + else if (field->data_size == sizeof(int_least16_t)) + value = *(const int_least16_t*)src; + else if (field->data_size == sizeof(int32_t)) + value = *(const int32_t*)src; + else if (field->data_size == sizeof(pb_int64_t)) + value = *(const pb_int64_t*)src; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + + return pb_encode_svarint(stream, value); +} + +static bool checkreturn pb_enc_fixed64(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + PB_UNUSED(field); +#ifndef PB_WITHOUT_64BIT + return pb_encode_fixed64(stream, src); +#else + PB_UNUSED(src); + PB_RETURN_ERROR(stream, "no 64bit support"); +#endif +} + +static bool checkreturn pb_enc_fixed32(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + PB_UNUSED(field); + return pb_encode_fixed32(stream, src); +} + +static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + const pb_bytes_array_t *bytes = NULL; + + bytes = (const pb_bytes_array_t*)src; + + if (src == NULL) + { + /* Treat null pointer as an empty bytes field */ + return pb_encode_string(stream, NULL, 0); + } + + if (PB_ATYPE(field->type) == PB_ATYPE_STATIC && + PB_BYTES_ARRAY_T_ALLOCSIZE(bytes->size) > field->data_size) + { + PB_RETURN_ERROR(stream, "bytes size exceeded"); + } + + return pb_encode_string(stream, bytes->bytes, bytes->size); +} + +static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + size_t size = 0; + size_t max_size = field->data_size; + const char *p = (const char*)src; + + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) + { + max_size = (size_t)-1; + } + else + { + /* pb_dec_string() assumes string fields end with a null + * terminator when the type isn't PB_ATYPE_POINTER, so we + * shouldn't allow more than max-1 bytes to be written to + * allow space for the null terminator. + */ + if (max_size == 0) + PB_RETURN_ERROR(stream, "zero-length string"); + + max_size -= 1; + } + + + if (src == NULL) + { + size = 0; /* Treat null pointer as an empty string */ + } + else + { + /* strnlen() is not always available, so just use a loop */ + while (size < max_size && *p != '\0') + { + size++; + p++; + } + + if (*p != '\0') + { + PB_RETURN_ERROR(stream, "unterminated string"); + } + } + + return pb_encode_string(stream, (const pb_byte_t*)src, size); +} + +static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + if (field->ptr == NULL) + PB_RETURN_ERROR(stream, "invalid field descriptor"); + + return pb_encode_submessage(stream, (const pb_field_t*)field->ptr, src); +} + +static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + return pb_encode_string(stream, (const pb_byte_t*)src, field->data_size); +} + diff --git a/src/lib/ubjson/ubjr.c b/src/lib/ubjson/ubjr.c new file mode 100644 index 0000000..7bb2b2f --- /dev/null +++ b/src/lib/ubjson/ubjr.c @@ -0,0 +1,525 @@ +#include "ubj.h"
+#include "ubj_internal.h"
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef MULTIPASS_TRACE_MALLOC
+#include "lib/mpmalloc.h"
+#else
+#define mpmalloc malloc
+#define mprealloc realloc
+#define mpfree free
+#endif
+
+#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*)mpmalloc(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;
+ mpfree(ctx->userdata);
+ mpfree(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)
+{
+ //mpfree(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*)mpmalloc(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 = mpmalloc(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 = mpmalloc(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 = mpmalloc(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=mpmalloc(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 = mprealloc(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 = mpmalloc(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 = mpmalloc(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 = mpmalloc(1 * ls + 1); //the +1 is for memory for the 0-size elements
+ myobject.keys = mpmalloc(1 * sizeof(ubjr_string_t));
+ for (myobject.size = 0; priv_ubjr_context_peek(ctx) != '}'; myobject.size++)
+ {
+ if (myobject.size >= (1ULL << arrpot))
+ {
+ arrpot++;
+ myobject.values = mprealloc(myobject.values, (1ULL << arrpot)*ls + 1);
+ myobject.keys = mprealloc((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 = mpmalloc(ls*myobject.size + 1);
+ myobject.keys = mpmalloc(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);
+ }
+ }
+ mpfree(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;
+ mpfree((void*)*st);
+ break;
+ }
+ case UBJ_ARRAY:
+ {
+ ubjr_array_t* arr=(ubjr_array_t*)value;
+ priv_ubjr_cleanup_container(arr->type,arr->size,arr->values);
+ mpfree(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)
+ {
+ mpfree(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..9fce397 --- /dev/null +++ b/src/lib/ubjson/ubjw.c @@ -0,0 +1,626 @@ +#include "ubj.h"
+#include "ubj_internal.h"
+
+#ifdef MULTIPASS_TRACE_MALLOC
+#include "lib/mpmalloc.h"
+#else
+#define mpmalloc malloc
+#define mpfree free
+#endif
+
+#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 16
+#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*)mpmalloc(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)
+{
+ mpfree(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*)mpmalloc(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);
+ mpfree(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);
+}
+
+
+// Not used by benchmark.py -> high BUFFER_OUT_SIZE does not matter
+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);
+}
diff --git a/src/lib/xdr.cc b/src/lib/xdr.cc new file mode 100644 index 0000000..b44471f --- /dev/null +++ b/src/lib/xdr.cc @@ -0,0 +1,188 @@ +#include "lib/xdr.h" + +void XDRWriter::put(uint32_t number) +{ + *buffer++ = ((number >> 24) & 0xffU); + *buffer++ = ((number >> 16) & 0xffU); + *buffer++ = ((number >> 8) & 0xffU); + *buffer++ = (number & 0xffU); + pos += 4; +} + +void XDRWriter::put(int32_t number) +{ + *buffer++ = ((number >> 24) & 0xffU); + *buffer++ = ((number >> 16) & 0xffU); + *buffer++ = ((number >> 8) & 0xffU); + *buffer++ = (number & 0xffU); + pos += 4; +} + +void XDRWriter::put(uint64_t number) +{ + *buffer++ = ((number >> 56) & 0xffU); + *buffer++ = ((number >> 48) & 0xffU); + *buffer++ = ((number >> 40) & 0xffU); + *buffer++ = ((number >> 32) & 0xffU); + *buffer++ = ((number >> 24) & 0xffU); + *buffer++ = ((number >> 16) & 0xffU); + *buffer++ = ((number >> 8) & 0xffU); + *buffer++ = (number & 0xffU); + pos += 8; +} + +void XDRWriter::put(int64_t number) +{ + *buffer++ = ((number >> 56) & 0xffU); + *buffer++ = ((number >> 48) & 0xffU); + *buffer++ = ((number >> 40) & 0xffU); + *buffer++ = ((number >> 32) & 0xffU); + *buffer++ = ((number >> 24) & 0xffU); + *buffer++ = ((number >> 16) & 0xffU); + *buffer++ = ((number >> 8) & 0xffU); + *buffer++ = (number & 0xffU); + pos += 8; +} + +void XDRWriter::put(float number) +{ + union { + uint32_t i; + float f; + } v; + // Setting one member of a struct and then reading another is undefined + // behaviour, but works as intended in nearly any (embedded) compiler + v.f = number; + put(v.i); +} + +void XDRWriter::put(double number) +{ + union { + uint64_t i; + double d; + } v; + // Setting one member of a struct and then reading another is undefined + // behaviour, but works as intended in nearly any (embedded) compiler + v.d = number; + put(v.i); +} + +void XDRWriter::put(char const *data) +{ + if (!is_fixed_length) + { + put(next_array_len); + } + uint32_t i; + for (i = 0; i < next_array_len; i++) + { + *buffer++ = data[i]; + } + while ((i++) % 4 != 0) + { + *buffer++ = 0; + } + pos += i - 1; +} + +template <uint32_t TSize> +void XDRWriter::put(char const (&data)[TSize]) +{ + if (!is_fixed_length) + { + put(TSize); + } + uint32_t i; + for (i = 0; i < TSize; i++) + { + *buffer++ = data[i]; + } + while ((i++) % 4 != 0) + { + *buffer++ = 0; + } + pos += i - 1; +} + +uint32_t XDRReader::get_uint32() +{ + uint32_t ret = (((uint32_t)data[pos] & 0xffU) << 24) | (((uint32_t)data[pos + 1] & 0xffU) << 16) | (((uint32_t)data[pos + 2] & 0xffU) << 8) | ((uint32_t)data[pos + 3] & 0xffU); + pos += 4; + return ret; +} + +int32_t XDRReader::get_int32() +{ + int32_t ret = (((int32_t)data[pos] & 0xff) << 24) | (((int32_t)data[pos + 1] & 0xff) << 16) | (((int32_t)data[pos + 2] & 0xff) << 8) | ((int32_t)data[pos + 3] & 0xff); + pos += 4; + return ret; +} + +uint64_t XDRReader::get_uint64() +{ + uint64_t ret = (((uint64_t)data[pos] & 0xffU) << 56) | (((uint64_t)data[pos + 1] & 0xffU) << 48) | (((uint64_t)data[pos + 2] & 0xffU) << 40) | (((uint64_t)data[pos + 3] & 0xffU) << 32) | (((uint64_t)data[pos + 4] & 0xffU) << 24) | (((uint64_t)data[pos + 5] & 0xffU) << 16) | (((uint64_t)data[pos + 6] & 0xffU) << 8) | ((uint64_t)data[pos + 7] & 0xffU); + return ret; +} + +int64_t XDRReader::get_int64() +{ + int64_t ret = (((int64_t)data[pos] & 0xff) << 56) | (((int64_t)data[pos + 1] & 0xff) << 48) | (((int64_t)data[pos + 2] & 0xff) << 40) | (((int64_t)data[pos + 3] & 0xff) << 32) | (((int64_t)data[pos + 4] & 0xff) << 24) | (((int64_t)data[pos + 5] & 0xff) << 16) | (((int64_t)data[pos + 6] & 0xff) << 8) | ((int64_t)data[pos + 7] & 0xff); + return ret; +} + +float XDRReader::get_float() +{ + union { + uint32_t i; + float f; + } v; + // Setting one member of a struct and then reading another is undefined + // behaviour, but works as intended in nearly any (embedded) compiler + v.i = get_uint32(); + return v.f; +} + +double XDRReader::get_double() +{ + union { + uint64_t i; + double d; + } v; + // Setting one member of a struct and then reading another is undefined + // behaviour, but works as intended in nearly any (embedded) compiler + v.i = get_uint64(); + return v.d; +} + +uint32_t XDRReader::get_opaque_length() +{ + return get_uint32(); +} + +char *XDRReader::get_opaque(uint32_t length) +{ + char *ret = data + pos; + pos += length; + if (length % 4) + { + pos += 4 - (length % 4); + } + return ret; +} + +void XDRReader::get_string(char *target) +{ + uint16_t length = get_opaque_length(); + uint16_t i; + for (i = 0; i < length; i++) + { + target[i] = data[pos + i]; + } + target[i] = 0; + pos += length; + if (length % 4) + { + pos += 4 - (length % 4); + } +} diff --git a/src/lib/xdr16.cc b/src/lib/xdr16.cc new file mode 100644 index 0000000..b1fb7ca --- /dev/null +++ b/src/lib/xdr16.cc @@ -0,0 +1,216 @@ +#include "lib/xdr16.h" + +void XDRWriter::put(uint16_t number) +{ + *buffer++ = ((number >> 8) & 0xffU); + *buffer++ = (number & 0xffU); + pos += 2; +} + +void XDRWriter::put(int16_t number) +{ + *buffer++ = ((number >> 8) & 0xffU); + *buffer++ = (number & 0xffU); + pos += 2; +} + +void XDRWriter::put(uint32_t number) +{ + *buffer++ = ((number >> 24) & 0xffU); + *buffer++ = ((number >> 16) & 0xffU); + *buffer++ = ((number >> 8) & 0xffU); + *buffer++ = (number & 0xffU); + pos += 4; +} + +void XDRWriter::put(int32_t number) +{ + *buffer++ = ((number >> 24) & 0xffU); + *buffer++ = ((number >> 16) & 0xffU); + *buffer++ = ((number >> 8) & 0xffU); + *buffer++ = (number & 0xffU); + pos += 4; +} + +void XDRWriter::put(uint64_t number) +{ + *buffer++ = ((number >> 56) & 0xffU); + *buffer++ = ((number >> 48) & 0xffU); + *buffer++ = ((number >> 40) & 0xffU); + *buffer++ = ((number >> 32) & 0xffU); + *buffer++ = ((number >> 24) & 0xffU); + *buffer++ = ((number >> 16) & 0xffU); + *buffer++ = ((number >> 8) & 0xffU); + *buffer++ = (number & 0xffU); + pos += 8; +} + +void XDRWriter::put(int64_t number) +{ + *buffer++ = ((number >> 56) & 0xffU); + *buffer++ = ((number >> 48) & 0xffU); + *buffer++ = ((number >> 40) & 0xffU); + *buffer++ = ((number >> 32) & 0xffU); + *buffer++ = ((number >> 24) & 0xffU); + *buffer++ = ((number >> 16) & 0xffU); + *buffer++ = ((number >> 8) & 0xffU); + *buffer++ = (number & 0xffU); + pos += 8; +} + +void XDRWriter::put(float number) +{ + union { + uint32_t i; + float f; + } v; + // Setting one member of a struct and then reading another is undefined + // behaviour, but works as intended in nearly any (embedded) compiler + v.f = number; + put(v.i); +} + +void XDRWriter::put(double number) +{ + union { + uint64_t i; + double d; + } v; + // Setting one member of a struct and then reading another is undefined + // behaviour, but works as intended in nearly any (embedded) compiler + v.d = number; + put(v.i); +} + +void XDRWriter::put(char const *data) +{ + if (!is_fixed_length) + { + put(next_array_len); + } + uint16_t i; + for (i = 0; i < next_array_len; i++) + { + *buffer++ = data[i]; + } + while ((i++) % 2 != 0) + { + *buffer++ = 0; + } + pos += i - 1; +} + +template <uint16_t TSize> +void XDRWriter::put(char const (&data)[TSize]) +{ + if (!is_fixed_length) + { + put(TSize); + } + uint16_t i; + for (i = 0; i < TSize; i++) + { + *buffer++ = data[i]; + } + while ((i++) % 2 != 0) + { + *buffer++ = 0; + } + pos += i - 1; +} + +uint16_t XDRReader::get_uint16() +{ + uint16_t ret = (((uint16_t)data[pos] & 0xffU) << 8) | ((uint16_t)data[pos + 1] & 0xffU); + pos += 2; + return ret; +} + +int16_t XDRReader::get_int16() +{ + int16_t ret = (((int16_t)data[pos] & 0xff) << 8) | ((int16_t)data[pos + 1] & 0xff); + pos += 2; + return ret; +} + +uint32_t XDRReader::get_uint32() +{ + uint32_t ret = (((uint32_t)data[pos] & 0xffU) << 24) | (((uint32_t)data[pos + 1] & 0xffU) << 16) | (((uint32_t)data[pos + 2] & 0xffU) << 8) | ((uint32_t)data[pos + 3] & 0xffU); + pos += 4; + return ret; +} + +int32_t XDRReader::get_int32() +{ + int32_t ret = (((int32_t)data[pos] & 0xff) << 24) | (((int32_t)data[pos + 1] & 0xff) << 16) | (((int32_t)data[pos + 2] & 0xff) << 8) | ((int32_t)data[pos + 3] & 0xff); + pos += 4; + return ret; +} + +uint64_t XDRReader::get_uint64() +{ + uint64_t ret = (((uint64_t)data[pos] & 0xffU) << 56) | (((uint64_t)data[pos + 1] & 0xffU) << 48) | (((uint64_t)data[pos + 2] & 0xffU) << 40) | (((uint64_t)data[pos + 3] & 0xffU) << 32) | (((uint64_t)data[pos + 4] & 0xffU) << 24) | (((uint64_t)data[pos + 5] & 0xffU) << 16) | (((uint64_t)data[pos + 6] & 0xffU) << 8) | ((uint64_t)data[pos + 7] & 0xffU); + return ret; +} + +int64_t XDRReader::get_int64() +{ + int64_t ret = (((int64_t)data[pos] & 0xff) << 56) | (((int64_t)data[pos + 1] & 0xff) << 48) | (((int64_t)data[pos + 2] & 0xff) << 40) | (((int64_t)data[pos + 3] & 0xff) << 32) | (((int64_t)data[pos + 4] & 0xff) << 24) | (((int64_t)data[pos + 5] & 0xff) << 16) | (((int64_t)data[pos + 6] & 0xff) << 8) | ((int64_t)data[pos + 7] & 0xff); + return ret; +} + +float XDRReader::get_float() +{ + union { + uint32_t i; + float f; + } v; + // Setting one member of a struct and then reading another is undefined + // behaviour, but works as intended in nearly any (embedded) compiler + v.i = get_uint32(); + return v.f; +} + +double XDRReader::get_double() +{ + union { + uint64_t i; + double d; + } v; + // Setting one member of a struct and then reading another is undefined + // behaviour, but works as intended in nearly any (embedded) compiler + v.i = get_uint64(); + return v.d; +} + +uint32_t XDRReader::get_opaque_length() +{ + return get_uint16(); +} + +char *XDRReader::get_opaque(uint32_t length) +{ + char *ret = data + pos; + pos += length; + if (length % 2) + { + pos += 2 - (length % 2); + } + return ret; +} + +void XDRReader::get_string(char *target) +{ + uint16_t length = get_opaque_length(); + uint16_t i; + for (i = 0; i < length; i++) + { + target[i] = data[pos + i]; + } + target[i] = 0; + pos += length; + if (length % 2) + { + pos += 2 - (length % 2); + } +} |