From 1199e9d83f1b2c2d47d8fb7de911674319379651 Mon Sep 17 00:00:00 2001 From: yangwei Date: Mon, 25 Nov 2024 19:22:19 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(integrate=20utable):=20deps/ut?= =?UTF-8?q?able?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 + deps/CMakeLists.txt | 6 +- deps/base64/CMakeLists.txt | 1 + deps/base64/LICENSE | 21 + deps/base64/README.md | 84 + deps/base64/b64.h | 84 + deps/base64/base64.c | 164 - deps/base64/base64.h | 28 - deps/base64/buffer.c | 33 + deps/base64/decode.c | 117 + deps/base64/encode.c | 93 + deps/mpack/CMakeLists.txt | 8 + deps/mpack/mpack.c | 7304 +++++++++++++ deps/mpack/mpack.h | 8207 ++++++++++++++ deps/nmx_pool/CMakeLists.txt | 3 +- deps/nmx_pool/mempool.c | 66 + deps/nmx_pool/mempool.h | 12 + deps/nmx_pool/nmx_palloc.c | 99 +- deps/nmx_pool/nmx_palloc.h | 33 +- deps/utable/CMakeLists.txt | 17 + deps/utable/ipfix_exporter_example.cpp | 197 + deps/utable/test/CMakeLists.txt | 31 + deps/utable/test/conf/ipfix_http_test.json | 59 + deps/utable/test/conf/ipfix_schema.json | 1222 +++ deps/utable/test/conf/ipfix_ssl_test.json | 50 + deps/utable/test/unit_test_ipfix_exporter.cpp | 802 ++ deps/utable/test/unit_test_utable.cpp | 773 ++ deps/utable/utable.c | 730 ++ deps/utable/utable.h | 87 + deps/utable/utable_ipfix_exporter.c | 795 ++ deps/utable/version.map | 6 + deps/yyjson/CMakeLists.txt | 8 + deps/yyjson/LICENSE | 21 + deps/yyjson/yyjson.c | 9447 +++++++++++++++++ deps/yyjson/yyjson.h | 7814 ++++++++++++++ 35 files changed, 38191 insertions(+), 232 deletions(-) create mode 100644 deps/base64/CMakeLists.txt create mode 100644 deps/base64/LICENSE create mode 100644 deps/base64/README.md create mode 100644 deps/base64/b64.h delete mode 100644 deps/base64/base64.c delete mode 100644 deps/base64/base64.h create mode 100644 deps/base64/buffer.c create mode 100644 deps/base64/decode.c create mode 100644 deps/base64/encode.c create mode 100644 deps/mpack/CMakeLists.txt create mode 100644 deps/mpack/mpack.c create mode 100644 deps/mpack/mpack.h create mode 100644 deps/nmx_pool/mempool.c create mode 100644 deps/nmx_pool/mempool.h create mode 100644 deps/utable/CMakeLists.txt create mode 100644 deps/utable/ipfix_exporter_example.cpp create mode 100644 deps/utable/test/CMakeLists.txt create mode 100644 deps/utable/test/conf/ipfix_http_test.json create mode 100644 deps/utable/test/conf/ipfix_schema.json create mode 100644 deps/utable/test/conf/ipfix_ssl_test.json create mode 100644 deps/utable/test/unit_test_ipfix_exporter.cpp create mode 100644 deps/utable/test/unit_test_utable.cpp create mode 100644 deps/utable/utable.c create mode 100644 deps/utable/utable.h create mode 100644 deps/utable/utable_ipfix_exporter.c create mode 100644 deps/utable/version.map create mode 100644 deps/yyjson/CMakeLists.txt create mode 100644 deps/yyjson/LICENSE create mode 100644 deps/yyjson/yyjson.c create mode 100644 deps/yyjson/yyjson.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cb3d02a..5d05e8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ if (CMAKE_CXX_CPPCHECK) "--suppress=internalAstError" "--suppress=integerOverflow" "--suppress=*:${CMAKE_SOURCE_DIR}/infra/monitor/stellar-dump/*" + "--suppress=redundantInitialization" ) set(CMAKE_C_CPPCHECK ${CMAKE_CXX_CPPCHECK}) else() diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index a00ff10..f0f6184 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -8,4 +8,8 @@ add_subdirectory(nmx_pool) add_subdirectory(logger) add_subdirectory(sds) add_subdirectory(linenoise) -add_subdirectory(ringbuf) \ No newline at end of file +add_subdirectory(ringbuf) +add_subdirectory(mpack) +add_subdirectory(yyjson) +add_subdirectory(base64) +add_subdirectory(utable) diff --git a/deps/base64/CMakeLists.txt b/deps/base64/CMakeLists.txt new file mode 100644 index 0000000..a1972e9 --- /dev/null +++ b/deps/base64/CMakeLists.txt @@ -0,0 +1 @@ +add_library(base64 decode.c encode.c buffer.c) \ No newline at end of file diff --git a/deps/base64/LICENSE b/deps/base64/LICENSE new file mode 100644 index 0000000..78b34d5 --- /dev/null +++ b/deps/base64/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Little Star Media, Inc. + +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. \ No newline at end of file diff --git a/deps/base64/README.md b/deps/base64/README.md new file mode 100644 index 0000000..0555c0d --- /dev/null +++ b/deps/base64/README.md @@ -0,0 +1,84 @@ +b64.c +===== + +Base64 encode/decode + +## install + +```sh +$ clib install jwerle/b64.c +``` + +## usage + +```c +#include +``` + +or + +```c +#include +``` + +## example + +```c +#include +#include +#include +#include "b64.h" + +int +main (void) { + unsigned char *str = "brian the monkey and bradley the kinkajou are friends"; + char *enc = b64_encode(str, strlen(str)); + + printf("%s\n", enc); // YnJpYW4gdGhlIG1vbmtleSBhbmQgYnJhZGxleSB0aGUga2lua2Fqb3UgYXJlIGZyaWVuZHM= + + char *dec = b64_decode(enc, strlen(enc)); + + printf("%s\n", dec); // brian the monkey and bradley the kinkajou are friends + free(enc); + free(dec); + return 0; +} +``` + +## api + +Base64 index table + +```c + +static const char b64_table[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' +}; +``` + +Encode `unsigned char *` source with `size_t` size. +Returns a `char *` base64 encoded string + +```c +char * +b64_encode (const unsigned char *, size_t); +``` + +Decode `char *` source with `size_t` size. +Returns a `unsigned char *` base64 decoded string + +```c +unsigned char * +b64_decode (const char *, size_t); +``` + +## license + +MIT diff --git a/deps/base64/b64.h b/deps/base64/b64.h new file mode 100644 index 0000000..e39d746 --- /dev/null +++ b/deps/base64/b64.h @@ -0,0 +1,84 @@ + +/** + * `b64.h' - b64 + * + * copyright (c) 2014 joseph werle + */ + +#ifndef B64_H +#define B64_H 1 + +typedef struct b64_buffer { + char * ptr; + int bufc; +} b64_buffer_t; + +/** + * Memory allocation functions to use. You can define b64_malloc and + * b64_realloc to custom functions if you want. + */ + +#ifndef b64_malloc +# define b64_malloc(ptr) malloc(ptr) +#endif +#ifndef b64_realloc +# define b64_realloc(ptr, size) realloc(ptr, size) +#endif + + // How much memory to allocate per buffer +#define B64_BUFFER_SIZE (1024 * 64) // 64K + + // Start buffered memory +int b64_buf_malloc(b64_buffer_t * buffer); + +// Update memory size. Returns the same pointer if we +// have enough space in the buffer. Otherwise, we add +// additional buffers. +int b64_buf_realloc(b64_buffer_t * buffer, size_t size); + +/** + * Base64 index table. + */ + +static const char b64_table[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Encode `unsigned char *' source with `size_t' size. + * Returns a `char *' base64 encoded string. + */ + +char * +b64_encode (const unsigned char *, size_t); + +/** + * Decode `char *' source with `size_t' size. + * Returns a `unsigned char *' base64 decoded string. + */ +unsigned char * +b64_decode (const char *, size_t); + +/** + * Decode `char *' source with `size_t' size. + * Returns a `unsigned char *' base64 decoded string + size of decoded string. + */ +unsigned char * +b64_decode_ex (const char *, size_t, size_t *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/base64/base64.c b/deps/base64/base64.c deleted file mode 100644 index eeded25..0000000 --- a/deps/base64/base64.c +++ /dev/null @@ -1,164 +0,0 @@ -/* This is a public domain base64 implementation written by WEI Zhicheng. */ - -#include "base64.h" - -#define BASE64_PAD '=' -#define BASE64DE_FIRST '+' -#define BASE64DE_LAST 'z' - -/* BASE 64 encode table */ -static const char base64en[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '+', '/', -}; - -/* ASCII order for BASE 64 decode, 255 in unused character */ -static const unsigned char base64de[] = { - /* nul, soh, stx, etx, eot, enq, ack, bel, */ - 255, 255, 255, 255, 255, 255, 255, 255, - - /* bs, ht, nl, vt, np, cr, so, si, */ - 255, 255, 255, 255, 255, 255, 255, 255, - - /* dle, dc1, dc2, dc3, dc4, nak, syn, etb, */ - 255, 255, 255, 255, 255, 255, 255, 255, - - /* can, em, sub, esc, fs, gs, rs, us, */ - 255, 255, 255, 255, 255, 255, 255, 255, - - /* sp, '!', '"', '#', '$', '%', '&', ''', */ - 255, 255, 255, 255, 255, 255, 255, 255, - - /* '(', ')', '*', '+', ',', '-', '.', '/', */ - 255, 255, 255, 62, 255, 255, 255, 63, - - /* '0', '1', '2', '3', '4', '5', '6', '7', */ - 52, 53, 54, 55, 56, 57, 58, 59, - - /* '8', '9', ':', ';', '<', '=', '>', '?', */ - 60, 61, 255, 255, 255, 255, 255, 255, - - /* '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', */ - 255, 0, 1, 2, 3, 4, 5, 6, - - /* 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', */ - 7, 8, 9, 10, 11, 12, 13, 14, - - /* 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', */ - 15, 16, 17, 18, 19, 20, 21, 22, - - /* 'X', 'Y', 'Z', '[', '\', ']', '^', '_', */ - 23, 24, 25, 255, 255, 255, 255, 255, - - /* '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', */ - 255, 26, 27, 28, 29, 30, 31, 32, - - /* 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', */ - 33, 34, 35, 36, 37, 38, 39, 40, - - /* 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', */ - 41, 42, 43, 44, 45, 46, 47, 48, - - /* 'x', 'y', 'z', '{', '|', '}', '~', del, */ - 49, 50, 51, 255, 255, 255, 255, 255 -}; - -unsigned int -base64_encode(const unsigned char *in, unsigned int inlen, char *out) -{ - int s; - unsigned int i; - unsigned int j; - unsigned char c; - unsigned char l; - - s = 0; - l = 0; - for (i = j = 0; i < inlen; i++) { - c = in[i]; - - switch (s) { - case 0: - s = 1; - out[j++] = base64en[(c >> 2) & 0x3F]; - break; - case 1: - s = 2; - out[j++] = base64en[((l & 0x3) << 4) | ((c >> 4) & 0xF)]; - break; - case 2: - s = 0; - out[j++] = base64en[((l & 0xF) << 2) | ((c >> 6) & 0x3)]; - out[j++] = base64en[c & 0x3F]; - break; - } - l = c; - } - - switch (s) { - case 1: - out[j++] = base64en[(l & 0x3) << 4]; - out[j++] = BASE64_PAD; - out[j++] = BASE64_PAD; - break; - case 2: - out[j++] = base64en[(l & 0xF) << 2]; - out[j++] = BASE64_PAD; - break; - } - - out[j] = 0; - - return j; -} - -unsigned int -base64_decode(const char *in, unsigned int inlen, unsigned char *out) -{ - unsigned int i; - unsigned int j; - unsigned char c; - - if (inlen & 0x3) { - return 0; - } - - for (i = j = 0; i < inlen; i++) { - if (in[i] == BASE64_PAD) { - break; - } - if (in[i] < BASE64DE_FIRST || in[i] > BASE64DE_LAST) { - return 0; - } - - c = base64de[(unsigned char)in[i]]; - if (c == 255) { - return 0; - } - - switch (i & 0x3) { - case 0: - out[j] = (c << 2) & 0xFF; - break; - case 1: - out[j++] |= (c >> 4) & 0x3; - out[j] = (c & 0xF) << 4; - break; - case 2: - out[j++] |= (c >> 2) & 0xF; - out[j] = (c & 0x3) << 6; - break; - case 3: - out[j++] |= c; - break; - } - } - - return j; -} diff --git a/deps/base64/base64.h b/deps/base64/base64.h deleted file mode 100644 index 9bea95a..0000000 --- a/deps/base64/base64.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef BASE64_H -#define BASE64_H - -#define BASE64_ENCODE_OUT_SIZE(s) ((unsigned int)((((s) + 2) / 3) * 4 + 1)) -#define BASE64_DECODE_OUT_SIZE(s) ((unsigned int)(((s) / 4) * 3)) - -#ifdef __cplusplus -extern "C" -{ -#endif -/* - * out is null-terminated encode string. - * return values is out length, exclusive terminating `\0' - */ -unsigned int -base64_encode(const unsigned char *in, unsigned int inlen, char *out); - -/* - * return values is out length - */ -unsigned int -base64_decode(const char *in, unsigned int inlen, unsigned char *out); - -#ifdef __cplusplus -} -#endif - -#endif /* BASE64_H */ diff --git a/deps/base64/buffer.c b/deps/base64/buffer.c new file mode 100644 index 0000000..aad598c --- /dev/null +++ b/deps/base64/buffer.c @@ -0,0 +1,33 @@ +#include +#include +#include "b64.h" + +#ifdef b64_USE_CUSTOM_MALLOC +extern void* b64_malloc(size_t); +#endif + +#ifdef b64_USE_CUSTOM_REALLOC +extern void* b64_realloc(void*, size_t); +#endif + +int b64_buf_malloc(b64_buffer_t * buf) +{ + buf->ptr = b64_malloc(B64_BUFFER_SIZE); + if(!buf->ptr) return -1; + + buf->bufc = 1; + + return 0; +} + +int b64_buf_realloc(b64_buffer_t* buf, size_t size) +{ + if ((int)size > buf->bufc * B64_BUFFER_SIZE) + { + while ((int)size > buf->bufc * B64_BUFFER_SIZE) buf->bufc++; + buf->ptr = b64_realloc(buf->ptr, B64_BUFFER_SIZE * buf->bufc); + if (!buf->ptr) return -1; + } + + return 0; +} diff --git a/deps/base64/decode.c b/deps/base64/decode.c new file mode 100644 index 0000000..38093bb --- /dev/null +++ b/deps/base64/decode.c @@ -0,0 +1,117 @@ + +/** + * `decode.c' - b64 + * + * copyright (c) 2014 joseph werle + */ + +#include +#include +#include +#include "b64.h" + +#ifdef b64_USE_CUSTOM_MALLOC +extern void* b64_malloc(size_t); +#endif + +#ifdef b64_USE_CUSTOM_REALLOC +extern void* b64_realloc(void*, size_t); +#endif + +unsigned char * +b64_decode (const char *src, size_t len) { + return b64_decode_ex(src, len, NULL); +} + +unsigned char * +b64_decode_ex (const char *src, size_t len, size_t *decsize) { + int i = 0; + int j = 0; + int l = 0; + size_t size = 0; + b64_buffer_t decbuf; + unsigned char buf[3]; + unsigned char tmp[4]; + + // alloc + if (b64_buf_malloc(&decbuf) == -1) { return NULL; } + + // parse until end of source + while (len--) { + // break if char is `=' or not base64 char + if ('=' == src[j]) { break; } + if (!(isalnum(src[j]) || '+' == src[j] || '/' == src[j])) { break; } + + // read up to 4 bytes at a time into `tmp' + tmp[i++] = src[j++]; + + // if 4 bytes read then decode into `buf' + if (4 == i) { + // translate values in `tmp' from table + for (i = 0; i < 4; ++i) { + // find translation char in `b64_table' + for (l = 0; l < 64; ++l) { + if (tmp[i] == b64_table[l]) { + tmp[i] = l; + break; + } + } + } + + // decode + buf[0] = (tmp[0] << 2) + ((tmp[1] & 0x30) >> 4); + buf[1] = ((tmp[1] & 0xf) << 4) + ((tmp[2] & 0x3c) >> 2); + buf[2] = ((tmp[2] & 0x3) << 6) + tmp[3]; + + // write decoded buffer to `decbuf.ptr' + if (b64_buf_realloc(&decbuf, size + 3) == -1) return NULL; + for (i = 0; i < 3; ++i) { + ((unsigned char*)decbuf.ptr)[size++] = buf[i]; + } + + // reset + i = 0; + } + } + + // remainder + if (i > 0) { + // fill `tmp' with `\0' at most 4 times + for (j = i; j < 4; ++j) { + tmp[j] = '\0'; + } + + // translate remainder + for (j = 0; j < 4; ++j) { + // find translation char in `b64_table' + for (l = 0; l < 64; ++l) { + if (tmp[j] == b64_table[l]) { + tmp[j] = l; + break; + } + } + } + + // decode remainder + buf[0] = (tmp[0] << 2) + ((tmp[1] & 0x30) >> 4); + buf[1] = ((tmp[1] & 0xf) << 4) + ((tmp[2] & 0x3c) >> 2); + buf[2] = ((tmp[2] & 0x3) << 6) + tmp[3]; + + // write remainer decoded buffer to `decbuf.ptr' + if (b64_buf_realloc(&decbuf, size + (i - 1)) == -1) return NULL; + for (j = 0; (j < i - 1); ++j) { + ((unsigned char*)decbuf.ptr)[size++] = buf[j]; + } + } + + // Make sure we have enough space to add '\0' character at end. + if (b64_buf_realloc(&decbuf, size + 1) == -1) return NULL; + ((unsigned char*)decbuf.ptr)[size] = '\0'; + + // Return back the size of decoded string if demanded. + if (decsize != NULL) { + *decsize = size; + } + + return (unsigned char*) decbuf.ptr; +} diff --git a/deps/base64/encode.c b/deps/base64/encode.c new file mode 100644 index 0000000..68e7924 --- /dev/null +++ b/deps/base64/encode.c @@ -0,0 +1,93 @@ + +/** + * `encode.c' - b64 + * + * copyright (c) 2014 joseph werle + */ + +#include +#include +#include "b64.h" + +#ifdef b64_USE_CUSTOM_MALLOC +extern void* b64_malloc(size_t); +#endif + +#ifdef b64_USE_CUSTOM_REALLOC +extern void* b64_realloc(void*, size_t); +#endif + +char * +b64_encode (const unsigned char *src, size_t len) { + int i = 0; + int j = 0; + b64_buffer_t encbuf; + size_t size = 0; + unsigned char buf[4]; + unsigned char tmp[3]; + + // alloc + if(b64_buf_malloc(&encbuf) == -1) { return NULL; } + + // parse until end of source + while (len--) { + // read up to 3 bytes at a time into `tmp' + tmp[i++] = *(src++); + + // if 3 bytes read then encode into `buf' + if (3 == i) { + buf[0] = (tmp[0] & 0xfc) >> 2; + buf[1] = ((tmp[0] & 0x03) << 4) + ((tmp[1] & 0xf0) >> 4); + buf[2] = ((tmp[1] & 0x0f) << 2) + ((tmp[2] & 0xc0) >> 6); + buf[3] = tmp[2] & 0x3f; + + // allocate 4 new byts for `enc` and + // then translate each encoded buffer + // part by index from the base 64 index table + // into `encbuf.ptr' unsigned char array + if (b64_buf_realloc(&encbuf, size + 4) == -1) return NULL; + + for (i = 0; i < 4; ++i) { + encbuf.ptr[size++] = b64_table[buf[i]]; + } + + // reset index + i = 0; + } + } + + // remainder + if (i > 0) { + // fill `tmp' with `\0' at most 3 times + for (j = i; j < 3; ++j) { + tmp[j] = '\0'; + } + + // perform same codec as above + buf[0] = (tmp[0] & 0xfc) >> 2; + buf[1] = ((tmp[0] & 0x03) << 4) + ((tmp[1] & 0xf0) >> 4); + buf[2] = ((tmp[1] & 0x0f) << 2) + ((tmp[2] & 0xc0) >> 6); + buf[3] = tmp[2] & 0x3f; + + // perform same write to `encbuf->ptr` with new allocation + for (j = 0; (j < i + 1); ++j) { + if (b64_buf_realloc(&encbuf, size + 1) == -1) return NULL; + + encbuf.ptr[size++] = b64_table[buf[j]]; + } + + // while there is still a remainder + // append `=' to `encbuf.ptr' + while ((i++ < 3)) { + if (b64_buf_realloc(&encbuf, size + 1) == -1) return NULL; + + encbuf.ptr[size++] = '='; + } + } + + // Make sure we have enough space to add '\0' character at end. + if (b64_buf_realloc(&encbuf, size + 1) == -1) return NULL; + encbuf.ptr[size] = '\0'; + + return encbuf.ptr; +} diff --git a/deps/mpack/CMakeLists.txt b/deps/mpack/CMakeLists.txt new file mode 100644 index 0000000..763b318 --- /dev/null +++ b/deps/mpack/CMakeLists.txt @@ -0,0 +1,8 @@ +if (CMAKE_CXX_CPPCHECK) + list(APPEND CMAKE_CXX_CPPCHECK + "--suppress=*:${CMAKE_CURRENT_SOURCE_DIR}/*" + ) + set(CMAKE_C_CPPCHECK ${CMAKE_CXX_CPPCHECK}) +endif() + +add_library(mpack mpack.c) \ No newline at end of file diff --git a/deps/mpack/mpack.c b/deps/mpack/mpack.c new file mode 100644 index 0000000..4f0dab4 --- /dev/null +++ b/deps/mpack/mpack.c @@ -0,0 +1,7304 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015-2021 Nicholas Fraser and the MPack authors + * + * 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.1.1 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" */ + +MPACK_SILENCE_WARNINGS_BEGIN + +#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_SILENCE_WARNINGS_END + +/* mpack/mpack-common.c.c */ + +#define MPACK_INTERNAL 1 + +/* #include "mpack-common.h" */ + +MPACK_SILENCE_WARNINGS_BEGIN + +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) { + // Older compilers (e.g. GCC 4.4.7) promote the result of this ternary to + // int and warn under -Wconversion, so we have to cast it back to char. + return (char)((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; + size_t i; + for (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, ""); + 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: + #if MPACK_FLOAT + mpack_snprintf(buffer, buffer_size, "%f", tag.v.f); + #else + mpack_snprintf(buffer, buffer_size, ""); + #endif + return; + case mpack_type_double: + #if MPACK_DOUBLE + mpack_snprintf(buffer, buffer_size, "%f", tag.v.d); + #else + mpack_snprintf(buffer, buffer_size, ""); + #endif + return; + + case mpack_type_str: + mpack_snprintf(buffer, buffer_size, "", 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, "", tag.v.n); + return; + case mpack_type_map: + mpack_snprintf(buffer, buffer_size, "", tag.v.n); + return; + } + + mpack_snprintf(buffer, buffer_size, ""); +} + +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: + #if MPACK_FLOAT + mpack_snprintf(buffer, buffer_size, "float %f", tag.v.f); + #else + mpack_snprintf(buffer, buffer_size, "float"); + #endif + return; + case mpack_type_double: + #if MPACK_DOUBLE + mpack_snprintf(buffer, buffer_size, "double %f", tag.v.d); + #else + mpack_snprintf(buffer, buffer_size, "double"); + #endif + return; + case mpack_type_str: + mpack_snprintf(buffer, buffer_size, "str of %" PRIu32 " bytes", tag.v.l); + return; + case mpack_type_bin: + mpack_snprintf(buffer, buffer_size, "bin of %" PRIu32 " bytes", tag.v.l); + return; + #if MPACK_EXTENSIONS + case mpack_type_ext: + mpack_snprintf(buffer, buffer_size, "ext of type %i, %" PRIu32 " bytes", + mpack_tag_ext_exttype(&tag), mpack_tag_ext_length(&tag)); + return; + #endif + case mpack_type_array: + mpack_snprintf(buffer, buffer_size, "array of %" PRIu32 " elements", tag.v.n); + return; + case mpack_type_map: + mpack_snprintf(buffer, buffer_size, "map of %" PRIu32 " 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, uint32_t count) { + mpack_assert(track->elements, "null track elements!"); + mpack_log("track pushing %s count %i\n", mpack_type_to_string(type), (int)count); + + // 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->elements[track->count].builder = false; + track->elements[track->count].key_needs_value = false; + ++track->count; + return mpack_ok; +} + +// TODO dedupe this +mpack_error_t mpack_track_push_builder(mpack_track_t* track, mpack_type_t type) { + mpack_assert(track->elements, "null track elements!"); + mpack_log("track pushing %s builder\n", mpack_type_to_string(type)); + + // 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 = 0; + track->elements[track->count].builder = true; + track->elements[track->count].key_needs_value = false; + ++track->count; + return mpack_ok; +} + +static mpack_error_t mpack_track_pop_impl(mpack_track_t* track, mpack_type_t type, bool builder) { + 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->key_needs_value) { + mpack_assert(type == mpack_type_map, "key_needs_value can only be true for maps!"); + mpack_break("attempting to close a %s but an odd number of elements were written", + mpack_type_to_string(type)); + return mpack_error_bug; + } + + if (element->left != 0) { + mpack_break("attempting to close a %s but there are %i %s left", + mpack_type_to_string(type), element->left, + (type == mpack_type_map || type == mpack_type_array) ? "elements" : "bytes"); + return mpack_error_bug; + } + + if (element->builder != builder) { + mpack_break("attempting to pop a %sbuilder but the open element is %sa builder", + builder ? "" : "non-", + element->builder ? "" : "not "); + return mpack_error_bug; + } + + --track->count; + return mpack_ok; +} + +mpack_error_t mpack_track_pop(mpack_track_t* track, mpack_type_t type) { + return mpack_track_pop_impl(track, type, false); +} + +mpack_error_t mpack_track_pop_builder(mpack_track_t* track, mpack_type_t type) { + return mpack_track_pop_impl(track, type, true); +} + +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->builder && element->left == 0 && !element->key_needs_value) { + 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) + return error; + + mpack_track_element_t* element = &track->elements[track->count - 1]; + + if (element->type == mpack_type_map) { + if (!element->key_needs_value) { + element->key_needs_value = true; + return mpack_ok; // don't decrement + } + element->key_needs_value = false; + } + + if (!element->builder) + --element->left; + return mpack_ok; +} + +mpack_error_t mpack_track_bytes(mpack_track_t* track, bool read, size_t count) { + MPACK_UNUSED(read); + mpack_assert(track->elements, "null track elements!"); + + if (count > MPACK_UINT32_MAX) { + mpack_break("%s more bytes than could possibly fit in a str/bin/ext!", + read ? "reading" : "writing"); + return mpack_error_bug; + } + + 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 -= (uint32_t)count; + return mpack_ok; +} + +mpack_error_t mpack_track_str_bytes_all(mpack_track_t* track, bool read, size_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) { + size_t i; + for (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_SILENCE_WARNINGS_END + +/* mpack/mpack-writer.c.c */ + +#define MPACK_INTERNAL 1 + +/* #include "mpack-writer.h" */ + +MPACK_SILENCE_WARNINGS_BEGIN + +#if MPACK_WRITER + +#if MPACK_BUILDER +static void mpack_builder_flush(mpack_writer_t* writer); +#endif + +#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, uint32_t count) { + if (writer->error == mpack_ok) + mpack_writer_flag_if_error(writer, mpack_track_push(&writer->track, type, count)); +} + +void mpack_writer_track_push_builder(mpack_writer_t* writer, mpack_type_t type) { + if (writer->error == mpack_ok) + mpack_writer_flag_if_error(writer, mpack_track_push_builder(&writer->track, type)); +} + +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_pop_builder(mpack_writer_t* writer, mpack_type_t type) { + if (writer->error == mpack_ok) + mpack_writer_flag_if_error(writer, mpack_track_pop_builder(&writer->track, type)); +} + +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 + +// This should probably be renamed. It's not solely used for tracking. +static inline void mpack_writer_track_element(mpack_writer_t* writer) { + (void)writer; + + #if MPACK_WRITE_TRACKING + if (writer->error == mpack_ok) + mpack_writer_flag_if_error(writer, mpack_track_element(&writer->track, false)); + #endif + + #if MPACK_BUILDER + if (writer->builder.current_build != NULL) { + mpack_build_t* build = writer->builder.current_build; + // We only track this write if it's not nested within another non-build + // map or array. + if (build->nested_compound_elements == 0) { + if (build->type != mpack_type_map) { + ++build->count; + mpack_log("adding element to build %p, now %" PRIu32 " elements\n", (void*)build, build->count); + } else if (build->key_needs_value) { + build->key_needs_value = false; + ++build->count; + } else { + build->key_needs_value = true; + } + } + } + #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->position = NULL; + writer->end = NULL; + writer->error = mpack_ok; + + #if MPACK_WRITE_TRACKING + mpack_memset(&writer->track, 0, sizeof(writer->track)); + #endif + + #if MPACK_BUILDER + writer->builder.current_build = NULL; + writer->builder.latest_build = NULL; + writer->builder.current_page = NULL; + writer->builder.pages = NULL; + writer->builder.stash_buffer = NULL; + writer->builder.stash_position = NULL; + writer->builder.stash_end = NULL; + #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->position = 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->position = 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->position = new_buffer + used; + writer->buffer = new_buffer; + writer->end = writer->buffer + new_size; + + // append the extra data + if (count > 0) { + mpack_memcpy(writer->position, data, count); + writer->position += 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->position = 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", (void*)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->position = 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 + // You cannot flush while there are elements open. + mpack_writer_flag_if_error(writer, mpack_track_check_empty(&writer->track)); + if (writer->error != mpack_ok) + return; + #endif + + #if MPACK_BUILDER + if (writer->builder.current_build != NULL) { + mpack_break("cannot call mpack_writer_flush_message() while there are elements open!"); + mpack_writer_flag_error(writer, mpack_error_bug); + 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 MPACK_BUILDER + // if we have a build in progress, we just ask the builder for a page. + // either it will have space for a tag, or it will flag a memory error. + if (writer->builder.current_build != NULL) { + mpack_builder_flush(writer); + return mpack_writer_error(writer) == mpack_ok; + } + #endif + + 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))); + + #if MPACK_BUILDER + // if we have a build in progress, we can't flush. we need to copy all + // bytes into as many build buffer pages as it takes. + if (writer->builder.current_build != NULL) { + while (true) { + size_t step = (size_t)(writer->end - writer->position); + if (step > count) + step = count; + mpack_memcpy(writer->position, p, step); + writer->position += step; + p += step; + count -= step; + + if (count == 0) + return; + + mpack_builder_flush(writer); + if (mpack_writer_error(writer) != mpack_ok) + return; + mpack_assert(writer->position != writer->end); + } + } + #endif + + // 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->position, p, count); + writer->position += 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->position, p, count); + writer->position += 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 + + #if MPACK_BUILDER + mpack_builder_t* builder = &writer->builder; + if (builder->current_build != NULL) { + // A builder is open! + + // Flag an error, if there's not already an error. You can only skip + // closing any open compound types if a write error occurred. If there + // wasn't already an error, it's a bug, which will assert in debug. + if (mpack_writer_error(writer) == mpack_ok) { + mpack_break("writer cannot be destroyed with an incomplete builder unless " + "an error was flagged!"); + mpack_writer_flag_error(writer, mpack_error_bug); + } + + // Free any remaining builder pages + mpack_builder_page_t* page = builder->pages; + #if MPACK_BUILDER_INTERNAL_STORAGE + mpack_assert(page == (mpack_builder_page_t*)builder->internal); + page = page->next; + #endif + while (page != NULL) { + mpack_builder_page_t* next = page->next; + MPACK_FREE(page); + page = next; + } + + // Restore the stashed pointers. The teardown function may need to free + // them (e.g. mpack_growable_writer_teardown().) + writer->buffer = builder->stash_buffer; + writer->position = builder->stash_position; + writer->end = builder->stash_end; + + // Note: It's not necessary to clean up the current_build or other + // pointers at this point because we're guaranteed to be in an error + // state already so a user error callback can't longjmp out. This + // destroy function will complete no matter what so it doesn't matter + // what junk is left in the writer. + } + #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_int: mpack_write_int (writer, value.v.i); return; + case mpack_type_uint: mpack_write_uint (writer, value.v.u); return; + + case mpack_type_float: + #if MPACK_FLOAT + mpack_write_float + #else + mpack_write_raw_float + #endif + (writer, value.v.f); + return; + case mpack_type_double: + #if MPACK_DOUBLE + mpack_write_double + #else + mpack_write_raw_double + #endif + (writer, value.v.d); + 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->position++) = 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 > MPACK_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 > MPACK_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 > MPACK_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 < MPACK_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 < MPACK_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 < MPACK_INT32_MIN); + mpack_store_u8(p, 0xd3); + mpack_store_i64(p + 1, value); +} + +#if MPACK_FLOAT +MPACK_STATIC_INLINE void mpack_encode_float(char* p, float value) { + mpack_store_u8(p, 0xca); + mpack_store_float(p + 1, value); +} +#else +MPACK_STATIC_INLINE void mpack_encode_raw_float(char* p, uint32_t value) { + mpack_store_u8(p, 0xca); + mpack_store_u32(p + 1, value); +} +#endif + +#if MPACK_DOUBLE +MPACK_STATIC_INLINE void mpack_encode_double(char* p, double value) { + mpack_store_u8(p, 0xcb); + mpack_store_double(p + 1, value); +} +#else +MPACK_STATIC_INLINE void mpack_encode_raw_double(char* p, uint64_t value) { + mpack_store_u8(p, 0xcb); + mpack_store_u64(p + 1, value); +} +#endif + +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 > MPACK_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 > MPACK_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, MPACK_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 > MPACK_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 > MPACK_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 > MPACK_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 > MPACK_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 > MPACK_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->position, __VA_ARGS__)); \ + writer->position += 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 <= MPACK_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 <= MPACK_UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value); + } else if (value <= MPACK_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 <= MPACK_UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value); + } else if (value <= MPACK_UINT16_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value); + } else if (value <= MPACK_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 <= MPACK_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 >= MPACK_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 <= MPACK_UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value); + } else if (value <= MPACK_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 >= MPACK_INT8_MIN) { + MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value); + } else if (value >= MPACK_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 <= MPACK_UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value); + } else if (value <= MPACK_UINT16_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value); + } else if (value <= MPACK_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 >= MPACK_INT8_MIN) { + MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value); + } else if (value >= MPACK_INT16_MIN) { + MPACK_WRITE_ENCODED(mpack_encode_i16, MPACK_TAG_SIZE_I16, (int16_t)value); + } else if (value >= MPACK_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); + } +} + +#if MPACK_FLOAT +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); +} +#else +void mpack_write_raw_float(mpack_writer_t* writer, uint32_t value) { + mpack_writer_track_element(writer); + MPACK_WRITE_ENCODED(mpack_encode_raw_float, MPACK_TAG_SIZE_FLOAT, value); +} +#endif + +#if MPACK_DOUBLE +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); +} +#else +void mpack_write_raw_double(mpack_writer_t* writer, uint64_t value) { + mpack_writer_track_element(writer); + MPACK_WRITE_ENCODED(mpack_encode_raw_double, MPACK_TAG_SIZE_DOUBLE, value); +} +#endif + +#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: %" PRIu32 , nanoseconds); + mpack_writer_flag_error(writer, mpack_error_bug); + return; + } + + mpack_writer_track_element(writer); + + if (seconds < 0 || seconds >= (MPACK_INT64_C(1) << 34)) { + MPACK_WRITE_ENCODED(mpack_encode_timestamp_12, MPACK_EXT_SIZE_TIMESTAMP12, seconds, nanoseconds); + } else if (seconds > MPACK_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 + +static void mpack_write_array_notrack(mpack_writer_t* writer, uint32_t count) { + if (count <= 15) { + MPACK_WRITE_ENCODED(mpack_encode_fixarray, MPACK_TAG_SIZE_FIXARRAY, (uint8_t)count); + } else if (count <= MPACK_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); + } +} + +static void mpack_write_map_notrack(mpack_writer_t* writer, uint32_t count) { + if (count <= 15) { + MPACK_WRITE_ENCODED(mpack_encode_fixmap, MPACK_TAG_SIZE_FIXMAP, (uint8_t)count); + } else if (count <= MPACK_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); + } +} + +void mpack_start_array(mpack_writer_t* writer, uint32_t count) { + mpack_writer_track_element(writer); + mpack_write_array_notrack(writer, count); + mpack_writer_track_push(writer, mpack_type_array, count); + mpack_builder_compound_push(writer); +} + +void mpack_start_map(mpack_writer_t* writer, uint32_t count) { + mpack_writer_track_element(writer); + mpack_write_map_notrack(writer, count); + mpack_writer_track_push(writer, mpack_type_map, count); + mpack_builder_compound_push(writer); +} + +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 <= MPACK_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 <= MPACK_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 <= MPACK_UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_bin8, MPACK_TAG_SIZE_BIN8, (uint8_t)count); + } else if (count <= MPACK_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 <= MPACK_UINT8_MAX) { + MPACK_WRITE_ENCODED(mpack_encode_ext8, MPACK_TAG_SIZE_EXT8, exttype, (uint8_t)count); + } else if (count <= MPACK_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(count == 0 || 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->position; + mpack_encode_fixstr(p, (uint8_t)count); + mpack_memcpy(p + MPACK_TAG_SIZE_FIXSTR, data, count); + writer->position += count + MPACK_TAG_SIZE_FIXSTR; + } + return; + } + + if (count <= MPACK_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->position; + mpack_encode_str8(p, (uint8_t)count); + mpack_memcpy(p + MPACK_TAG_SIZE_STR8, data, count); + writer->position += 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 <= MPACK_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(count == 0 || 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(count == 0 || 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(count == 0 || 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 > MPACK_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(length == 0 || 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 > MPACK_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); +} + +/* + * Builder implementation + * + * When a writer is in build mode, it diverts writes to an internal growable + * buffer. All elements other than builder start tags are encoded as normal + * into the builder buffer (even nested maps and arrays of known size, e.g. + * `mpack_start_array()`.) But for compound elements of unknown size, an + * mpack_build_t is written to the buffer instead. + * + * The mpack_build_t tracks everything needed to re-constitute the final + * message once all sizes are known. When the last build element is completed, + * the builder resolves the build by walking through the builds, outputting the + * final encoded tag, and copying everything in between to the writer's true + * buffer. + * + * To make things extra complicated, the builder buffer is not contiguous. It's + * allocated in pages, where the first page may be an internal page in the + * writer. But, each mpack_build_t must itself be contiguous and aligned + * properly within the buffer. This means bytes can be skipped (and wasted) + * before the builds or at the end of pages. + * + * To keep track of this, builds store both their element count and the number + * of encoded bytes that follow, and pages store the number of bytes used. As + * elements are written, each element adds to the count in the current open + * build, and the number of bytes written adds to the current page and the byte + * count in the last started build (whether or not it is completed.) + */ + +#if MPACK_BUILDER + +#ifdef MPACK_ALIGNOF + #define MPACK_BUILD_ALIGNMENT MPACK_ALIGNOF(mpack_build_t) +#else + // without alignof, we just align to the greater of size_t, void* and uint64_t. + // (we do this even though we don't have uint64_t in it in case we add it later.) + #define MPACK_BUILD_ALIGNMENT_MAX(x, y) ((x) > (y) ? (x) : (y)) + #define MPACK_BUILD_ALIGNMENT (MPACK_BUILD_ALIGNMENT_MAX(sizeof(void*), \ + MPACK_BUILD_ALIGNMENT_MAX(sizeof(size_t), sizeof(uint64_t)))) +#endif + +static inline void mpack_builder_check_sizes(mpack_writer_t* writer) { + + // We check internal and page sizes here so that we don't have to check + // them again. A new page with a build in it will have a page header, + // build, and minimum space for a tag. This will perform horribly and waste + // tons of memory if the page size is small, so you're best off just + // sticking with the defaults. + // + // These are all known at compile time, so if they are large + // enough this function should trivially optimize to a no-op. + + #if MPACK_BUILDER_INTERNAL_STORAGE + // make sure the internal storage is big enough to be useful + MPACK_STATIC_ASSERT(MPACK_BUILDER_INTERNAL_STORAGE_SIZE >= (sizeof(mpack_builder_page_t) + + sizeof(mpack_build_t) + MPACK_WRITER_MINIMUM_BUFFER_SIZE), + "MPACK_BUILDER_INTERNAL_STORAGE_SIZE is too small to be useful!"); + if (MPACK_BUILDER_INTERNAL_STORAGE_SIZE < (sizeof(mpack_builder_page_t) + + sizeof(mpack_build_t) + MPACK_WRITER_MINIMUM_BUFFER_SIZE)) + { + mpack_break("MPACK_BUILDER_INTERNAL_STORAGE_SIZE is too small to be useful!"); + mpack_writer_flag_error(writer, mpack_error_bug); + } + #endif + + // make sure the builder page size is big enough to be useful + MPACK_STATIC_ASSERT(MPACK_BUILDER_PAGE_SIZE >= (sizeof(mpack_builder_page_t) + + sizeof(mpack_build_t) + MPACK_WRITER_MINIMUM_BUFFER_SIZE), + "MPACK_BUILDER_PAGE_SIZE is too small to be useful!"); + if (MPACK_BUILDER_PAGE_SIZE < (sizeof(mpack_builder_page_t) + + sizeof(mpack_build_t) + MPACK_WRITER_MINIMUM_BUFFER_SIZE)) + { + mpack_break("MPACK_BUILDER_PAGE_SIZE is too small to be useful!"); + mpack_writer_flag_error(writer, mpack_error_bug); + } +} + +static inline size_t mpack_builder_page_size(mpack_writer_t* writer, mpack_builder_page_t* page) { + #if MPACK_BUILDER_INTERNAL_STORAGE + if ((char*)page == writer->builder.internal) + return sizeof(writer->builder.internal); + #else + (void)writer; + (void)page; + #endif + return MPACK_BUILDER_PAGE_SIZE; +} + +static inline size_t mpack_builder_align_build(size_t bytes_used) { + size_t offset = bytes_used; + offset += MPACK_BUILD_ALIGNMENT - 1; + offset -= offset % MPACK_BUILD_ALIGNMENT; + mpack_log("aligned %zi to %zi\n", bytes_used, offset); + return offset; +} + +static inline void mpack_builder_free_page(mpack_writer_t* writer, mpack_builder_page_t* page) { + mpack_log("freeing page %p\n", (void*)page); + #if MPACK_BUILDER_INTERNAL_STORAGE + if ((char*)page == writer->builder.internal) + return; + #else + (void)writer; + #endif + MPACK_FREE(page); +} + +static inline size_t mpack_builder_page_remaining(mpack_writer_t* writer, mpack_builder_page_t* page) { + return mpack_builder_page_size(writer, page) - page->bytes_used; +} + +static void mpack_builder_configure_buffer(mpack_writer_t* writer) { + if (mpack_writer_error(writer) != mpack_ok) + return; + mpack_builder_t* builder = &writer->builder; + + mpack_builder_page_t* page = builder->current_page; + mpack_assert(page != NULL, "page is null??"); + + // This diverts the writer into the remainder of the current page of our + // build buffer. + writer->buffer = (char*)page + page->bytes_used; + writer->position = (char*)page + page->bytes_used; + writer->end = (char*)page + mpack_builder_page_size(writer, page); + mpack_log("configuring buffer from %p to %p\n", (void*)writer->position, (void*)writer->end); +} + +static void mpack_builder_add_page(mpack_writer_t* writer) { + mpack_builder_t* builder = &writer->builder; + mpack_assert(writer->error == mpack_ok); + + mpack_log("adding a page.\n"); + mpack_builder_page_t* page = (mpack_builder_page_t*)MPACK_MALLOC(MPACK_BUILDER_PAGE_SIZE); + if (page == NULL) { + mpack_writer_flag_error(writer, mpack_error_memory); + return; + } + + page->next = NULL; + page->bytes_used = sizeof(mpack_builder_page_t); + builder->current_page->next = page; + builder->current_page = page; +} + +// Checks how many bytes the writer wrote to the page, adding it to the page's +// bytes_used. This must be followed up with mpack_builder_configure_buffer() +// (after adding a new page, build, etc) to reset the writer's buffer pointers. +static void mpack_builder_apply_writes(mpack_writer_t* writer) { + mpack_assert(writer->error == mpack_ok); + mpack_builder_t* builder = &writer->builder; + mpack_log("latest build is %p\n", (void*)builder->latest_build); + + // The difference between buffer and current is the number of bytes that + // were written to the page. + size_t bytes_written = (size_t)(writer->position - writer->buffer); + mpack_log("applying write of %zi bytes to build %p\n", bytes_written, (void*)builder->latest_build); + + mpack_assert(builder->current_page != NULL); + mpack_assert(builder->latest_build != NULL); + builder->current_page->bytes_used += bytes_written; + builder->latest_build->bytes += bytes_written; + mpack_log("latest build %p now has %zi bytes\n", (void*)builder->latest_build, builder->latest_build->bytes); +} + +static void mpack_builder_flush(mpack_writer_t* writer) { + mpack_assert(writer->error == mpack_ok); + mpack_builder_apply_writes(writer); + mpack_builder_add_page(writer); + mpack_builder_configure_buffer(writer); +} + +MPACK_NOINLINE static void mpack_builder_begin(mpack_writer_t* writer) { + mpack_builder_t* builder = &writer->builder; + mpack_assert(writer->error == mpack_ok); + mpack_assert(builder->current_build == NULL); + mpack_assert(builder->latest_build == NULL); + mpack_assert(builder->pages == NULL); + + // If this is the first build, we need to stash the real buffer backing our + // writer. We'll be diverting the writer to our build buffer. + builder->stash_buffer = writer->buffer; + builder->stash_position = writer->position; + builder->stash_end = writer->end; + + mpack_builder_page_t* page; + + // we've checked that both these sizes are large enough above. + #if MPACK_BUILDER_INTERNAL_STORAGE + page = (mpack_builder_page_t*)builder->internal; + mpack_log("beginning builder with internal storage %p\n", (void*)page); + #else + page = (mpack_builder_page_t*)MPACK_MALLOC(MPACK_BUILDER_PAGE_SIZE); + if (page == NULL) { + mpack_writer_flag_error(writer, mpack_error_memory); + return; + } + mpack_log("beginning builder with allocated page %p\n", (void*)page); + #endif + + page->next = NULL; + page->bytes_used = sizeof(mpack_builder_page_t); + builder->pages = page; + builder->current_page = page; +} + +static void mpack_builder_build(mpack_writer_t* writer, mpack_type_t type) { + mpack_builder_check_sizes(writer); + if (mpack_writer_error(writer) != mpack_ok) + return; + + mpack_writer_track_element(writer); + mpack_writer_track_push_builder(writer, type); + + mpack_builder_t* builder = &writer->builder; + + if (builder->current_build == NULL) { + mpack_builder_begin(writer); + } else { + mpack_builder_apply_writes(writer); + } + if (mpack_writer_error(writer) != mpack_ok) + return; + + // find aligned space for a new build. if there isn't enough space in the + // current page, we discard the remaining space in it and allocate a new + // page. + size_t offset = mpack_builder_align_build(builder->current_page->bytes_used); + if (offset + sizeof(mpack_build_t) > mpack_builder_page_size(writer, builder->current_page)) { + mpack_log("not enough space for a build. %zi bytes used of %zi in this page\n", + builder->current_page->bytes_used, mpack_builder_page_size(writer, builder->current_page)); + mpack_builder_add_page(writer); + // there is always enough space in a fresh page. + offset = mpack_builder_align_build(builder->current_page->bytes_used); + } + + // allocate the build within the page. note that we don't keep track of the + // space wasted due to the offset. instead the previous build has stored + // how many bytes follow it, and we'll redo this offset calculation to find + // this build after it. + mpack_builder_page_t* page = builder->current_page; + page->bytes_used = offset + sizeof(mpack_build_t); + mpack_assert(page->bytes_used <= mpack_builder_page_size(writer, page)); + mpack_build_t* build = (mpack_build_t*)((char*)page + offset); + mpack_log("created new build %p within page %p, which now has %zi bytes used\n", + (void*)build, (void*)page, page->bytes_used); + + // configure the new build + build->parent = builder->current_build; + build->bytes = 0; + build->count = 0; + build->type = type; + build->key_needs_value = false; + build->nested_compound_elements = 0; + + mpack_log("setting current and latest build to new build %p\n", (void*)build); + builder->current_build = build; + builder->latest_build = build; + + // we always need to provide a buffer that meets the minimum buffer size. + // if there isn't enough space, we discard the remaining space in the + // current page and allocate a new one. + if (mpack_builder_page_remaining(writer, page) < MPACK_WRITER_MINIMUM_BUFFER_SIZE) { + mpack_log("less than minimum buffer size in current page. %zi bytes used of %zi in this page\n", + builder->current_page->bytes_used, mpack_builder_page_size(writer, builder->current_page)); + mpack_builder_add_page(writer); + if (mpack_writer_error(writer) != mpack_ok) + return; + } + mpack_assert(mpack_builder_page_remaining(writer, builder->current_page) >= MPACK_WRITER_MINIMUM_BUFFER_SIZE); + mpack_builder_configure_buffer(writer); +} + +MPACK_NOINLINE +static void mpack_builder_resolve(mpack_writer_t* writer) { + mpack_builder_t* builder = &writer->builder; + + // We should not have gotten here if we are in an error state. If an error + // occurs with an open builder, the writer will free the open builder pages + // when destroyed. + mpack_assert(mpack_writer_error(writer) == mpack_ok, "can't resolve in error state!"); + + // We don't want the user to longjmp out of any I/O errors while we are + // walking the page list, so defer error callbacks to after we're done. + mpack_writer_error_t error_fn = writer->error_fn; + writer->error_fn = NULL; + + // The starting page is the internal storage (if we have it), otherwise + // it's the first page in the array + mpack_builder_page_t* page = + #if MPACK_BUILDER_INTERNAL_STORAGE + (mpack_builder_page_t*)builder->internal + #else + builder->pages + #endif + ; + + // We start by restoring the writer's original buffer so we can write the + // data for real. + writer->buffer = builder->stash_buffer; + writer->position = builder->stash_position; + writer->end = builder->stash_end; + + // We can also close out the build now. + builder->current_build = NULL; + builder->latest_build = NULL; + builder->current_page = NULL; + builder->pages = NULL; + + // the starting page always starts with the first build + size_t offset = mpack_builder_align_build(sizeof(mpack_builder_page_t)); + mpack_build_t* build = (mpack_build_t*)((char*)page + offset); + mpack_log("starting resolve with build %p in page %p\n", (void*)build, (void*)page); + + // encoded data immediately follows the build + offset += sizeof(mpack_build_t); + + // Walk the list of builds, writing everything out in the buffer. Note that + // we don't check for errors anywhere. The lower-level write functions will + // all check for errors and do nothing after an error occurs. We need to + // walk all pages anyway to free them, so there's not much point in + // optimizing an error path at the expense of the normal path. + while (true) { + + // write out the container tag + mpack_log("writing out an %s with count %" PRIu32 " followed by %zi bytes\n", + mpack_type_to_string(build->type), build->count, build->bytes); + switch (build->type) { + case mpack_type_map: + mpack_write_map_notrack(writer, build->count); + break; + case mpack_type_array: + mpack_write_array_notrack(writer, build->count); + break; + default: + mpack_break("invalid type in builder?"); + mpack_writer_flag_error(writer, mpack_error_bug); + return; + } + + // figure out how many bytes follow this container. we're going to be + // freeing pages as we write, so we need to be done with this build. + size_t left = build->bytes; + build = NULL; + + // write out all bytes following this container + while (left > 0) { + size_t bytes_used = page->bytes_used; + if (offset < bytes_used) { + size_t step = bytes_used - offset; + if (step > left) + step = left; + mpack_log("writing out %zi bytes starting at %p in page %p\n", + step, (void*)((char*)page + offset), (void*)page); + mpack_write_native(writer, (char*)page + offset, step); + offset += step; + left -= step; + } + + if (left == 0) { + mpack_log("done writing bytes for this build\n"); + break; + } + + // still need to write more bytes. free this page and jump to the + // next one. + mpack_builder_page_t* next_page = page->next; + mpack_builder_free_page(writer, page); + page = next_page; + // bytes on the next page immediately follow the header. + offset = sizeof(mpack_builder_page_t); + } + + // now see if we can find another build. + offset = mpack_builder_align_build(offset); + if (offset + sizeof(mpack_build_t) > mpack_builder_page_size(writer, page)) { + mpack_log("not enough room in this page for another build\n"); + mpack_builder_page_t* next_page = page->next; + mpack_builder_free_page(writer, page); + page = next_page; + if (page == NULL) { + mpack_log("no more pages\n"); + // there are no more pages. we're done. + break; + } + offset = mpack_builder_align_build(sizeof(mpack_builder_page_t)); + } + if (offset + sizeof(mpack_build_t) > page->bytes_used) { + // there is no more data. we're done. + mpack_log("no more data\n"); + mpack_builder_free_page(writer, page); + break; + } + + // we've found another build. loop around! + build = (mpack_build_t*)((char*)page + offset); + offset += sizeof(mpack_build_t); + mpack_log("found build %p\n", (void*)build); + } + + mpack_log("done resolve.\n"); + + // We can now restore the error handler and call it if an error occurred. + writer->error_fn = error_fn; + if (writer->error_fn && mpack_writer_error(writer) != mpack_ok) + writer->error_fn(writer, writer->error); +} + +static void mpack_builder_complete(mpack_writer_t* writer, mpack_type_t type) { + mpack_writer_track_pop_builder(writer, type); + if (mpack_writer_error(writer) != mpack_ok) + return; + + mpack_builder_t* builder = &writer->builder; + mpack_assert(builder->current_build != NULL, "no build in progress!"); + mpack_assert(builder->latest_build != NULL, "missing latest build!"); + mpack_assert(builder->current_build->type == type, "completing wrong type!"); + mpack_log("completing build %p\n", (void*)builder->current_build); + + if (builder->current_build->key_needs_value) { + mpack_break("an odd number of elements were written in a map!"); + mpack_writer_flag_error(writer, mpack_error_bug); + return; + } + + if (builder->current_build->nested_compound_elements != 0) { + mpack_break("there is a nested unfinished non-build map or array in this build."); + mpack_writer_flag_error(writer, mpack_error_bug); + return; + } + + // We need to apply whatever writes have been made to the current build + // before popping it. + mpack_builder_apply_writes(writer); + + // For a nested build, we just switch the current build back to its parent. + if (builder->current_build->parent != NULL) { + mpack_log("setting current build to parent build %p. latest is still %p.\n", + (void*)builder->current_build->parent, (void*)builder->latest_build); + builder->current_build = builder->current_build->parent; + mpack_builder_configure_buffer(writer); + } else { + // We're completing the final build. + mpack_builder_resolve(writer); + } +} + +void mpack_build_map(mpack_writer_t* writer) { + mpack_builder_build(writer, mpack_type_map); +} + +void mpack_build_array(mpack_writer_t* writer) { + mpack_builder_build(writer, mpack_type_array); +} + +void mpack_complete_map(mpack_writer_t* writer) { + mpack_builder_complete(writer, mpack_type_map); +} + +void mpack_complete_array(mpack_writer_t* writer) { + mpack_builder_complete(writer, mpack_type_array); +} + +#endif // MPACK_BUILDER +#endif // MPACK_WRITER + +MPACK_SILENCE_WARNINGS_END + +/* mpack/mpack-reader.c.c */ + +#define MPACK_INTERNAL 1 + +/* #include "mpack-reader.h" */ + +MPACK_SILENCE_WARNINGS_BEGIN + +#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", (void*)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 return -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 %" PRIu32 " bytes still in buffer\n", (uint32_t)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; + #if MPACK_FLOAT + *tag = mpack_tag_make_float(mpack_load_float(reader->data + 1)); + #else + *tag = mpack_tag_make_raw_float(mpack_load_u32(reader->data + 1)); + #endif + return MPACK_TAG_SIZE_FLOAT; + + // double + case 0xcb: + if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_DOUBLE)) + return 0; + #if MPACK_DOUBLE + *tag = mpack_tag_make_double(mpack_load_double(reader->data + 1)); + #else + *tag = mpack_tag_make_raw_double(mpack_load_u64(reader->data + 1)); + #endif + 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 & ((MPACK_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; + size_t i, j; + + switch (val.type) { + case mpack_type_str: + mpack_print_append_cstr(print, "\""); + for (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 (i = 0; i < val.v.n; ++i) { + for (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 (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 (i = 0; i < val.v.n; ++i) { + for (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 (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) { + size_t i; + for (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_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_SILENCE_WARNINGS_END + +/* mpack/mpack-expect.c.c */ + +#define MPACK_INTERNAL 1 + +/* #include "mpack-expect.h" */ + +MPACK_SILENCE_WARNINGS_BEGIN + +#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 <= MPACK_UINT8_MAX) + return (uint8_t)var.v.u; + } else if (var.type == mpack_type_int) { + if (var.v.i >= 0 && var.v.i <= MPACK_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 <= MPACK_UINT16_MAX) + return (uint16_t)var.v.u; + } else if (var.type == mpack_type_int) { + if (var.v.i >= 0 && var.v.i <= MPACK_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 <= MPACK_UINT32_MAX) + return (uint32_t)var.v.u; + } else if (var.type == mpack_type_int) { + if (var.v.i >= 0 && var.v.i <= MPACK_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 <= MPACK_INT8_MAX) + return (int8_t)var.v.u; + } else if (var.type == mpack_type_int) { + if (var.v.i >= MPACK_INT8_MIN && var.v.i <= MPACK_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 <= MPACK_INT16_MAX) + return (int16_t)var.v.u; + } else if (var.type == mpack_type_int) { + if (var.v.i >= MPACK_INT16_MIN && var.v.i <= MPACK_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 <= MPACK_INT32_MAX) + return (int32_t)var.v.u; + } else if (var.type == mpack_type_int) { + if (var.v.i >= MPACK_INT32_MIN && var.v.i <= MPACK_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 <= MPACK_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; +} + +#if MPACK_FLOAT +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; + if (var.type == mpack_type_int) + return (float)var.v.i; + if (var.type == mpack_type_float) + return var.v.f; + + if (var.type == mpack_type_double) { + #if MPACK_DOUBLE + return (float)var.v.d; + #else + return mpack_shorten_raw_double_to_float(var.v.d); + #endif + } + + mpack_reader_flag_error(reader, mpack_error_type); + return 0.0f; +} +#endif + +#if MPACK_DOUBLE +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; +} +#endif + +#if MPACK_FLOAT +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; +} +#endif + +#if MPACK_DOUBLE +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; +} +#endif + +#if !MPACK_FLOAT +uint32_t mpack_expect_raw_float(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; +} +#endif + +#if !MPACK_DOUBLE +uint64_t mpack_expect_raw_double(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_double) + return var.v.d; + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} +#endif + + +// 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)} + +#if MPACK_FLOAT +float mpack_expect_float_range(mpack_reader_t* reader, float min_value, float max_value) {MPACK_EXPECT_RANGE_IMPL(float, float)} +#endif +#if MPACK_DOUBLE +double mpack_expect_double_range(mpack_reader_t* reader, double min_value, double max_value) {MPACK_EXPECT_RANGE_IMPL(double, double)} +#endif + +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; +} + +void mpack_expect_bin_size_buf(mpack_reader_t* reader, char* buf, uint32_t size) { + mpack_assert(buf != NULL, "buf cannot be NULL"); + mpack_expect_bin_size(reader, size); + mpack_read_bytes(reader, buf, size); + mpack_done_bin(reader); +} + +#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 (SIZE_MAX < MPACK_UINT32_MAX) { + if (maxsize > SIZE_MAX) + maxsize = SIZE_MAX; + } else { + if (maxsize > (size_t)MPACK_UINT32_MAX) + maxsize = (size_t)MPACK_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 > MPACK_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, (uint32_t)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 (SIZE_MAX < MPACK_UINT32_MAX) { + if (maxsize > SIZE_MAX) + maxsize = SIZE_MAX; + } else { + if (maxsize > (size_t)MPACK_UINT32_MAX) + maxsize = (size_t)MPACK_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 (SIZE_MAX < MPACK_UINT32_MAX) { + if (maxsize > SIZE_MAX) + maxsize = SIZE_MAX; + } else { + if (maxsize > (size_t)MPACK_UINT32_MAX) + maxsize = (size_t)MPACK_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 + size_t i; + for (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 + size_t i; + for (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_SILENCE_WARNINGS_END + +/* mpack/mpack-node.c.c */ + +#define MPACK_INTERNAL 1 + +/* #include "mpack-node.h" */ + +MPACK_SILENCE_WARNINGS_BEGIN + +#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 %" PRIu32 " 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 + // MPACK_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", + (void*)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", + (void*)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_FLOAT + if (!mpack_tree_reserve_bytes(tree, sizeof(float))) + return false; + node->value.f = mpack_load_float(tree->data + tree->size + 1); + #else + if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t))) + return false; + node->value.f = mpack_load_u32(tree->data + tree->size + 1); + #endif + node->type = mpack_type_float; + return true; + + // double + case 0xcb: + #if MPACK_DOUBLE + if (!mpack_tree_reserve_bytes(tree, sizeof(double))) + return false; + node->value.d = mpack_load_double(tree->data + tree->size + 1); + #else + if (!mpack_tree_reserve_bytes(tree, sizeof(uint64_t))) + return false; + node->value.d = mpack_load_u64(tree->data + tree->size + 1); + #endif + 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", (void*)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", + (void*)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", (void*)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; + size_t i,j; + switch (data->type) { + case mpack_type_str: + { + mpack_print_append_cstr(print, "\""); + const char* bytes = mpack_node_data_unchecked(node); + for (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 (i = 0; i < data->len; ++i) { + for (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 (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 (i = 0; i < data->len; ++i) { + for (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 (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; + size_t i; + for (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 & ((MPACK_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; + + size_t i; + for (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; + + size_t i; + for (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; + + size_t i; + for (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 + size_t i; + for (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 <= MPACK_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 <= MPACK_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 <= MPACK_INT8_MAX) + return (int8_t)node.data->value.u; + } else if (node.data->type == mpack_type_int) { + if (node.data->value.i >= MPACK_INT8_MIN && node.data->value.i <= MPACK_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 <= MPACK_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 <= MPACK_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 <= MPACK_INT16_MAX) + return (int16_t)node.data->value.u; + } else if (node.data->type == mpack_type_int) { + if (node.data->value.i >= MPACK_INT16_MIN && node.data->value.i <= MPACK_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 <= MPACK_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 <= MPACK_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 <= MPACK_INT32_MAX) + return (int32_t)node.data->value.u; + } else if (node.data->type == mpack_type_int) { + if (node.data->value.i >= MPACK_INT32_MIN && node.data->value.i <= MPACK_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)MPACK_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 <= MPACK_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 >= MPACK_INT_MIN && val <= MPACK_INT_MAX) + return (int)val; + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} + +#if MPACK_FLOAT +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; + if (node.data->type == mpack_type_int) + return (float)node.data->value.i; + if (node.data->type == mpack_type_float) + return node.data->value.f; + + if (node.data->type == mpack_type_double) { + #if MPACK_DOUBLE + return (float)node.data->value.d; + #else + return mpack_shorten_raw_double_to_float(node.data->value.d); + #endif + } + + mpack_node_flag_error(node, mpack_error_type); + return 0.0f; +} +#endif + +#if MPACK_DOUBLE +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; +} +#endif + +#if MPACK_FLOAT +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; +} +#endif + +#if MPACK_DOUBLE +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; +} +#endif + +#if !MPACK_FLOAT +uint32_t mpack_node_raw_float(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type == mpack_type_float) + return node.data->value.f; + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} +#endif + +#if !MPACK_DOUBLE +uint64_t mpack_node_raw_double(mpack_node_t node) { + if (mpack_node_error(node) != mpack_ok) + return 0; + + if (node.data->type == mpack_type_double) + return node.data->value.d; + + mpack_node_flag_error(node, mpack_error_type); + return 0; +} +#endif + +#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 + +MPACK_SILENCE_WARNINGS_END diff --git a/deps/mpack/mpack.h b/deps/mpack/mpack.h new file mode 100644 index 0000000..1f2386a --- /dev/null +++ b/deps/mpack/mpack.h @@ -0,0 +1,8207 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015-2021 Nicholas Fraser and the MPack authors + * + * 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.1.1 amalgamation package. + * + * http://github.com/ludocode/mpack + */ + +#ifndef MPACK_H +#define MPACK_H 1 + +#define MPACK_AMALGAMATED 1 +#define MPACK_RELEASE_VERSION 1 + +#if defined(MPACK_HAS_CONFIG) && MPACK_HAS_CONFIG +#include "mpack-config.h" +#endif + + +/* mpack/mpack-platform.h.h */ + +/** + * @file + * + * Abstracts all platform-specific code from MPack and handles configuration + * options. + * + * This verifies the configuration and sets defaults based on the platform, + * contains implementations of standard C functions when libc is not available, + * and provides wrappers to all library functions. + * + * Documentation for configuration options is available here: + * + * https://ludocode.github.io/mpack/group__config.html + */ + +#ifndef MPACK_PLATFORM_H +#define MPACK_PLATFORM_H 1 + + + +/** + * @defgroup config Configuration Options + * + * Defines the MPack configuration options. + * + * Custom configuration of MPack is not usually necessary. In almost all + * cases you can ignore this and use the defaults. + * + * If you do want to configure MPack, you can define the below options as part + * of your build system or project settings. This will override the below + * defaults. + * + * If you'd like to use a file for configuration instead, define + * @ref MPACK_HAS_CONFIG to 1 in your build system or project settings. + * This will cause MPack to include a file you create called @c mpack-config.h + * in which you can define your configuration. This is useful if you need to + * include specific headers (such as a custom allocator) in order to configure + * MPack to use it. + * + * @warning The value of all configuration options must be the same in + * all translation units of your project, as well as in the mpack source + * itself. These configuration options affect the layout of structs, among + * other things, which cannot be different in source files that are linked + * together. + * + * @note MPack does not contain defaults for building inside the Linux kernel. + * There is a + * configuration file for the Linux kernel that can be used instead. + * + * @{ + */ + + + +/* + * Pre-include checks + * + * These need to come before the user's mpack-config.h because they might be + * including headers in it. + */ + +/** @cond */ +#if defined(_MSC_VER) && _MSC_VER < 1800 && !defined(__cplusplus) + #error "In Visual Studio 2012 and earlier, MPack must be compiled as C++. Enable the /Tp compiler flag." +#endif + +#if defined(_WIN32) && MPACK_INTERNAL + #define _CRT_SECURE_NO_WARNINGS 1 +#endif + +#ifndef __STDC_LIMIT_MACROS + #define __STDC_LIMIT_MACROS 1 +#endif +#ifndef __STDC_FORMAT_MACROS + #define __STDC_FORMAT_MACROS 1 +#endif +#ifndef __STDC_CONSTANT_MACROS + #define __STDC_CONSTANT_MACROS 1 +#endif +/** @endcond */ + + + +/** + * @name File Configuration + * @{ + */ + +/** + * @def MPACK_HAS_CONFIG + * + * Causes MPack to include a file you create called @c mpack-config.h . + * + * The file is included before MPack sets any defaults for undefined + * configuration options. You can use it to configure MPack. + * + * This is off by default. + */ +#if defined(MPACK_HAS_CONFIG) + #if MPACK_HAS_CONFIG + #include "mpack-config.h" + #endif +#else + #define MPACK_HAS_CONFIG 0 +#endif + +/** + * @} + */ + +// this needs to come first since some stuff depends on it +/** @cond */ +#ifndef MPACK_NO_BUILTINS + #define MPACK_NO_BUILTINS 0 +#endif +/** @endcond */ + + + +/** + * @name Features + * @{ + */ + +/** + * @def MPACK_READER + * + * Enables compilation of the base Tag Reader. + */ +#ifndef MPACK_READER +#define MPACK_READER 1 +#endif + +/** + * @def MPACK_EXPECT + * + * Enables compilation of the static Expect API. + */ +#ifndef MPACK_EXPECT +#define MPACK_EXPECT 1 +#endif + +/** + * @def MPACK_NODE + * + * Enables compilation of the dynamic Node API. + */ +#ifndef MPACK_NODE +#define MPACK_NODE 1 +#endif + +/** + * @def MPACK_WRITER + * + * Enables compilation of the Writer. + */ +#ifndef MPACK_WRITER +#define MPACK_WRITER 1 +#endif + +/** + * @def MPACK_BUILDER + * + * Enables compilation of the Builder. + * + * The Builder API provides additional functions to the Writer for + * automatically determining the element count of compound elements so you do + * not have to specify them up-front. + * + * This requires a @c malloc(). It is enabled by default if MPACK_WRITER is + * enabled and MPACK_MALLOC is defined. + * + * @see mpack_build_map() + * @see mpack_build_array() + * @see mpack_complete_map() + * @see mpack_complete_array() + */ +// This is defined furthur below after we've resolved whether we have malloc(). + +/** + * @def MPACK_COMPATIBILITY + * + * Enables compatibility features for reading and writing older + * versions of MessagePack. + * + * This is disabled by default. When disabled, the behaviour is equivalent to + * using the default version, @ref mpack_version_current. + * + * Enable this if you need to interoperate with applications or data that do + * not support the new (v5) MessagePack spec. See the section on v4 + * compatibility in @ref docs/protocol.md for more information. + */ +#ifndef MPACK_COMPATIBILITY +#define MPACK_COMPATIBILITY 0 +#endif + +/** + * @def MPACK_EXTENSIONS + * + * Enables the use of extension types. + * + * This is disabled by default. Define it to 1 to enable it. If disabled, + * functions to read and write extensions will not exist, and any occurrence of + * extension types in parsed messages will flag @ref mpack_error_invalid. + * + * MPack discourages the use of extension types. See the section on extension + * types in @ref docs/protocol.md for more information. + */ +#ifndef MPACK_EXTENSIONS +#define MPACK_EXTENSIONS 0 +#endif + +/** + * @} + */ + + + +// workarounds for Doxygen +#if defined(MPACK_DOXYGEN) +#if MPACK_DOXYGEN +// We give these their default values of 0 here even though they are defined to +// 1 in the doxyfile. Doxygen will show this as the value in the docs, even +// though it ignores it when parsing the rest of the source. This is what we +// want, since we want the documentation to show these defaults but still +// generate documentation for the functions they add when they're on. +#define MPACK_COMPATIBILITY 0 +#define MPACK_EXTENSIONS 0 +#endif +#endif + + + +/** + * @name Dependencies + * @{ + */ + +/** + * @def MPACK_CONFORMING + * + * Enables the inclusion of basic C headers to define standard types and + * macros. + * + * This causes MPack to include headers required for conforming implementations + * of C99 even in freestanding, in particular , , + * and . It also includes ; this is + * technically not required for freestanding but MPack needs it to detect + * integer limits. + * + * You can disable this if these headers are unavailable or if they do not + * define the standard types and macros (for example inside the Linux kernel.) + * If this is disabled, MPack will include no headers and will assume a 32-bit + * int. You will probably also want to define @ref MPACK_HAS_CONFIG to 1 and + * include your own headers in the config file. You must provide definitions + * for standard types such as @c size_t, @c bool, @c int32_t and so on. + * + * @see + * cppreference.com documentation on Conformance + */ +#ifndef MPACK_CONFORMING + #define MPACK_CONFORMING 1 +#endif + +/** + * @def MPACK_STDLIB + * + * Enables the use of the C stdlib. + * + * This allows the library to use basic functions like @c memcmp() and @c + * strlen(), as well as @c malloc() for debugging and in allocation helpers. + * + * If this is disabled, allocation helper functions will not be defined, and + * MPack will attempt to detect compiler intrinsics for functions like @c + * memcmp() (assuming @ref MPACK_NO_BUILTINS is not set.) It will fallback to + * its own (slow) implementations if it cannot use builtins. You may want to + * define @ref MPACK_MEMCMP and friends if you disable this. + * + * @see MPACK_MEMCMP + * @see MPACK_MEMCPY + * @see MPACK_MEMMOVE + * @see MPACK_MEMSET + * @see MPACK_STRLEN + * @see MPACK_MALLOC + * @see MPACK_REALLOC + * @see MPACK_FREE + */ +#ifndef MPACK_STDLIB + #if !MPACK_CONFORMING + // If we don't even have a proper we assume we won't have + // malloc() either. + #define MPACK_STDLIB 0 + #else + #define MPACK_STDLIB 1 + #endif +#endif + +/** + * @def MPACK_STDIO + * + * Enables the use of C stdio. This adds helpers for easily + * reading/writing C files and makes debugging easier. + */ +#ifndef MPACK_STDIO + #if !MPACK_STDLIB || defined(__AVR__) + #define MPACK_STDIO 0 + #else + #define MPACK_STDIO 1 + #endif +#endif + +/** + * Whether the 'float' type and floating point operations are supported. + * + * If @ref MPACK_FLOAT is disabled, floats are read and written as @c uint32_t + * instead. This way messages with floats do not result in errors and you can + * still perform manual float parsing yourself. + */ +#ifndef MPACK_FLOAT + #define MPACK_FLOAT 1 +#endif + +/** + * Whether the 'double' type is supported. This requires support for 'float'. + * + * If @ref MPACK_DOUBLE is disabled, doubles are read and written as @c + * uint32_t instead. This way messages with doubles do not result in errors and + * you can still perform manual doubles parsing yourself. + * + * If @ref MPACK_FLOAT is enabled but @ref MPACK_DOUBLE is not, doubles can be + * read as floats using the shortening conversion functions, e.g. @ref + * mpack_expect_float() or @ref mpack_node_float(). + */ +#ifndef MPACK_DOUBLE + #if !MPACK_FLOAT || defined(__AVR__) + // AVR supports only float, not double. + #define MPACK_DOUBLE 0 + #else + #define MPACK_DOUBLE 1 + #endif +#endif + +/** + * @} + */ + + + +/** + * @name Allocation Functions + * @{ + */ + +/** + * @def MPACK_MALLOC + * + * Defines the memory allocation function used by MPack. This is used by + * helpers for automatically allocating data the correct size, and for + * debugging functions. If this macro is undefined, the allocation helpers + * will not be compiled. + * + * Set this to use a custom @c malloc() function. Your function must have the + * signature: + * + * @code + * void* malloc(size_t size); + * @endcode + * + * The default is @c malloc() if @ref MPACK_STDLIB is enabled. + */ +/** + * @def MPACK_FREE + * + * Defines the memory free function used by MPack. This is used by helpers + * for automatically allocating data the correct size. If this macro is + * undefined, the allocation helpers will not be compiled. + * + * Set this to use a custom @c free() function. Your function must have the + * signature: + * + * @code + * void free(void* p); + * @endcode + * + * The default is @c free() if @ref MPACK_MALLOC has not been customized and + * @ref MPACK_STDLIB is enabled. + */ +/** + * @def MPACK_REALLOC + * + * Defines the realloc function used by MPack. It is used by growable + * buffers to resize more efficiently. + * + * The default is @c realloc() if @ref MPACK_MALLOC has not been customized and + * @ref MPACK_STDLIB is enabled. + * + * Set this to use a custom @c realloc() function. Your function must have the + * signature: + * + * @code + * void* realloc(void* p, size_t new_size); + * @endcode + * + * This is optional, even when @ref MPACK_MALLOC is used. If @ref MPACK_MALLOC is + * set and @ref MPACK_REALLOC is not, @ref MPACK_MALLOC is used with a simple copy + * to grow buffers. + */ + +#if defined(MPACK_MALLOC) && !defined(MPACK_FREE) + #error "MPACK_MALLOC requires MPACK_FREE." +#endif +#if !defined(MPACK_MALLOC) && defined(MPACK_FREE) + #error "MPACK_FREE requires MPACK_MALLOC." +#endif + +// These were never configurable in lowercase but we check anyway. +#ifdef mpack_malloc + #error "Define MPACK_MALLOC, not mpack_malloc." +#endif +#ifdef mpack_realloc + #error "Define MPACK_REALLOC, not mpack_realloc." +#endif +#ifdef mpack_free + #error "Define MPACK_FREE, not mpack_free." +#endif + +// We don't use calloc() at all. +#ifdef MPACK_CALLOC + #error "Don't define MPACK_CALLOC. MPack does not use calloc()." +#endif +#ifdef mpack_calloc + #error "Don't define mpack_calloc. MPack does not use calloc()." +#endif + +// Use defaults in stdlib if we have them. Without it we don't use malloc. +#if defined(MPACK_STDLIB) + #if MPACK_STDLIB && !defined(MPACK_MALLOC) + #define MPACK_MALLOC malloc + #define MPACK_REALLOC realloc + #define MPACK_FREE free + #endif +#endif + +/** + * @} + */ + + + +// This needs to be defined after we've decided whether we have malloc(). +#ifndef MPACK_BUILDER + #if defined(MPACK_MALLOC) && MPACK_WRITER + #define MPACK_BUILDER 1 + #else + #define MPACK_BUILDER 0 + #endif +#endif + + + +/** + * @name System Functions + * @{ + */ + +/** + * @def MPACK_MEMCMP + * + * The function MPack will use to provide @c memcmp(). + * + * Set this to use a custom @c memcmp() function. Your function must have the + * signature: + * + * @code + * int memcmp(const void* left, const void* right, size_t count); + * @endcode + */ +/** + * @def MPACK_MEMCPY + * + * The function MPack will use to provide @c memcpy(). + * + * Set this to use a custom @c memcpy() function. Your function must have the + * signature: + * + * @code + * void* memcpy(void* restrict dest, const void* restrict src, size_t count); + * @endcode + */ +/** + * @def MPACK_MEMMOVE + * + * The function MPack will use to provide @c memmove(). + * + * Set this to use a custom @c memmove() function. Your function must have the + * signature: + * + * @code + * void* memmove(void* dest, const void* src, size_t count); + * @endcode + */ +/** + * @def MPACK_MEMSET + * + * The function MPack will use to provide @c memset(). + * + * Set this to use a custom @c memset() function. Your function must have the + * signature: + * + * @code + * void* memset(void* p, int c, size_t count); + * @endcode + */ +/** + * @def MPACK_STRLEN + * + * The function MPack will use to provide @c strlen(). + * + * Set this to use a custom @c strlen() function. Your function must have the + * signature: + * + * @code + * size_t strlen(const char* str); + * @endcode + */ + +// These were briefly configurable in lowercase in an unreleased version. Just +// to make sure no one is doing this, we make sure these aren't already defined. +#ifdef mpack_memcmp + #error "Define MPACK_MEMCMP, not mpack_memcmp." +#endif +#ifdef mpack_memcpy + #error "Define MPACK_MEMCPY, not mpack_memcpy." +#endif +#ifdef mpack_memmove + #error "Define MPACK_MEMMOVE, not mpack_memmove." +#endif +#ifdef mpack_memset + #error "Define MPACK_MEMSET, not mpack_memset." +#endif +#ifdef mpack_strlen + #error "Define MPACK_STRLEN, not mpack_strlen." +#endif + +// If the standard library is available, we prefer to use its functions. +#if MPACK_STDLIB + #ifndef MPACK_MEMCMP + #define MPACK_MEMCMP memcmp + #endif + #ifndef MPACK_MEMCPY + #define MPACK_MEMCPY memcpy + #endif + #ifndef MPACK_MEMMOVE + #define MPACK_MEMMOVE memmove + #endif + #ifndef MPACK_MEMSET + #define MPACK_MEMSET memset + #endif + #ifndef MPACK_STRLEN + #define MPACK_STRLEN strlen + #endif +#endif + +#if !MPACK_NO_BUILTINS + #ifdef __has_builtin + #if !defined(MPACK_MEMCMP) && __has_builtin(__builtin_memcmp) + #define MPACK_MEMCMP __builtin_memcmp + #endif + #if !defined(MPACK_MEMCPY) && __has_builtin(__builtin_memcpy) + #define MPACK_MEMCPY __builtin_memcpy + #endif + #if !defined(MPACK_MEMMOVE) && __has_builtin(__builtin_memmove) + #define MPACK_MEMMOVE __builtin_memmove + #endif + #if !defined(MPACK_MEMSET) && __has_builtin(__builtin_memset) + #define MPACK_MEMSET __builtin_memset + #endif + #if !defined(MPACK_STRLEN) && __has_builtin(__builtin_strlen) + #define MPACK_STRLEN __builtin_strlen + #endif + #elif defined(__GNUC__) + #ifndef MPACK_MEMCMP + #define MPACK_MEMCMP __builtin_memcmp + #endif + #ifndef MPACK_MEMCPY + #define MPACK_MEMCPY __builtin_memcpy + #endif + // There's not always a builtin memmove for GCC. If we can't test for + // it with __has_builtin above, we don't use it. It's been around for + // much longer under clang, but then so has __has_builtin, so we let + // the block above handle it. + #ifndef MPACK_MEMSET + #define MPACK_MEMSET __builtin_memset + #endif + #ifndef MPACK_STRLEN + #define MPACK_STRLEN __builtin_strlen + #endif + #endif +#endif + +/** + * @} + */ + + + +/** + * @name Debugging Options + * @{ + */ + +/** + * @def MPACK_DEBUG + * + * Enables debug features. You may want to wrap this around your + * own debug preprocs. By default, this is enabled if @c DEBUG or @c _DEBUG + * are defined. (@c NDEBUG is not used since it is allowed to have + * different values in different translation units.) + */ +#if !defined(MPACK_DEBUG) + #if defined(DEBUG) || defined(_DEBUG) + #define MPACK_DEBUG 1 + #else + #define MPACK_DEBUG 0 + #endif +#endif + +/** + * @def MPACK_STRINGS + * + * Enables descriptive error and type strings. + * + * This can be turned off (by defining it to 0) to maximize space savings + * on embedded devices. If this is disabled, string functions such as + * mpack_error_to_string() and mpack_type_to_string() return an empty string. + */ +#ifndef MPACK_STRINGS + #ifdef __AVR__ + #define MPACK_STRINGS 0 + #else + #define MPACK_STRINGS 1 + #endif +#endif + +/** + * Set this to 1 to implement a custom @ref mpack_assert_fail() function. + * See the documentation on @ref mpack_assert_fail() for details. + * + * Asserts are only used when @ref MPACK_DEBUG is enabled, and can be + * triggered by bugs in MPack or bugs due to incorrect usage of MPack. + */ +#ifndef MPACK_CUSTOM_ASSERT +#define MPACK_CUSTOM_ASSERT 0 +#endif + +/** + * @def MPACK_READ_TRACKING + * + * Enables compound type size tracking for readers. This ensures that the + * correct number of elements or bytes are read from a compound type. + * + * This is enabled by default in debug builds (provided a @c malloc() is + * available.) + */ +#if !defined(MPACK_READ_TRACKING) + #if MPACK_DEBUG && MPACK_READER && defined(MPACK_MALLOC) + #define MPACK_READ_TRACKING 1 + #else + #define MPACK_READ_TRACKING 0 + #endif +#endif +#if MPACK_READ_TRACKING && !MPACK_READER + #error "MPACK_READ_TRACKING requires MPACK_READER." +#endif + +/** + * @def MPACK_WRITE_TRACKING + * + * Enables compound type size tracking for writers. This ensures that the + * correct number of elements or bytes are written in a compound type. + * + * Note that without write tracking enabled, it is possible for buggy code + * to emit invalid MessagePack without flagging an error by writing the wrong + * number of elements or bytes in a compound type. With tracking enabled, + * MPack will catch such errors and break on the offending line of code. + * + * This is enabled by default in debug builds (provided a @c malloc() is + * available.) + */ +#if !defined(MPACK_WRITE_TRACKING) + #if MPACK_DEBUG && MPACK_WRITER && defined(MPACK_MALLOC) + #define MPACK_WRITE_TRACKING 1 + #else + #define MPACK_WRITE_TRACKING 0 + #endif +#endif +#if MPACK_WRITE_TRACKING && !MPACK_WRITER + #error "MPACK_WRITE_TRACKING requires MPACK_WRITER." +#endif + +/** + * @} + */ + + + + +/** + * @name Miscellaneous Options + * @{ + */ + +/** + * Whether to optimize for size or speed. + * + * Optimizing for size simplifies some parsing and encoding algorithms + * at the expense of speed and saves a few kilobytes of space in the + * resulting executable. + * + * This automatically detects -Os with GCC/Clang. Unfortunately there + * doesn't seem to be a macro defined for /Os under MSVC. + */ +#ifndef MPACK_OPTIMIZE_FOR_SIZE + #ifdef __OPTIMIZE_SIZE__ + #define MPACK_OPTIMIZE_FOR_SIZE 1 + #else + #define MPACK_OPTIMIZE_FOR_SIZE 0 + #endif +#endif + +/** + * Stack space in bytes to use when initializing a reader or writer + * with a stack-allocated buffer. + * + * @warning Make sure you have sufficient stack space. Some libc use relatively + * small stacks even on desktop platforms, e.g. musl. + */ +#ifndef MPACK_STACK_SIZE +#define MPACK_STACK_SIZE 4096 +#endif + +/** + * Buffer size to use for allocated buffers (such as for a file writer.) + * + * Starting with a single page and growing as needed seems to + * provide the best performance with minimal memory waste. + * Increasing this does not improve performance even when writing + * huge messages. + */ +#ifndef MPACK_BUFFER_SIZE +#define MPACK_BUFFER_SIZE 4096 +#endif + +/** + * Minimum size for paged allocations in bytes. + * + * This is the value used by default for MPACK_NODE_PAGE_SIZE and + * MPACK_BUILDER_PAGE_SIZE. + */ +#ifndef MPACK_PAGE_SIZE +#define MPACK_PAGE_SIZE 4096 +#endif + +/** + * Minimum size of an allocated node page in bytes. + * + * The children for a given compound element must be contiguous, so + * larger pages than this may be allocated as needed. (Safety checks + * exist to prevent malicious data from causing too large allocations.) + * + * See @ref mpack_node_data_t for the size of nodes. + * + * Using as many nodes fit in one memory page seems to provide the + * best performance, and has very little waste when parsing small + * messages. + */ +#ifndef MPACK_NODE_PAGE_SIZE +#define MPACK_NODE_PAGE_SIZE MPACK_PAGE_SIZE +#endif + +/** + * Minimum size of an allocated builder page in bytes. + * + * Builder writes are deferred to the allocated builder buffer which is + * composed of a list of buffer pages. This defines the size of those pages. + */ +#ifndef MPACK_BUILDER_PAGE_SIZE +#define MPACK_BUILDER_PAGE_SIZE MPACK_PAGE_SIZE +#endif + +/** + * @def MPACK_BUILDER_INTERNAL_STORAGE + * + * Enables a small amount of internal storage within the writer to avoid some + * allocations when using builders. + * + * This is disabled by default. Enable it to potentially improve performance at + * the expense of a larger writer. + * + * @see MPACK_BUILDER_INTERNAL_STORAGE_SIZE to configure its size. + */ +#ifndef MPACK_BUILDER_INTERNAL_STORAGE +#define MPACK_BUILDER_INTERNAL_STORAGE 0 +#endif + +/** + * Amount of space reserved inside @ref mpack_writer_t for the Builders. This + * can allow small messages to be built with the Builder API without incurring + * an allocation. + * + * Builder metadata is placed in this space in addition to the literal + * MessagePack data. It needs to be big enough to be useful, but not so big as + * to overflow the stack. If more space is needed, pages are allocated. + * + * This is only used if MPACK_BUILDER_INTERNAL_STORAGE is enabled. + * + * @see MPACK_BUILDER_PAGE_SIZE + * @see MPACK_BUILDER_INTERNAL_STORAGE + * + * @warning Writers are typically placed on the stack so make sure you have + * sufficient stack space. Some libc use relatively small stacks even on + * desktop platforms, e.g. musl. + */ +#ifndef MPACK_BUILDER_INTERNAL_STORAGE_SIZE +#define MPACK_BUILDER_INTERNAL_STORAGE_SIZE 256 +#endif + +/** + * The initial depth for the node parser. When MPACK_MALLOC is available, + * the node parser has no practical depth limit, and it is not recursive + * so there is no risk of overflowing the call stack. + */ +#ifndef MPACK_NODE_INITIAL_DEPTH +#define MPACK_NODE_INITIAL_DEPTH 8 +#endif + +/** + * The maximum depth for the node parser if @ref MPACK_MALLOC is not available. + */ +#ifndef MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC +#define MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC 32 +#endif + +/** + * @def MPACK_NO_BUILTINS + * + * Whether to disable compiler intrinsics and other built-in functions. + * + * If this is enabled, MPack won't use `__attribute__`, `__declspec`, any + * function starting with `__builtin`, or pretty much anything else that isn't + * standard C. + */ +#if defined(MPACK_DOXYGEN) +#if MPACK_DOXYGEN + #define MPACK_NO_BUILTINS 0 +#endif +#endif + +/** + * @} + */ + + + +#if MPACK_DEBUG +/** + * @name Debug Functions + * @{ + */ +/** + * Implement this and define @ref MPACK_CUSTOM_ASSERT to use a custom + * assertion function. + * + * This function should not return. If it does, MPack will @c abort(). + * + * If you use C++, make sure you include @c mpack.h where you define + * this to get the correct linkage (or define it extern "C".) + * + * Asserts are only used when @ref MPACK_DEBUG is enabled, and can be + * triggered by bugs in MPack or bugs due to incorrect usage of MPack. + */ +void mpack_assert_fail(const char* message); +/** + * @} + */ +#endif + + + +// The rest of this file shouldn't show up in Doxygen docs. +/** @cond */ + + + +/* + * All remaining pseudo-configuration options that have not yet been set must + * be defined here in order to support -Wundef. + * + * These aren't real configuration options; they are implementation details of + * MPack. + */ +#ifndef MPACK_CUSTOM_BREAK +#define MPACK_CUSTOM_BREAK 0 +#endif +#ifndef MPACK_EMIT_INLINE_DEFS +#define MPACK_EMIT_INLINE_DEFS 0 +#endif +#ifndef MPACK_AMALGAMATED +#define MPACK_AMALGAMATED 0 +#endif +#ifndef MPACK_RELEASE_VERSION +#define MPACK_RELEASE_VERSION 0 +#endif +#ifndef MPACK_INTERNAL +#define MPACK_INTERNAL 0 +#endif + + + +/* System headers (based on configuration) */ + +#if MPACK_CONFORMING + #include + #include + #include + #include + #include +#endif + +#if MPACK_STDLIB + #include + #include +#endif + +#if MPACK_STDIO + #include + #include + #if MPACK_DEBUG + #include + #endif +#endif + + + +/* + * Integer Constants and Limits + */ + +#if MPACK_CONFORMING + #define MPACK_INT64_C INT64_C + #define MPACK_UINT64_C UINT64_C + + #define MPACK_INT8_MIN INT8_MIN + #define MPACK_INT16_MIN INT16_MIN + #define MPACK_INT32_MIN INT32_MIN + #define MPACK_INT64_MIN INT64_MIN + #define MPACK_INT_MIN INT_MIN + + #define MPACK_INT8_MAX INT8_MAX + #define MPACK_INT16_MAX INT16_MAX + #define MPACK_INT32_MAX INT32_MAX + #define MPACK_INT64_MAX INT64_MAX + #define MPACK_INT_MAX INT_MAX + + #define MPACK_UINT8_MAX UINT8_MAX + #define MPACK_UINT16_MAX UINT16_MAX + #define MPACK_UINT32_MAX UINT32_MAX + #define MPACK_UINT64_MAX UINT64_MAX + #define MPACK_UINT_MAX UINT_MAX +#else + // For a non-conforming implementation we assume int is 32 bits. + + #define MPACK_INT64_C(x) ((int64_t)(x##LL)) + #define MPACK_UINT64_C(x) ((uint64_t)(x##LLU)) + + #define MPACK_INT8_MIN ((int8_t)(0x80)) + #define MPACK_INT16_MIN ((int16_t)(0x8000)) + #define MPACK_INT32_MIN ((int32_t)(0x80000000)) + #define MPACK_INT64_MIN MPACK_INT64_C(0x8000000000000000) + #define MPACK_INT_MIN MPACK_INT32_MIN + + #define MPACK_INT8_MAX ((int8_t)(0x7f)) + #define MPACK_INT16_MAX ((int16_t)(0x7fff)) + #define MPACK_INT32_MAX ((int32_t)(0x7fffffff)) + #define MPACK_INT64_MAX MPACK_INT64_C(0x7fffffffffffffff) + #define MPACK_INT_MAX MPACK_INT32_MAX + + #define MPACK_UINT8_MAX ((uint8_t)(0xffu)) + #define MPACK_UINT16_MAX ((uint16_t)(0xffffu)) + #define MPACK_UINT32_MAX ((uint32_t)(0xffffffffu)) + #define MPACK_UINT64_MAX MPACK_UINT64_C(0xffffffffffffffff) + #define MPACK_UINT_MAX MPACK_UINT32_MAX +#endif + + + +/* + * Floating point support + */ + +#if MPACK_DOUBLE && !MPACK_FLOAT + #error "MPACK_DOUBLE requires MPACK_FLOAT." +#endif + +// If we don't have support for float or double, we poison the identifiers to +// make sure we don't define anything related to them. +#if MPACK_INTERNAL + #ifdef __GNUC__ + #if !MPACK_FLOAT + #pragma GCC poison float + #endif + #if !MPACK_DOUBLE + #pragma GCC poison double + #endif + #endif +#endif + + + +/* + * extern C + */ + +#ifdef __cplusplus + #define MPACK_EXTERN_C_BEGIN extern "C" { + #define MPACK_EXTERN_C_END } +#else + #define MPACK_EXTERN_C_BEGIN /*nothing*/ + #define MPACK_EXTERN_C_END /*nothing*/ +#endif + + + +/* + * Warnings + */ + +#if defined(__GNUC__) + // Diagnostic push is not supported before GCC 4.6. + #if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) + #define MPACK_SILENCE_WARNINGS_PUSH _Pragma ("GCC diagnostic push") + #define MPACK_SILENCE_WARNINGS_POP _Pragma ("GCC diagnostic pop") + #endif +#elif defined(_MSC_VER) + // To support VS2017 and earlier we need to use __pragma and not _Pragma + #define MPACK_SILENCE_WARNINGS_PUSH __pragma(warning(push)) + #define MPACK_SILENCE_WARNINGS_POP __pragma(warning(pop)) +#endif + +#if defined(_MSC_VER) + // These are a bunch of mostly useless warnings emitted under MSVC /W4, + // some as a result of the expansion of macros. + #define MPACK_SILENCE_WARNINGS_MSVC_W4 \ + __pragma(warning(disable:4996)) /* _CRT_SECURE_NO_WARNINGS */ \ + __pragma(warning(disable:4127)) /* comparison is constant */ \ + __pragma(warning(disable:4702)) /* unreachable code */ \ + __pragma(warning(disable:4310)) /* cast truncates constant value */ +#else + #define MPACK_SILENCE_WARNINGS_MSVC_W4 /*nothing*/ +#endif + +/* GCC versions before 5.1 warn about defining a C99 non-static inline function + * before declaring it (see issue #20). */ +#if defined(__GNUC__) && !defined(__clang__) + #if __GNUC__ < 5 || (__GNUC__ == 5 && __GNUC_MINOR__ < 1) + #ifdef __cplusplus + #define MPACK_SILENCE_WARNINGS_MISSING_PROTOTYPES \ + _Pragma ("GCC diagnostic ignored \"-Wmissing-declarations\"") + #else + #define MPACK_SILENCE_WARNINGS_MISSING_PROTOTYPES \ + _Pragma ("GCC diagnostic ignored \"-Wmissing-prototypes\"") + #endif + #endif +#endif +#ifndef MPACK_SILENCE_WARNINGS_MISSING_PROTOTYPES + #define MPACK_SILENCE_WARNINGS_MISSING_PROTOTYPES /*nothing*/ +#endif + +/* GCC versions before 4.8 warn about shadowing a function with a variable that + * isn't a function or function pointer (like "index"). */ +#if defined(__GNUC__) && !defined(__clang__) + #if __GNUC__ == 4 && __GNUC_MINOR__ < 8 + #define MPACK_SILENCE_WARNINGS_SHADOW \ + _Pragma ("GCC diagnostic ignored \"-Wshadow\"") + #endif +#endif +#ifndef MPACK_SILENCE_WARNINGS_SHADOW + #define MPACK_SILENCE_WARNINGS_SHADOW /*nothing*/ +#endif + +// On platforms with small size_t (e.g. AVR) we get type limits warnings where +// we compare a size_t to e.g. MPACK_UINT32_MAX. +#ifdef __AVR__ + #define MPACK_SILENCE_WARNINGS_TYPE_LIMITS \ + _Pragma ("GCC diagnostic ignored \"-Wtype-limits\"") +#else + #define MPACK_SILENCE_WARNINGS_TYPE_LIMITS /*nothing*/ +#endif + +// MPack uses declarations after statements. This silences warnings about it +// (e.g. when using MPack in a Linux kernel module.) +#if defined(__GNUC__) && !defined(__cplusplus) + #define MPACK_SILENCE_WARNINGS_DECLARATION_AFTER_STATEMENT \ + _Pragma ("GCC diagnostic ignored \"-Wdeclaration-after-statement\"") +#else + #define MPACK_SILENCE_WARNINGS_DECLARATION_AFTER_STATEMENT /*nothing*/ +#endif + +#ifdef MPACK_SILENCE_WARNINGS_PUSH + // We only silence warnings if push/pop is supported, that way we aren't + // silencing warnings in code that uses MPack. If your compiler doesn't + // support push/pop silencing of warnings, you'll have to turn off + // conflicting warnings manually. + + #define MPACK_SILENCE_WARNINGS_BEGIN \ + MPACK_SILENCE_WARNINGS_PUSH \ + MPACK_SILENCE_WARNINGS_MSVC_W4 \ + MPACK_SILENCE_WARNINGS_MISSING_PROTOTYPES \ + MPACK_SILENCE_WARNINGS_SHADOW \ + MPACK_SILENCE_WARNINGS_TYPE_LIMITS \ + MPACK_SILENCE_WARNINGS_DECLARATION_AFTER_STATEMENT + + #define MPACK_SILENCE_WARNINGS_END \ + MPACK_SILENCE_WARNINGS_POP +#else + #define MPACK_SILENCE_WARNINGS_BEGIN /*nothing*/ + #define MPACK_SILENCE_WARNINGS_END /*nothing*/ +#endif + +MPACK_SILENCE_WARNINGS_BEGIN +MPACK_EXTERN_C_BEGIN + + + +/* Miscellaneous helper macros */ + +#define MPACK_UNUSED(var) ((void)(var)) + +#define MPACK_STRINGIFY_IMPL(arg) #arg +#define MPACK_STRINGIFY(arg) MPACK_STRINGIFY_IMPL(arg) + +// This is a workaround for MSVC's incorrect expansion of __VA_ARGS__. It +// treats __VA_ARGS__ as a single preprocessor token when passed in the +// argument list of another macro unless we use an outer wrapper to expand it +// lexically first. (For safety/consistency we use this in all variadic macros +// that don't ignore the variadic arguments regardless of whether __VA_ARGS__ +// is passed to another macro.) +// https://stackoverflow.com/a/32400131 +#define MPACK_EXPAND(x) x + +// Extracts the first argument of a variadic macro list, where there might only +// be one argument. +#define MPACK_EXTRACT_ARG0_IMPL(first, ...) first +#define MPACK_EXTRACT_ARG0(...) MPACK_EXPAND(MPACK_EXTRACT_ARG0_IMPL( __VA_ARGS__ , ignored)) + +// Stringifies the first argument of a variadic macro list, where there might +// only be one argument. +#define MPACK_STRINGIFY_ARG0_impl(first, ...) #first +#define MPACK_STRINGIFY_ARG0(...) MPACK_EXPAND(MPACK_STRINGIFY_ARG0_impl( __VA_ARGS__ , ignored)) + + + +/* + * Definition of inline macros. + * + * MPack does not use static inline in header files; only one non-inline definition + * of each function should exist in the final build. This can reduce the binary size + * in cases where the compiler cannot or chooses not to inline a function. + * The addresses of functions should also compare equal across translation units + * regardless of whether they are declared inline. + * + * The above requirements mean that the declaration and definition of non-trivial + * inline functions must be separated so that the definitions will only + * appear when necessary. In addition, three different linkage models need + * to be supported: + * + * - The C99 model, where a standalone function is emitted only if there is any + * `extern inline` or non-`inline` declaration (including the definition itself) + * + * - The GNU model, where an `inline` definition emits a standalone function and an + * `extern inline` definition does not, regardless of other declarations + * + * - The C++ model, where `inline` emits a standalone function with special + * (COMDAT) linkage + * + * The macros below wrap up everything above. All inline functions defined in header + * files have a single non-inline definition emitted in the compilation of + * mpack-platform.c. All inline declarations and definitions use the same MPACK_INLINE + * specification to simplify the rules on when standalone functions are emitted. + * Inline functions in source files are defined MPACK_STATIC_INLINE. + * + * Additional reading: + * http://www.greenend.org.uk/rjk/tech/inline.html + */ + +#if defined(__cplusplus) + // C++ rules + // The linker will need COMDAT support to link C++ object files, + // so we don't need to worry about emitting definitions from C++ + // translation units. If mpack-platform.c (or the amalgamation) + // is compiled as C, its definition will be used, otherwise a + // C++ definition will be used, and no other C files will emit + // a definition. + #define MPACK_INLINE inline + +#elif defined(_MSC_VER) + // MSVC 2013 always uses COMDAT linkage, but it doesn't treat 'inline' as a + // keyword in C99 mode. (This appears to be fixed in a later version of + // MSVC but we don't bother detecting it.) + #define MPACK_INLINE __inline + #define MPACK_STATIC_INLINE static __inline + +#elif defined(__GNUC__) && (defined(__GNUC_GNU_INLINE__) || \ + (!defined(__GNUC_STDC_INLINE__) && !defined(__GNUC_GNU_INLINE__))) + // GNU rules + #if MPACK_EMIT_INLINE_DEFS + #define MPACK_INLINE inline + #else + #define MPACK_INLINE extern inline + #endif + +#elif defined(__TINYC__) + // tcc ignores the inline keyword, so we have to use static inline. We + // issue a warning to make sure you are aware. You can define the below + // macro to disable the warning. Hopefully this will be fixed soon: + // https://lists.nongnu.org/archive/html/tinycc-devel/2019-06/msg00000.html + #ifndef MPACK_DISABLE_TINYC_INLINE_WARNING + #warning "Single-definition inline is not supported by tcc. All inlines will be static. Define MPACK_DISABLE_TINYC_INLINE_WARNING to disable this warning." + #endif + #define MPACK_INLINE static inline + +#else + // C99 rules + #if MPACK_EMIT_INLINE_DEFS + #define MPACK_INLINE extern inline + #else + #define MPACK_INLINE inline + #endif +#endif + +#ifndef MPACK_STATIC_INLINE +#define MPACK_STATIC_INLINE static inline +#endif + +#ifdef MPACK_OPTIMIZE_FOR_SPEED + #error "You should define MPACK_OPTIMIZE_FOR_SIZE, not MPACK_OPTIMIZE_FOR_SPEED." +#endif + + + +/* + * Prevent inlining + * + * When a function is only used once, it is almost always inlined + * automatically. This can cause poor instruction cache usage because a + * function that should rarely be called (such as buffer exhaustion handling) + * will get inlined into the middle of a hot code path. + */ + +#if !MPACK_NO_BUILTINS + #if defined(_MSC_VER) + #define MPACK_NOINLINE __declspec(noinline) + #elif defined(__GNUC__) || defined(__clang__) + #define MPACK_NOINLINE __attribute__((__noinline__)) + #endif +#endif +#ifndef MPACK_NOINLINE + #define MPACK_NOINLINE /* nothing */ +#endif + + + +/* restrict */ + +// We prefer the builtins even though e.g. MSVC's __restrict may not have +// exactly the same behaviour as the proper C99 restrict keyword because the +// builtins work in C++, so using the same keyword in both C and C++ prevents +// any incompatibilities when using MPack compiled as C in C++ code. +#if !MPACK_NO_BUILTINS + #if defined(__GNUC__) + #define MPACK_RESTRICT __restrict__ + #elif defined(_MSC_VER) + #define MPACK_RESTRICT __restrict + #endif +#endif + +#ifndef MPACK_RESTRICT + #ifdef __cplusplus + #define MPACK_RESTRICT /* nothing, unavailable in C++ */ + #endif +#endif + +#ifndef MPACK_RESTRICT + #ifdef _MSC_VER + // MSVC 2015 apparently doesn't properly support the restrict keyword + // in C. We're using builtins above which do work on 2015, but when + // MPACK_NO_BUILTINS is enabled we can't use it. + #if _MSC_VER < 1910 + #define MPACK_RESTRICT /*nothing*/ + #endif + #endif +#endif + +#ifndef MPACK_RESTRICT + #define MPACK_RESTRICT restrict /* required in C99 */ +#endif + + + +/* Some compiler-specific keywords and builtins */ + +#if !MPACK_NO_BUILTINS + #if defined(__GNUC__) || defined(__clang__) + #define MPACK_UNREACHABLE __builtin_unreachable() + #define MPACK_NORETURN(fn) fn __attribute__((__noreturn__)) + #elif defined(_MSC_VER) + #define MPACK_UNREACHABLE __assume(0) + #define MPACK_NORETURN(fn) __declspec(noreturn) fn + #endif +#endif + +#ifndef MPACK_UNREACHABLE +#define MPACK_UNREACHABLE ((void)0) +#endif +#ifndef MPACK_NORETURN +#define MPACK_NORETURN(fn) fn +#endif + + + +/* + * Likely/unlikely + * + * These should only really be used when a branch is taken (or not taken) less + * than 1/1000th of the time. Buffer flush checks when writing very small + * elements are a good example. + */ + +#if !MPACK_NO_BUILTINS + #if defined(__GNUC__) || defined(__clang__) + #ifndef MPACK_LIKELY + #define MPACK_LIKELY(x) __builtin_expect((x),1) + #endif + #ifndef MPACK_UNLIKELY + #define MPACK_UNLIKELY(x) __builtin_expect((x),0) + #endif + #endif +#endif + +#ifndef MPACK_LIKELY + #define MPACK_LIKELY(x) (x) +#endif +#ifndef MPACK_UNLIKELY + #define MPACK_UNLIKELY(x) (x) +#endif + + + +/* alignof */ + +#ifndef MPACK_ALIGNOF + #if defined(__STDC_VERSION__) + #if __STDC_VERSION__ >= 201112L + #define MPACK_ALIGNOF(T) (_Alignof(T)) + #endif + #endif +#endif + +#ifndef MPACK_ALIGNOF + #if defined(__cplusplus) + #if __cplusplus >= 201103L + #define MPACK_ALIGNOF(T) (alignof(T)) + #endif + #endif +#endif + +#ifndef MPACK_ALIGNOF + #if defined(__GNUC__) && !defined(MPACK_NO_BUILTINS) + #if defined(__clang__) || __GNUC__ >= 4 + #define MPACK_ALIGNOF(T) (__alignof__(T)) + #endif + #endif +#endif + +#ifndef MPACK_ALIGNOF + #ifdef _MSC_VER + #define MPACK_ALIGNOF(T) __alignof(T) + #endif +#endif + +// MPACK_ALIGNOF may not exist, in which case a workaround is used. + + + +/* Static assert */ + +#ifndef MPACK_STATIC_ASSERT + #if defined(__cplusplus) + #if __cplusplus >= 201103L + #define MPACK_STATIC_ASSERT static_assert + #endif + #elif defined(__STDC_VERSION__) + #if __STDC_VERSION__ >= 201112L + #define MPACK_STATIC_ASSERT _Static_assert + #endif + #endif +#endif + +#if !MPACK_NO_BUILTINS + #ifndef MPACK_STATIC_ASSERT + #if defined(__has_feature) + #if __has_feature(cxx_static_assert) + #define MPACK_STATIC_ASSERT static_assert + #elif __has_feature(c_static_assert) + #define MPACK_STATIC_ASSERT _Static_assert + #endif + #endif + #endif + + #ifndef MPACK_STATIC_ASSERT + #if defined(__GNUC__) + /* Diagnostic push is not supported before GCC 4.6. */ + #if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) + #ifndef __cplusplus + #if defined(__clang__) || __GNUC__ >= 5 + #define MPACK_IGNORE_PEDANTIC "GCC diagnostic ignored \"-Wpedantic\"" + #else + #define MPACK_IGNORE_PEDANTIC "GCC diagnostic ignored \"-pedantic\"" + #endif + #define MPACK_STATIC_ASSERT(expr, str) do { \ + _Pragma ("GCC diagnostic push") \ + _Pragma (MPACK_IGNORE_PEDANTIC) \ + _Pragma ("GCC diagnostic ignored \"-Wc++-compat\"") \ + _Static_assert(expr, str); \ + _Pragma ("GCC diagnostic pop") \ + } while (0) + #endif + #endif + #endif + #endif + + #ifndef MPACK_STATIC_ASSERT + #ifdef _MSC_VER + #if _MSC_VER >= 1600 + #define MPACK_STATIC_ASSERT static_assert + #endif + #endif + #endif +#endif + +#ifndef MPACK_STATIC_ASSERT + #define MPACK_STATIC_ASSERT(expr, str) (MPACK_UNUSED(sizeof(char[1 - 2*!(expr)]))) +#endif + + + +/* _Generic */ + +#ifndef MPACK_HAS_GENERIC + #if defined(__clang__) && defined(__has_feature) + // With Clang we can test for _Generic support directly + // and ignore C/C++ version + #if __has_feature(c_generic_selections) + #define MPACK_HAS_GENERIC 1 + #else + #define MPACK_HAS_GENERIC 0 + #endif + #endif +#endif + +#ifndef MPACK_HAS_GENERIC + #if defined(__STDC_VERSION__) + #if __STDC_VERSION__ >= 201112L + #if defined(__GNUC__) && !defined(__clang__) + // GCC does not have full C11 support in GCC 4.7 and 4.8 + #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9) + #define MPACK_HAS_GENERIC 1 + #endif + #else + // We hope other compilers aren't lying about C11/_Generic support + #define MPACK_HAS_GENERIC 1 + #endif + #endif + #endif +#endif + +#ifndef MPACK_HAS_GENERIC + #define MPACK_HAS_GENERIC 0 +#endif + + + +/* + * Finite Math + * + * -ffinite-math-only, included in -ffast-math, breaks functions that + * that check for non-finite real values such as isnan() and isinf(). + * + * We should use this to trap errors when reading data that contains + * non-finite reals. This isn't currently implemented. + */ + +#ifndef MPACK_FINITE_MATH +#if defined(__FINITE_MATH_ONLY__) && __FINITE_MATH_ONLY__ +#define MPACK_FINITE_MATH 1 +#endif +#endif + +#ifndef MPACK_FINITE_MATH +#define MPACK_FINITE_MATH 0 +#endif + + + +/* + * Endianness checks + * + * These define MPACK_NHSWAP*() which swap network<->host byte + * order when needed. + * + * We leave them undefined if we can't determine the endianness + * at compile-time, in which case we fall back to bit-shifts. + * + * See the notes in mpack-common.h. + */ + +#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && defined(__ORDER_BIG_ENDIAN__) + #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + #define MPACK_NHSWAP16(x) (x) + #define MPACK_NHSWAP32(x) (x) + #define MPACK_NHSWAP64(x) (x) + #elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + + #if !MPACK_NO_BUILTINS + #if defined(__clang__) + #ifdef __has_builtin + // Unlike the GCC builtins, the bswap builtins in Clang + // significantly improve ARM performance. + #if __has_builtin(__builtin_bswap16) + #define MPACK_NHSWAP16(x) __builtin_bswap16(x) + #endif + #if __has_builtin(__builtin_bswap32) + #define MPACK_NHSWAP32(x) __builtin_bswap32(x) + #endif + #if __has_builtin(__builtin_bswap64) + #define MPACK_NHSWAP64(x) __builtin_bswap64(x) + #endif + #endif + + #elif defined(__GNUC__) + + // The GCC bswap builtins are apparently poorly optimized on older + // versions of GCC, so we set a minimum version here just in case. + // http://hardwarebug.org/2010/01/14/beware-the-builtins/ + + #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) + #define MPACK_NHSWAP64(x) __builtin_bswap64(x) + #endif + + // __builtin_bswap16() was not implemented on all platforms + // until GCC 4.8.0: + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52624 + // + // The 16- and 32-bit versions in GCC significantly reduce performance + // on ARM with little effect on code size so we don't use them. + + #endif + #endif + #endif + +#elif defined(_MSC_VER) && defined(_WIN32) && MPACK_STDLIB && !MPACK_NO_BUILTINS + + // On Windows, we assume x86 and x86_64 are always little-endian. + // We make no assumptions about ARM even though all current + // Windows Phone devices are little-endian in case Microsoft's + // compiler is ever used with a big-endian ARM device. + + // These are functions in so we depend on MPACK_STDLIB. + // It's not clear if these are actually faster than just doing the + // swap manually; maybe we shouldn't bother with this. + + #if defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64) + #define MPACK_NHSWAP16(x) _byteswap_ushort(x) + #define MPACK_NHSWAP32(x) _byteswap_ulong(x) + #define MPACK_NHSWAP64(x) _byteswap_uint64(x) + #endif + +#endif + +#if defined(__FLOAT_WORD_ORDER__) && defined(__BYTE_ORDER__) + + // We check where possible that the float byte order matches the + // integer byte order. This is extremely unlikely to fail, but + // we check anyway just in case. + // + // (The static assert is placed in float/double encoders instead + // of here because our static assert fallback doesn't work at + // file scope) + + #define MPACK_CHECK_FLOAT_ORDER() \ + MPACK_STATIC_ASSERT(__FLOAT_WORD_ORDER__ == __BYTE_ORDER__, \ + "float byte order does not match int byte order! float/double " \ + "encoding is not properly implemented on this platform.") + +#endif + +#ifndef MPACK_CHECK_FLOAT_ORDER + #define MPACK_CHECK_FLOAT_ORDER() /* nothing */ +#endif + + +/* + * Here we define mpack_assert() and mpack_break(). They both work like a normal + * assertion function in debug mode, causing a trap or abort. However, on some platforms + * you can safely resume execution from mpack_break(), whereas mpack_assert() is + * always fatal. + * + * In release mode, mpack_assert() is converted to an assurance to the compiler + * that the expression cannot be false (via e.g. __assume() or __builtin_unreachable()) + * to improve optimization where supported. There is thus no point in "safely" handling + * the case of this being false. Writing mpack_assert(0) rarely makes sense (except + * possibly as a default handler in a switch) since the compiler will throw away any + * code after it. If at any time an mpack_assert() is not true, the behaviour is + * undefined. This also means the expression is evaluated even in release. + * + * mpack_break() on the other hand is compiled to nothing in release. It is + * used in situations where we want to highlight a programming error as early as + * possible (in the debugger), but we still handle the situation safely if it + * happens in release to avoid producing incorrect results (such as in + * MPACK_WRITE_TRACKING.) It does not take an expression to test because it + * belongs in a safe-handling block after its failing condition has been tested. + * + * If stdio is available, we can add a format string describing the error, and + * on some compilers we can declare it noreturn to get correct results from static + * analysis tools. Note that the format string and arguments are not evaluated unless + * the assertion is hit. + * + * Note that any arguments to mpack_assert() beyond the first are only evaluated + * if the expression is false (and are never evaluated in release.) + * + * mpack_assert_fail() and mpack_break_hit() are defined separately + * because assert is noreturn and break isn't. This distinction is very + * important for static analysis tools to give correct results. + */ + +#if MPACK_DEBUG + MPACK_NORETURN(void mpack_assert_fail_wrapper(const char* message)); + #if MPACK_STDIO + MPACK_NORETURN(void mpack_assert_fail_format(const char* format, ...)); + #define mpack_assert_fail_at(line, file, exprstr, format, ...) \ + MPACK_EXPAND(mpack_assert_fail_format("mpack assertion failed at " file ":" #line "\n%s\n" format, exprstr, __VA_ARGS__)) + #else + #define mpack_assert_fail_at(line, file, exprstr, format, ...) \ + mpack_assert_fail_wrapper("mpack assertion failed at " file ":" #line "\n" exprstr "\n") + #endif + + #define mpack_assert_fail_pos(line, file, exprstr, expr, ...) \ + MPACK_EXPAND(mpack_assert_fail_at(line, file, exprstr, __VA_ARGS__)) + + // This contains a workaround to the pedantic C99 requirement of having at + // least one argument to a variadic macro. The first argument is the + // boolean expression, the optional second argument (if provided) must be a + // literal format string, and any additional arguments are the format + // argument list. + // + // Unfortunately this means macros are expanded in the expression before it + // gets stringified. I haven't found a workaround to this. + // + // This adds two unused arguments to the format argument list when a + // format string is provided, so this would complicate the use of + // -Wformat and __attribute__((__format__)) on mpack_assert_fail_format() + // if we ever bothered to implement it. + #define mpack_assert(...) \ + MPACK_EXPAND(((!(MPACK_EXTRACT_ARG0(__VA_ARGS__))) ? \ + mpack_assert_fail_pos(__LINE__, __FILE__, MPACK_STRINGIFY_ARG0(__VA_ARGS__) , __VA_ARGS__ , "", NULL) : \ + (void)0)) + + void mpack_break_hit(const char* message); + #if MPACK_STDIO + void mpack_break_hit_format(const char* format, ...); + #define mpack_break_hit_at(line, file, ...) \ + MPACK_EXPAND(mpack_break_hit_format("mpack breakpoint hit at " file ":" #line "\n" __VA_ARGS__)) + #else + #define mpack_break_hit_at(line, file, ...) \ + mpack_break_hit("mpack breakpoint hit at " file ":" #line ) + #endif + #define mpack_break_hit_pos(line, file, ...) MPACK_EXPAND(mpack_break_hit_at(line, file, __VA_ARGS__)) + #define mpack_break(...) MPACK_EXPAND(mpack_break_hit_pos(__LINE__, __FILE__, __VA_ARGS__)) +#else + #define mpack_assert(...) \ + (MPACK_EXPAND((!(MPACK_EXTRACT_ARG0(__VA_ARGS__))) ? \ + (MPACK_UNREACHABLE, (void)0) : \ + (void)0)) + #define mpack_break(...) ((void)0) +#endif + + + +// make sure we don't use the stdlib directly during development +#if MPACK_STDLIB && defined(MPACK_UNIT_TESTS) && MPACK_INTERNAL && defined(__GNUC__) + #undef memcmp + #undef memcpy + #undef memmove + #undef memset + #undef strlen + #undef malloc + #undef calloc + #undef realloc + #undef free + #pragma GCC poison memcmp + #pragma GCC poison memcpy + #pragma GCC poison memmove + #pragma GCC poison memset + #pragma GCC poison strlen + #pragma GCC poison malloc + #pragma GCC poison calloc + #pragma GCC poison realloc + #pragma GCC poison free +#endif + + + +// If we don't have these stdlib functions, we need to define them ourselves. +// Either way we give them a lowercase name to make the code a bit nicer. + +#ifdef MPACK_MEMCMP + #define mpack_memcmp MPACK_MEMCMP +#else + int mpack_memcmp(const void* s1, const void* s2, size_t n); +#endif + +#ifdef MPACK_MEMCPY + #define mpack_memcpy MPACK_MEMCPY +#else + void* mpack_memcpy(void* MPACK_RESTRICT s1, const void* MPACK_RESTRICT s2, size_t n); +#endif + +#ifdef MPACK_MEMMOVE + #define mpack_memmove MPACK_MEMMOVE +#else + void* mpack_memmove(void* s1, const void* s2, size_t n); +#endif + +#ifdef MPACK_MEMSET + #define mpack_memset MPACK_MEMSET +#else + void* mpack_memset(void* s, int c, size_t n); +#endif + +#ifdef MPACK_STRLEN + #define mpack_strlen MPACK_STRLEN +#else + size_t mpack_strlen(const char* s); +#endif + + + +#if MPACK_STDIO + #if defined(WIN32) + #define mpack_snprintf _snprintf + #else + #define mpack_snprintf snprintf + #endif +#endif + + + +/* Debug logging */ +#if 0 + #include + #define mpack_log(...) (MPACK_EXPAND(printf(__VA_ARGS__)), fflush(stdout)) +#else + #define mpack_log(...) ((void)0) +#endif + + + +/* Make sure our configuration makes sense */ +#ifndef MPACK_MALLOC + #if MPACK_STDIO + #error "MPACK_STDIO requires preprocessor definitions for MPACK_MALLOC and MPACK_FREE." + #endif + #if MPACK_READ_TRACKING + #error "MPACK_READ_TRACKING requires preprocessor definitions for MPACK_MALLOC and MPACK_FREE." + #endif + #if MPACK_WRITE_TRACKING + #error "MPACK_WRITE_TRACKING requires preprocessor definitions for MPACK_MALLOC and MPACK_FREE." + #endif +#endif + + + +/* Implement realloc if unavailable */ +#ifdef MPACK_MALLOC + #ifdef MPACK_REALLOC + MPACK_INLINE void* mpack_realloc(void* old_ptr, size_t used_size, size_t new_size) { + MPACK_UNUSED(used_size); + return MPACK_REALLOC(old_ptr, new_size); + } + #else + void* mpack_realloc(void* old_ptr, size_t used_size, size_t new_size); + #endif +#endif + + + +/** @endcond */ +/** + * @} + */ + +MPACK_EXTERN_C_END +MPACK_SILENCE_WARNINGS_END + +#endif + +/* mpack/mpack-common.h.h */ + +/** + * @file + * + * Defines types and functions shared by the MPack reader and writer. + */ + +#ifndef MPACK_COMMON_H +#define MPACK_COMMON_H 1 + +/* #include "mpack-platform.h" */ + +#ifndef MPACK_PRINT_BYTE_COUNT +#define MPACK_PRINT_BYTE_COUNT 12 +#endif + +MPACK_SILENCE_WARNINGS_BEGIN +MPACK_EXTERN_C_BEGIN + + + +/** + * @defgroup common Tags and Common Elements + * + * Contains types, constants and functions shared by both the encoding + * and decoding portions of MPack. + * + * @{ + */ + +/* Version information */ + +#define MPACK_VERSION_MAJOR 1 /**< The major version number of MPack. */ +#define MPACK_VERSION_MINOR 1 /**< The minor version number of MPack. */ +#define MPACK_VERSION_PATCH 1 /**< The patch version number of MPack. */ + +/** A number containing the version number of MPack for comparison purposes. */ +#define MPACK_VERSION ((MPACK_VERSION_MAJOR * 10000) + \ + (MPACK_VERSION_MINOR * 100) + MPACK_VERSION_PATCH) + +/** A macro to test for a minimum version of MPack. */ +#define MPACK_VERSION_AT_LEAST(major, minor, patch) \ + (MPACK_VERSION >= (((major) * 10000) + ((minor) * 100) + (patch))) + +/** @cond */ +#if (MPACK_VERSION_PATCH > 0) +#define MPACK_VERSION_STRING_BASE \ + MPACK_STRINGIFY(MPACK_VERSION_MAJOR) "." \ + MPACK_STRINGIFY(MPACK_VERSION_MINOR) "." \ + MPACK_STRINGIFY(MPACK_VERSION_PATCH) +#else +#define MPACK_VERSION_STRING_BASE \ + MPACK_STRINGIFY(MPACK_VERSION_MAJOR) "." \ + MPACK_STRINGIFY(MPACK_VERSION_MINOR) +#endif +/** @endcond */ + +/** + * @def MPACK_VERSION_STRING + * @hideinitializer + * + * A string containing the MPack version. + */ +#if MPACK_RELEASE_VERSION +#define MPACK_VERSION_STRING MPACK_VERSION_STRING_BASE +#else +#define MPACK_VERSION_STRING MPACK_VERSION_STRING_BASE "dev" +#endif + +/** + * @def MPACK_LIBRARY_STRING + * @hideinitializer + * + * A string describing MPack, containing the library name, version and debug mode. + */ +#if MPACK_DEBUG +#define MPACK_LIBRARY_STRING "MPack " MPACK_VERSION_STRING "-debug" +#else +#define MPACK_LIBRARY_STRING "MPack " MPACK_VERSION_STRING +#endif + +/** @cond */ +/** + * @def MPACK_MAXIMUM_TAG_SIZE + * + * The maximum encoded size of a tag in bytes. + */ +#define MPACK_MAXIMUM_TAG_SIZE 9 +/** @endcond */ + +#if MPACK_EXTENSIONS +/** + * @def MPACK_TIMESTAMP_NANOSECONDS_MAX + * + * The maximum value of nanoseconds for a timestamp. + * + * @note This requires @ref MPACK_EXTENSIONS. + */ +#define MPACK_TIMESTAMP_NANOSECONDS_MAX 999999999 +#endif + + + +#if MPACK_COMPATIBILITY +/** + * Versions of the MessagePack format. + * + * A reader, writer, or tree can be configured to serialize in an older + * version of the MessagePack spec. This is necessary to interface with + * older MessagePack libraries that do not support new MessagePack features. + * + * @note This requires @ref MPACK_COMPATIBILITY. + */ +typedef enum mpack_version_t { + + /** + * Version 1.0/v4, supporting only the @c raw type without @c str8. + */ + mpack_version_v4 = 4, + + /** + * Version 2.0/v5, supporting the @c str8, @c bin and @c ext types. + */ + mpack_version_v5 = 5, + + /** + * The most recent supported version of MessagePack. This is the default. + */ + mpack_version_current = mpack_version_v5, + +} mpack_version_t; +#endif + +/** + * Error states for MPack objects. + * + * When a reader, writer, or tree is in an error state, all subsequent calls + * are ignored and their return values are nil/zero. You should check whether + * the source is in an error state before using such values. + */ +typedef enum mpack_error_t { + mpack_ok = 0, /**< No error. */ + mpack_error_io = 2, /**< The reader or writer failed to fill or flush, or some other file or socket error occurred. */ + mpack_error_invalid, /**< The data read is not valid MessagePack. */ + mpack_error_unsupported, /**< The data read is not supported by this configuration of MPack. (See @ref MPACK_EXTENSIONS.) */ + mpack_error_type, /**< The type or value range did not match what was expected by the caller. */ + mpack_error_too_big, /**< A read or write was bigger than the maximum size allowed for that operation. */ + mpack_error_memory, /**< An allocation failure occurred. */ + mpack_error_bug, /**< The MPack API was used incorrectly. (This will always assert in debug mode.) */ + mpack_error_data, /**< The contained data is not valid. */ + mpack_error_eof, /**< The reader failed to read because of file or socket EOF */ +} mpack_error_t; + +/** + * Converts an MPack error to a string. This function returns an empty + * string when MPACK_DEBUG is not set. + */ +const char* mpack_error_to_string(mpack_error_t error); + +/** + * Defines the type of a MessagePack tag. + * + * Note that extension types, both user defined and built-in, are represented + * in tags as @ref mpack_type_ext. The value for an extension type is stored + * separately. + */ +typedef enum mpack_type_t { + mpack_type_missing = 0, /**< Special type indicating a missing optional value. */ + mpack_type_nil, /**< A null value. */ + mpack_type_bool, /**< A boolean (true or false.) */ + mpack_type_int, /**< A 64-bit signed integer. */ + mpack_type_uint, /**< A 64-bit unsigned integer. */ + mpack_type_float, /**< A 32-bit IEEE 754 floating point number. */ + mpack_type_double, /**< A 64-bit IEEE 754 floating point number. */ + mpack_type_str, /**< A string. */ + mpack_type_bin, /**< A chunk of binary data. */ + mpack_type_array, /**< An array of MessagePack objects. */ + mpack_type_map, /**< An ordered map of key/value pairs of MessagePack objects. */ + + #if MPACK_EXTENSIONS + /** + * A typed MessagePack extension object containing a chunk of binary data. + * + * @note This requires @ref MPACK_EXTENSIONS. + */ + mpack_type_ext, + #endif +} mpack_type_t; + +/** + * Converts an MPack type to a string. This function returns an empty + * string when MPACK_DEBUG is not set. + */ +const char* mpack_type_to_string(mpack_type_t type); + +#if MPACK_EXTENSIONS +/** + * A timestamp. + * + * @note This requires @ref MPACK_EXTENSIONS. + */ +typedef struct mpack_timestamp_t { + int64_t seconds; /*< The number of seconds (signed) since 1970-01-01T00:00:00Z. */ + uint32_t nanoseconds; /*< The number of additional nanoseconds, between 0 and 999,999,999. */ +} mpack_timestamp_t; +#endif + +/** + * An MPack tag is a MessagePack object header. It is a variant type + * representing any kind of object, and includes the length of compound types + * (e.g. map, array, string) or the value of non-compound types (e.g. boolean, + * integer, float.) + * + * If the type is compound (str, bin, ext, array or map), the contained + * elements or bytes are stored separately. + * + * This structure is opaque; its fields should not be accessed outside + * of MPack. + */ +typedef struct mpack_tag_t mpack_tag_t; + +/* Hide internals from documentation */ +/** @cond */ +struct mpack_tag_t { + mpack_type_t type; /*< The type of value. */ + + #if MPACK_EXTENSIONS + int8_t exttype; /*< The extension type if the type is @ref mpack_type_ext. */ + #endif + + /* The value for non-compound types. */ + union { + uint64_t u; /*< The value if the type is unsigned int. */ + int64_t i; /*< The value if the type is signed int. */ + bool b; /*< The value if the type is bool. */ + + #if MPACK_FLOAT + float f; /*< The value if the type is float. */ + #else + uint32_t f; /*< The raw value if the type is float. */ + #endif + + #if MPACK_DOUBLE + double d; /*< The value if the type is double. */ + #else + uint64_t d; /*< The raw value if the type is double. */ + #endif + + /* The number of bytes if the type is str, bin or ext. */ + uint32_t l; + + /* The element count if the type is an array, or the number of + key/value pairs if the type is map. */ + uint32_t n; + } v; +}; +/** @endcond */ + +/** + * @name Tag Generators + * @{ + */ + +/** + * @def MPACK_TAG_ZERO + * + * An @ref mpack_tag_t initializer that zeroes the given tag. + * + * @warning This does not make the tag nil! The tag's type is invalid when + * initialized this way. Use @ref mpack_tag_make_nil() to generate a nil tag. + */ +#if MPACK_EXTENSIONS +#define MPACK_TAG_ZERO {(mpack_type_t)0, 0, {0}} +#else +#define MPACK_TAG_ZERO {(mpack_type_t)0, {0}} +#endif + +/** Generates a nil tag. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_nil(void) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_nil; + return ret; +} + +/** Generates a bool tag. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_bool(bool value) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_bool; + ret.v.b = value; + return ret; +} + +/** Generates a bool tag with value true. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_true(void) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_bool; + ret.v.b = true; + return ret; +} + +/** Generates a bool tag with value false. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_false(void) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_bool; + ret.v.b = false; + return ret; +} + +/** Generates a signed int tag. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_int(int64_t value) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_int; + ret.v.i = value; + return ret; +} + +/** Generates an unsigned int tag. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_uint(uint64_t value) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_uint; + ret.v.u = value; + return ret; +} + +#if MPACK_FLOAT +/** Generates a float tag. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_float(float value) +#else +/** Generates a float tag from a raw uint32_t. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_raw_float(uint32_t value) +#endif +{ + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_float; + ret.v.f = value; + return ret; +} + +#if MPACK_DOUBLE +/** Generates a double tag. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_double(double value) +#else +/** Generates a double tag from a raw uint64_t. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_raw_double(uint64_t value) +#endif +{ + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_double; + ret.v.d = value; + return ret; +} + +/** Generates an array tag. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_array(uint32_t count) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_array; + ret.v.n = count; + return ret; +} + +/** Generates a map tag. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_map(uint32_t count) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_map; + ret.v.n = count; + return ret; +} + +/** Generates a str tag. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_str(uint32_t length) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_str; + ret.v.l = length; + return ret; +} + +/** Generates a bin tag. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_bin(uint32_t length) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_bin; + ret.v.l = length; + return ret; +} + +#if MPACK_EXTENSIONS +/** + * Generates an ext tag. + * + * @note This requires @ref MPACK_EXTENSIONS. + */ +MPACK_INLINE mpack_tag_t mpack_tag_make_ext(int8_t exttype, uint32_t length) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_ext; + ret.exttype = exttype; + ret.v.l = length; + return ret; +} +#endif + +/** + * @} + */ + +/** + * @name Tag Querying Functions + * @{ + */ + +/** + * Gets the type of a tag. + */ +MPACK_INLINE mpack_type_t mpack_tag_type(mpack_tag_t* tag) { + return tag->type; +} + +/** + * Gets the boolean value of a bool-type tag. The tag must be of type @ref + * mpack_type_bool. + * + * This asserts that the type in the tag is @ref mpack_type_bool. (No check is + * performed if MPACK_DEBUG is not set.) + */ +MPACK_INLINE bool mpack_tag_bool_value(mpack_tag_t* tag) { + mpack_assert(tag->type == mpack_type_bool, "tag is not a bool!"); + return tag->v.b; +} + +/** + * Gets the signed integer value of an int-type tag. + * + * This asserts that the type in the tag is @ref mpack_type_int. (No check is + * performed if MPACK_DEBUG is not set.) + * + * @warning This does not convert between signed and unsigned tags! A positive + * integer may be stored in a tag as either @ref mpack_type_int or @ref + * mpack_type_uint. You must check the type first; this can only be used if the + * type is @ref mpack_type_int. + * + * @see mpack_type_int + */ +MPACK_INLINE int64_t mpack_tag_int_value(mpack_tag_t* tag) { + mpack_assert(tag->type == mpack_type_int, "tag is not an int!"); + return tag->v.i; +} + +/** + * Gets the unsigned integer value of a uint-type tag. + * + * This asserts that the type in the tag is @ref mpack_type_uint. (No check is + * performed if MPACK_DEBUG is not set.) + * + * @warning This does not convert between signed and unsigned tags! A positive + * integer may be stored in a tag as either @ref mpack_type_int or @ref + * mpack_type_uint. You must check the type first; this can only be used if the + * type is @ref mpack_type_uint. + * + * @see mpack_type_uint + */ +MPACK_INLINE uint64_t mpack_tag_uint_value(mpack_tag_t* tag) { + mpack_assert(tag->type == mpack_type_uint, "tag is not a uint!"); + return tag->v.u; +} + +/** + * Gets the float value of a float-type tag. + * + * This asserts that the type in the tag is @ref mpack_type_float. (No check is + * performed if MPACK_DEBUG is not set.) + * + * @warning This does not convert between float and double tags! This can only + * be used if the type is @ref mpack_type_float. + * + * @see mpack_type_float + */ +MPACK_INLINE +#if MPACK_FLOAT +float mpack_tag_float_value(mpack_tag_t* tag) +#else +uint32_t mpack_tag_raw_float_value(mpack_tag_t* tag) +#endif +{ + mpack_assert(tag->type == mpack_type_float, "tag is not a float!"); + return tag->v.f; +} + +/** + * Gets the double value of a double-type tag. + * + * This asserts that the type in the tag is @ref mpack_type_double. (No check + * is performed if MPACK_DEBUG is not set.) + * + * @warning This does not convert between float and double tags! This can only + * be used if the type is @ref mpack_type_double. + * + * @see mpack_type_double + */ +MPACK_INLINE +#if MPACK_DOUBLE +double mpack_tag_double_value(mpack_tag_t* tag) +#else +uint64_t mpack_tag_raw_double_value(mpack_tag_t* tag) +#endif +{ + mpack_assert(tag->type == mpack_type_double, "tag is not a double!"); + return tag->v.d; +} + +/** + * Gets the number of elements in an array tag. + * + * This asserts that the type in the tag is @ref mpack_type_array. (No check is + * performed if MPACK_DEBUG is not set.) + * + * @see mpack_type_array + */ +MPACK_INLINE uint32_t mpack_tag_array_count(mpack_tag_t* tag) { + mpack_assert(tag->type == mpack_type_array, "tag is not an array!"); + return tag->v.n; +} + +/** + * Gets the number of key-value pairs in a map tag. + * + * This asserts that the type in the tag is @ref mpack_type_map. (No check is + * performed if MPACK_DEBUG is not set.) + * + * @see mpack_type_map + */ +MPACK_INLINE uint32_t mpack_tag_map_count(mpack_tag_t* tag) { + mpack_assert(tag->type == mpack_type_map, "tag is not a map!"); + return tag->v.n; +} + +/** + * Gets the length in bytes of a str-type tag. + * + * This asserts that the type in the tag is @ref mpack_type_str. (No check is + * performed if MPACK_DEBUG is not set.) + * + * @see mpack_type_str + */ +MPACK_INLINE uint32_t mpack_tag_str_length(mpack_tag_t* tag) { + mpack_assert(tag->type == mpack_type_str, "tag is not a str!"); + return tag->v.l; +} + +/** + * Gets the length in bytes of a bin-type tag. + * + * This asserts that the type in the tag is @ref mpack_type_bin. (No check is + * performed if MPACK_DEBUG is not set.) + * + * @see mpack_type_bin + */ +MPACK_INLINE uint32_t mpack_tag_bin_length(mpack_tag_t* tag) { + mpack_assert(tag->type == mpack_type_bin, "tag is not a bin!"); + return tag->v.l; +} + +#if MPACK_EXTENSIONS +/** + * Gets the length in bytes of an ext-type tag. + * + * This asserts that the type in the tag is @ref mpack_type_ext. (No check is + * performed if MPACK_DEBUG is not set.) + * + * @note This requires @ref MPACK_EXTENSIONS. + * + * @see mpack_type_ext + */ +MPACK_INLINE uint32_t mpack_tag_ext_length(mpack_tag_t* tag) { + mpack_assert(tag->type == mpack_type_ext, "tag is not an ext!"); + return tag->v.l; +} + +/** + * Gets the extension type (exttype) of an ext-type tag. + * + * This asserts that the type in the tag is @ref mpack_type_ext. (No check is + * performed if MPACK_DEBUG is not set.) + * + * @note This requires @ref MPACK_EXTENSIONS. + * + * @see mpack_type_ext + */ +MPACK_INLINE int8_t mpack_tag_ext_exttype(mpack_tag_t* tag) { + mpack_assert(tag->type == mpack_type_ext, "tag is not an ext!"); + return tag->exttype; +} +#endif + +/** + * Gets the length in bytes of a str-, bin- or ext-type tag. + * + * This asserts that the type in the tag is @ref mpack_type_str, @ref + * mpack_type_bin or @ref mpack_type_ext. (No check is performed if MPACK_DEBUG + * is not set.) + * + * @see mpack_type_str + * @see mpack_type_bin + * @see mpack_type_ext + */ +MPACK_INLINE uint32_t mpack_tag_bytes(mpack_tag_t* tag) { + #if MPACK_EXTENSIONS + mpack_assert(tag->type == mpack_type_str || tag->type == mpack_type_bin + || tag->type == mpack_type_ext, "tag is not a str, bin or ext!"); + #else + mpack_assert(tag->type == mpack_type_str || tag->type == mpack_type_bin, + "tag is not a str or bin!"); + #endif + return tag->v.l; +} + +/** + * @} + */ + +/** + * @name Other tag functions + * @{ + */ + +#if MPACK_EXTENSIONS +/** + * The extension type for a timestamp. + * + * @note This requires @ref MPACK_EXTENSIONS. + */ +#define MPACK_EXTTYPE_TIMESTAMP ((int8_t)(-1)) +#endif + +/** + * Compares two tags with an arbitrary fixed ordering. Returns 0 if the tags are + * equal, a negative integer if left comes before right, or a positive integer + * otherwise. + * + * \warning The ordering is not guaranteed to be preserved across MPack versions; do + * not rely on it in persistent data. + * + * \warning Floating point numbers are compared bit-for-bit, not using the language's + * operator==. This means that NaNs with matching representation will compare equal. + * This behaviour is up for debate; see comments in the definition of mpack_tag_cmp(). + * + * See mpack_tag_equal() for more information on when tags are considered equal. + */ +int mpack_tag_cmp(mpack_tag_t left, mpack_tag_t right); + +/** + * Compares two tags for equality. Tags are considered equal if the types are compatible + * and the values (for non-compound types) are equal. + * + * The field width of variable-width fields is ignored (and in fact is not stored + * in a tag), and positive numbers in signed integers are considered equal to their + * unsigned counterparts. So for example the value 1 stored as a positive fixint + * is equal to the value 1 stored in a 64-bit unsigned integer field. + * + * The "extension type" of an extension object is considered part of the value + * and must match exactly. + * + * \warning Floating point numbers are compared bit-for-bit, not using the language's + * operator==. This means that NaNs with matching representation will compare equal. + * This behaviour is up for debate; see comments in the definition of mpack_tag_cmp(). + */ +MPACK_INLINE bool mpack_tag_equal(mpack_tag_t left, mpack_tag_t right) { + return mpack_tag_cmp(left, right) == 0; +} + +#if MPACK_DEBUG && MPACK_STDIO +/** + * Generates a json-like debug description of the given tag into the given buffer. + * + * This is only available in debug mode, and only if stdio is available (since + * it uses snprintf().) It's strictly for debugging purposes. + * + * The prefix is used to print the first few hexadecimal bytes of a bin or ext + * type. Pass NULL if not a bin or ext. + */ +void mpack_tag_debug_pseudo_json(mpack_tag_t tag, char* buffer, size_t buffer_size, + const char* prefix, size_t prefix_size); + +/** + * Generates a debug string description of the given tag into the given buffer. + * + * This is only available in debug mode, and only if stdio is available (since + * it uses snprintf().) It's strictly for debugging purposes. + */ +void mpack_tag_debug_describe(mpack_tag_t tag, char* buffer, size_t buffer_size); + +/** @cond */ + +/* + * A callback function for printing pseudo-JSON for debugging purposes. + * + * @see mpack_node_print_callback + */ +typedef void (*mpack_print_callback_t)(void* context, const char* data, size_t count); + +// helpers for printing debug output +// i feel a bit like i'm re-implementing a buffered writer again... +typedef struct mpack_print_t { + char* buffer; + size_t size; + size_t count; + mpack_print_callback_t callback; + void* context; +} mpack_print_t; + +void mpack_print_append(mpack_print_t* print, const char* data, size_t count); + +MPACK_INLINE void mpack_print_append_cstr(mpack_print_t* print, const char* cstr) { + mpack_print_append(print, cstr, mpack_strlen(cstr)); +} + +void mpack_print_flush(mpack_print_t* print); + +void mpack_print_file_callback(void* context, const char* data, size_t count); + +/** @endcond */ + +#endif + +/** + * @} + */ + +/** + * @name Deprecated Tag Generators + * @{ + */ + +/* + * "make" has been added to their names to disambiguate them from the + * value-fetching functions (e.g. mpack_tag_make_bool() vs + * mpack_tag_bool_value().) + * + * The length and count for all compound types was the wrong sign (int32_t + * instead of uint32_t.) These preserve the old behaviour; the new "make" + * functions have the correct sign. + */ + +/** \deprecated Renamed to mpack_tag_make_nil(). */ +MPACK_INLINE mpack_tag_t mpack_tag_nil(void) { + return mpack_tag_make_nil(); +} + +/** \deprecated Renamed to mpack_tag_make_bool(). */ +MPACK_INLINE mpack_tag_t mpack_tag_bool(bool value) { + return mpack_tag_make_bool(value); +} + +/** \deprecated Renamed to mpack_tag_make_true(). */ +MPACK_INLINE mpack_tag_t mpack_tag_true(void) { + return mpack_tag_make_true(); +} + +/** \deprecated Renamed to mpack_tag_make_false(). */ +MPACK_INLINE mpack_tag_t mpack_tag_false(void) { + return mpack_tag_make_false(); +} + +/** \deprecated Renamed to mpack_tag_make_int(). */ +MPACK_INLINE mpack_tag_t mpack_tag_int(int64_t value) { + return mpack_tag_make_int(value); +} + +/** \deprecated Renamed to mpack_tag_make_uint(). */ +MPACK_INLINE mpack_tag_t mpack_tag_uint(uint64_t value) { + return mpack_tag_make_uint(value); +} + +#if MPACK_FLOAT +/** \deprecated Renamed to mpack_tag_make_float(). */ +MPACK_INLINE mpack_tag_t mpack_tag_float(float value) { + return mpack_tag_make_float(value); +} +#endif + +#if MPACK_DOUBLE +/** \deprecated Renamed to mpack_tag_make_double(). */ +MPACK_INLINE mpack_tag_t mpack_tag_double(double value) { + return mpack_tag_make_double(value); +} +#endif + +/** \deprecated Renamed to mpack_tag_make_array(). */ +MPACK_INLINE mpack_tag_t mpack_tag_array(int32_t count) { + return mpack_tag_make_array((uint32_t)count); +} + +/** \deprecated Renamed to mpack_tag_make_map(). */ +MPACK_INLINE mpack_tag_t mpack_tag_map(int32_t count) { + return mpack_tag_make_map((uint32_t)count); +} + +/** \deprecated Renamed to mpack_tag_make_str(). */ +MPACK_INLINE mpack_tag_t mpack_tag_str(int32_t length) { + return mpack_tag_make_str((uint32_t)length); +} + +/** \deprecated Renamed to mpack_tag_make_bin(). */ +MPACK_INLINE mpack_tag_t mpack_tag_bin(int32_t length) { + return mpack_tag_make_bin((uint32_t)length); +} + +#if MPACK_EXTENSIONS +/** \deprecated Renamed to mpack_tag_make_ext(). */ +MPACK_INLINE mpack_tag_t mpack_tag_ext(int8_t exttype, int32_t length) { + return mpack_tag_make_ext(exttype, (uint32_t)length); +} +#endif + +/** + * @} + */ + +/** @cond */ + +/* + * Helpers to perform unaligned network-endian loads and stores + * at arbitrary addresses. Byte-swapping builtins are used if they + * are available and if they improve performance. + * + * These will remain available in the public API so feel free to + * use them for other purposes, but they are undocumented. + */ + +MPACK_INLINE uint8_t mpack_load_u8(const char* p) { + return (uint8_t)p[0]; +} + +MPACK_INLINE uint16_t mpack_load_u16(const char* p) { + #ifdef MPACK_NHSWAP16 + uint16_t val; + mpack_memcpy(&val, p, sizeof(val)); + return MPACK_NHSWAP16(val); + #else + return (uint16_t)((((uint16_t)(uint8_t)p[0]) << 8) | + ((uint16_t)(uint8_t)p[1])); + #endif +} + +MPACK_INLINE uint32_t mpack_load_u32(const char* p) { + #ifdef MPACK_NHSWAP32 + uint32_t val; + mpack_memcpy(&val, p, sizeof(val)); + return MPACK_NHSWAP32(val); + #else + return (((uint32_t)(uint8_t)p[0]) << 24) | + (((uint32_t)(uint8_t)p[1]) << 16) | + (((uint32_t)(uint8_t)p[2]) << 8) | + ((uint32_t)(uint8_t)p[3]); + #endif +} + +MPACK_INLINE uint64_t mpack_load_u64(const char* p) { + #ifdef MPACK_NHSWAP64 + uint64_t val; + mpack_memcpy(&val, p, sizeof(val)); + return MPACK_NHSWAP64(val); + #else + return (((uint64_t)(uint8_t)p[0]) << 56) | + (((uint64_t)(uint8_t)p[1]) << 48) | + (((uint64_t)(uint8_t)p[2]) << 40) | + (((uint64_t)(uint8_t)p[3]) << 32) | + (((uint64_t)(uint8_t)p[4]) << 24) | + (((uint64_t)(uint8_t)p[5]) << 16) | + (((uint64_t)(uint8_t)p[6]) << 8) | + ((uint64_t)(uint8_t)p[7]); + #endif +} + +MPACK_INLINE void mpack_store_u8(char* p, uint8_t val) { + uint8_t* u = (uint8_t*)p; + u[0] = val; +} + +MPACK_INLINE void mpack_store_u16(char* p, uint16_t val) { + #ifdef MPACK_NHSWAP16 + val = MPACK_NHSWAP16(val); + mpack_memcpy(p, &val, sizeof(val)); + #else + uint8_t* u = (uint8_t*)p; + u[0] = (uint8_t)((val >> 8) & 0xFF); + u[1] = (uint8_t)( val & 0xFF); + #endif +} + +MPACK_INLINE void mpack_store_u32(char* p, uint32_t val) { + #ifdef MPACK_NHSWAP32 + val = MPACK_NHSWAP32(val); + mpack_memcpy(p, &val, sizeof(val)); + #else + uint8_t* u = (uint8_t*)p; + u[0] = (uint8_t)((val >> 24) & 0xFF); + u[1] = (uint8_t)((val >> 16) & 0xFF); + u[2] = (uint8_t)((val >> 8) & 0xFF); + u[3] = (uint8_t)( val & 0xFF); + #endif +} + +MPACK_INLINE void mpack_store_u64(char* p, uint64_t val) { + #ifdef MPACK_NHSWAP64 + val = MPACK_NHSWAP64(val); + mpack_memcpy(p, &val, sizeof(val)); + #else + uint8_t* u = (uint8_t*)p; + u[0] = (uint8_t)((val >> 56) & 0xFF); + u[1] = (uint8_t)((val >> 48) & 0xFF); + u[2] = (uint8_t)((val >> 40) & 0xFF); + u[3] = (uint8_t)((val >> 32) & 0xFF); + u[4] = (uint8_t)((val >> 24) & 0xFF); + u[5] = (uint8_t)((val >> 16) & 0xFF); + u[6] = (uint8_t)((val >> 8) & 0xFF); + u[7] = (uint8_t)( val & 0xFF); + #endif +} + +MPACK_INLINE int8_t mpack_load_i8 (const char* p) {return (int8_t) mpack_load_u8 (p);} +MPACK_INLINE int16_t mpack_load_i16(const char* p) {return (int16_t)mpack_load_u16(p);} +MPACK_INLINE int32_t mpack_load_i32(const char* p) {return (int32_t)mpack_load_u32(p);} +MPACK_INLINE int64_t mpack_load_i64(const char* p) {return (int64_t)mpack_load_u64(p);} +MPACK_INLINE void mpack_store_i8 (char* p, int8_t val) {mpack_store_u8 (p, (uint8_t) val);} +MPACK_INLINE void mpack_store_i16(char* p, int16_t val) {mpack_store_u16(p, (uint16_t)val);} +MPACK_INLINE void mpack_store_i32(char* p, int32_t val) {mpack_store_u32(p, (uint32_t)val);} +MPACK_INLINE void mpack_store_i64(char* p, int64_t val) {mpack_store_u64(p, (uint64_t)val);} + +#if MPACK_FLOAT +MPACK_INLINE float mpack_load_float(const char* p) { + MPACK_CHECK_FLOAT_ORDER(); + MPACK_STATIC_ASSERT(sizeof(float) == sizeof(uint32_t), "float is wrong size??"); + union { + float f; + uint32_t u; + } v; + v.u = mpack_load_u32(p); + return v.f; +} +#endif + +#if MPACK_DOUBLE +MPACK_INLINE double mpack_load_double(const char* p) { + MPACK_CHECK_FLOAT_ORDER(); + MPACK_STATIC_ASSERT(sizeof(double) == sizeof(uint64_t), "double is wrong size??"); + union { + double d; + uint64_t u; + } v; + v.u = mpack_load_u64(p); + return v.d; +} +#endif + +#if MPACK_FLOAT +MPACK_INLINE void mpack_store_float(char* p, float value) { + MPACK_CHECK_FLOAT_ORDER(); + union { + float f; + uint32_t u; + } v; + v.f = value; + mpack_store_u32(p, v.u); +} +#endif + +#if MPACK_DOUBLE +MPACK_INLINE void mpack_store_double(char* p, double value) { + MPACK_CHECK_FLOAT_ORDER(); + union { + double d; + uint64_t u; + } v; + v.d = value; + mpack_store_u64(p, v.u); +} +#endif + +#if MPACK_FLOAT && !MPACK_DOUBLE +/** + * Performs a manual shortening conversion on the raw 64-bit representation of + * a double. This is useful for parsing doubles on platforms that only support + * floats (such as AVR.) + * + * The significand is truncated rather than rounded and subnormal numbers are + * set to 0 so this may not be quite as accurate as a real double-to-float + * conversion. + */ +MPACK_INLINE float mpack_shorten_raw_double_to_float(uint64_t d) { + MPACK_CHECK_FLOAT_ORDER(); + union { + float f; + uint32_t u; + } v; + + // float has 1 bit sign, 8 bits exponent, 23 bits significand + // double has 1 bit sign, 11 bits exponent, 52 bits significand + + uint64_t d_sign = (uint64_t)(d >> 63); + uint64_t d_exponent = (uint32_t)(d >> 52) & ((1 << 11) - 1); + uint64_t d_significand = d & (((uint64_t)1 << 52) - 1); + + uint32_t f_sign = (uint32_t)d_sign; + uint32_t f_exponent; + uint32_t f_significand; + + if (MPACK_UNLIKELY(d_exponent == ((1 << 11) - 1))) { + // infinity or NAN. shift down to preserve the top bit since it + // indicates signaling NAN, but also set the low bit if any bits were + // set (that way we can't shift NAN to infinity.) + f_exponent = ((1 << 8) - 1); + f_significand = (uint32_t)(d_significand >> 29) | (d_significand ? 1 : 0); + + } else { + int fix_bias = (int)d_exponent - ((1 << 10) - 1) + ((1 << 7) - 1); + if (MPACK_UNLIKELY(fix_bias <= 0)) { + // we don't currently handle subnormal numbers. just set it to zero. + f_exponent = 0; + f_significand = 0; + } else if (MPACK_UNLIKELY(fix_bias > 0xff)) { + // exponent is too large; saturate to infinity + f_exponent = 0xff; + f_significand = 0; + } else { + // a normal number that fits in a float. this is the usual case. + f_exponent = (uint32_t)fix_bias; + f_significand = (uint32_t)(d_significand >> 29); + } + } + + #if 0 + printf("\n===============\n"); + for (size_t i = 0; i < 64; ++i) + printf("%i%s",(int)((d>>(63-i))&1),((i%8)==7)?" ":""); + printf("\n%lu %lu %lu\n", d_sign, d_exponent, d_significand); + printf("%u %u %u\n", f_sign, f_exponent, f_significand); + #endif + + v.u = (f_sign << 31) | (f_exponent << 23) | f_significand; + return v.f; +} +#endif + +/** @endcond */ + + + +/** @cond */ + +// Sizes in bytes for the various possible tags +#define MPACK_TAG_SIZE_FIXUINT 1 +#define MPACK_TAG_SIZE_U8 2 +#define MPACK_TAG_SIZE_U16 3 +#define MPACK_TAG_SIZE_U32 5 +#define MPACK_TAG_SIZE_U64 9 +#define MPACK_TAG_SIZE_FIXINT 1 +#define MPACK_TAG_SIZE_I8 2 +#define MPACK_TAG_SIZE_I16 3 +#define MPACK_TAG_SIZE_I32 5 +#define MPACK_TAG_SIZE_I64 9 +#define MPACK_TAG_SIZE_FLOAT 5 +#define MPACK_TAG_SIZE_DOUBLE 9 +#define MPACK_TAG_SIZE_FIXARRAY 1 +#define MPACK_TAG_SIZE_ARRAY16 3 +#define MPACK_TAG_SIZE_ARRAY32 5 +#define MPACK_TAG_SIZE_FIXMAP 1 +#define MPACK_TAG_SIZE_MAP16 3 +#define MPACK_TAG_SIZE_MAP32 5 +#define MPACK_TAG_SIZE_FIXSTR 1 +#define MPACK_TAG_SIZE_STR8 2 +#define MPACK_TAG_SIZE_STR16 3 +#define MPACK_TAG_SIZE_STR32 5 +#define MPACK_TAG_SIZE_BIN8 2 +#define MPACK_TAG_SIZE_BIN16 3 +#define MPACK_TAG_SIZE_BIN32 5 +#define MPACK_TAG_SIZE_FIXEXT1 2 +#define MPACK_TAG_SIZE_FIXEXT2 2 +#define MPACK_TAG_SIZE_FIXEXT4 2 +#define MPACK_TAG_SIZE_FIXEXT8 2 +#define MPACK_TAG_SIZE_FIXEXT16 2 +#define MPACK_TAG_SIZE_EXT8 3 +#define MPACK_TAG_SIZE_EXT16 4 +#define MPACK_TAG_SIZE_EXT32 6 + +// size in bytes for complete ext types +#define MPACK_EXT_SIZE_TIMESTAMP4 (MPACK_TAG_SIZE_FIXEXT4 + 4) +#define MPACK_EXT_SIZE_TIMESTAMP8 (MPACK_TAG_SIZE_FIXEXT8 + 8) +#define MPACK_EXT_SIZE_TIMESTAMP12 (MPACK_TAG_SIZE_EXT8 + 12) + +/** @endcond */ + + + +#if MPACK_READ_TRACKING || MPACK_WRITE_TRACKING +/* Tracks the write state of compound elements (maps, arrays, */ +/* strings, binary blobs and extension types) */ +/** @cond */ + +typedef struct mpack_track_element_t { + mpack_type_t type; + uint32_t left; + + // indicates that a value still needs to be read/written for an already + // read/written key. left is not decremented until both key and value are + // read/written. + bool key_needs_value; + + // tracks whether the map/array being written is using a builder. if true, + // the number of elements is automatic, and left is 0. + bool builder; +} mpack_track_element_t; + +typedef struct mpack_track_t { + size_t count; + size_t capacity; + mpack_track_element_t* elements; +} mpack_track_t; + +#if MPACK_INTERNAL +mpack_error_t mpack_track_init(mpack_track_t* track); +mpack_error_t mpack_track_grow(mpack_track_t* track); +mpack_error_t mpack_track_push(mpack_track_t* track, mpack_type_t type, uint32_t count); +mpack_error_t mpack_track_push_builder(mpack_track_t* track, mpack_type_t type); +mpack_error_t mpack_track_pop(mpack_track_t* track, mpack_type_t type); +mpack_error_t mpack_track_pop_builder(mpack_track_t* track, mpack_type_t type); +mpack_error_t mpack_track_element(mpack_track_t* track, bool read); +mpack_error_t mpack_track_peek_element(mpack_track_t* track, bool read); +mpack_error_t mpack_track_bytes(mpack_track_t* track, bool read, size_t count); +mpack_error_t mpack_track_str_bytes_all(mpack_track_t* track, bool read, size_t count); +mpack_error_t mpack_track_check_empty(mpack_track_t* track); +mpack_error_t mpack_track_destroy(mpack_track_t* track, bool cancel); +#endif + +/** @endcond */ +#endif + + + +#if MPACK_INTERNAL +/** @cond */ + + + +/* Miscellaneous string functions */ + +/** + * Returns true if the given UTF-8 string is valid. + */ +bool mpack_utf8_check(const char* str, size_t bytes); + +/** + * Returns true if the given UTF-8 string is valid and contains no null characters. + */ +bool mpack_utf8_check_no_null(const char* str, size_t bytes); + +/** + * Returns true if the given string has no null bytes. + */ +bool mpack_str_check_no_null(const char* str, size_t bytes); + + + +/** @endcond */ +#endif + + + +/** + * @} + */ + +MPACK_EXTERN_C_END +MPACK_SILENCE_WARNINGS_END + +#endif + + +/* mpack/mpack-writer.h.h */ + +/** + * @file + * + * Declares the MPack Writer. + */ + +#ifndef MPACK_WRITER_H +#define MPACK_WRITER_H 1 + +/* #include "mpack-common.h" */ + +#if MPACK_WRITER + +MPACK_SILENCE_WARNINGS_BEGIN +MPACK_EXTERN_C_BEGIN + +#if MPACK_WRITE_TRACKING +struct mpack_track_t; +#endif + +/** + * @defgroup writer Write API + * + * The MPack Write API encodes structured data of a fixed (hardcoded) schema to MessagePack. + * + * @{ + */ + +/** + * @def MPACK_WRITER_MINIMUM_BUFFER_SIZE + * + * The minimum buffer size for a writer with a flush function. + */ +#define MPACK_WRITER_MINIMUM_BUFFER_SIZE 32 + +/** + * A buffered MessagePack encoder. + * + * The encoder wraps an existing buffer and, optionally, a flush function. + * This allows efficiently encoding to an in-memory buffer or to a stream. + * + * All write operations are synchronous; they will block until the + * data is fully written, or an error occurs. + */ +typedef struct mpack_writer_t mpack_writer_t; + +/** + * The MPack writer's flush function to flush the buffer to the output stream. + * It should flag an appropriate error on the writer if flushing fails (usually + * mpack_error_io or mpack_error_memory.) + * + * The specified context for callbacks is at writer->context. + */ +typedef void (*mpack_writer_flush_t)(mpack_writer_t* writer, const char* buffer, size_t count); + +/** + * An error handler function to be called when an error is flagged on + * the writer. + * + * The error handler will only be called once on the first error flagged; + * any subsequent writes and errors are ignored, and the writer is + * permanently in that error state. + * + * MPack is safe against non-local jumps out of error handler callbacks. + * This means you are allowed to longjmp or throw an exception (in C++, + * Objective-C, or with SEH) out of this callback. + * + * Bear in mind when using longjmp that local non-volatile variables that + * have changed are undefined when setjmp() returns, so you can't put the + * writer on the stack in the same activation frame as the setjmp without + * declaring it volatile. + * + * You must still eventually destroy the writer. It is not destroyed + * automatically when an error is flagged. It is safe to destroy the + * writer within this error callback, but you will either need to perform + * a non-local jump, or store something in your context to identify + * that the writer is destroyed since any future accesses to it cause + * undefined behavior. + */ +typedef void (*mpack_writer_error_t)(mpack_writer_t* writer, mpack_error_t error); + +/** + * A teardown function to be called when the writer is destroyed. + */ +typedef void (*mpack_writer_teardown_t)(mpack_writer_t* writer); + +/* Hide internals from documentation */ +/** @cond */ + +#if MPACK_BUILDER +/** + * Build buffer pages form a linked list. + * + * They don't always fill up. If there is not enough space within them to write + * a tag or place an mpack_build_t, a new page is allocated. For this reason + * they store the number of used bytes. + */ +typedef struct mpack_builder_page_t { + struct mpack_builder_page_t* next; + size_t bytes_used; +} mpack_builder_page_t; + +/** + * Builds form a linked list of mpack_build_t, interleaved with their encoded + * contents directly in the paged builder buffer. + */ +typedef struct mpack_build_t { + //mpack_builder_page_t* page; + struct mpack_build_t* parent; + //struct mpack_build_t* next; + + size_t bytes; // number of bytes between this build and the next one + uint32_t count; // number of elements (or key/value pairs) in this map/array + mpack_type_t type; + + // depth of nested non-build compound elements within this + // build. + uint32_t nested_compound_elements; + + // indicates that a value still needs to be written for an already + // written key. count is not incremented until both key and value are + // written. + bool key_needs_value; +} mpack_build_t; + +/** + * The builder state. This is stored within mpack_writer_t. + */ +typedef struct mpack_builder_t { + mpack_build_t* current_build; // build which is accumulating elements + mpack_build_t* latest_build; // build which is accumulating bytes + mpack_builder_page_t* current_page; + mpack_builder_page_t* pages; + char* stash_buffer; + char* stash_position; + char* stash_end; + #if MPACK_BUILDER_INTERNAL_STORAGE + char internal[MPACK_BUILDER_INTERNAL_STORAGE_SIZE]; + #endif +} mpack_builder_t; +#endif + +struct mpack_writer_t { + #if MPACK_COMPATIBILITY + mpack_version_t version; /* Version of the MessagePack spec to write */ + #endif + mpack_writer_flush_t flush; /* Function to write bytes to the output stream */ + mpack_writer_error_t error_fn; /* Function to call on error */ + mpack_writer_teardown_t teardown; /* Function to teardown the context on destroy */ + void* context; /* Context for writer callbacks */ + + char* buffer; /* Byte buffer */ + char* position; /* Current position within the buffer */ + char* end; /* The end of the buffer */ + mpack_error_t error; /* Error state */ + + #if MPACK_WRITE_TRACKING + mpack_track_t track; /* Stack of map/array/str/bin/ext writes */ + #endif + + #ifdef MPACK_MALLOC + /* Reserved. You can use this space to allocate a custom + * context in order to reduce heap allocations. */ + void* reserved[2]; + #endif + + #if MPACK_BUILDER + mpack_builder_t builder; + #endif +}; + + +#if MPACK_WRITE_TRACKING +void mpack_writer_track_push(mpack_writer_t* writer, mpack_type_t type, uint32_t count); +void mpack_writer_track_push_builder(mpack_writer_t* writer, mpack_type_t type); +void mpack_writer_track_pop(mpack_writer_t* writer, mpack_type_t type); +void mpack_writer_track_pop_builder(mpack_writer_t* writer, mpack_type_t type); +void mpack_writer_track_bytes(mpack_writer_t* writer, size_t count); +#else +MPACK_INLINE void mpack_writer_track_push(mpack_writer_t* writer, mpack_type_t type, uint32_t count) { + MPACK_UNUSED(writer); + MPACK_UNUSED(type); + MPACK_UNUSED(count); +} +MPACK_INLINE void mpack_writer_track_push_builder(mpack_writer_t* writer, mpack_type_t type) { + MPACK_UNUSED(writer); + MPACK_UNUSED(type); +} +MPACK_INLINE void mpack_writer_track_pop(mpack_writer_t* writer, mpack_type_t type) { + MPACK_UNUSED(writer); + MPACK_UNUSED(type); +} +MPACK_INLINE void mpack_writer_track_pop_builder(mpack_writer_t* writer, mpack_type_t type) { + MPACK_UNUSED(writer); + MPACK_UNUSED(type); +} +MPACK_INLINE void mpack_writer_track_bytes(mpack_writer_t* writer, size_t count) { + MPACK_UNUSED(writer); + MPACK_UNUSED(count); +} +#endif + +/** @endcond */ + +/** + * @name Lifecycle Functions + * @{ + */ + +/** + * Initializes an MPack writer with the given buffer. The writer + * does not assume ownership of the buffer. + * + * Trying to write past the end of the buffer will result in mpack_error_too_big + * unless a flush function is set with mpack_writer_set_flush(). To use the data + * without flushing, call mpack_writer_buffer_used() to determine the number of + * bytes written. + * + * @param writer The MPack writer. + * @param buffer The buffer into which to write MessagePack data. + * @param size The size of the buffer. + */ +void mpack_writer_init(mpack_writer_t* writer, char* buffer, size_t size); + +#ifdef MPACK_MALLOC +/** + * Initializes an MPack writer using a growable buffer. + * + * The data is placed in the given data pointer if and when the writer + * is destroyed without error. The data pointer is NULL during writing, + * and will remain NULL if an error occurs. + * + * The allocated data must be freed with MPACK_FREE() (or simply free() + * if MPack's allocator hasn't been customized.) + * + * @throws mpack_error_memory if the buffer fails to grow when + * flushing. + * + * @param writer The MPack writer. + * @param data Where to place the allocated data. + * @param size Where to write the size of the data. + */ +void mpack_writer_init_growable(mpack_writer_t* writer, char** data, size_t* size); +#endif + +/** + * Initializes an MPack writer directly into an error state. Use this if you + * are writing a wrapper to mpack_writer_init() which can fail its setup. + */ +void mpack_writer_init_error(mpack_writer_t* writer, mpack_error_t error); + +#if MPACK_STDIO +/** + * Initializes an MPack writer that writes to a file. + * + * @throws mpack_error_memory if allocation fails + * @throws mpack_error_io if the file cannot be opened + */ +void mpack_writer_init_filename(mpack_writer_t* writer, const char* filename); + +/** + * Deprecated. + * + * \deprecated Renamed to mpack_writer_init_filename(). + */ +MPACK_INLINE void mpack_writer_init_file(mpack_writer_t* writer, const char* filename) { + mpack_writer_init_filename(writer, filename); +} + +/** + * Initializes an MPack writer that writes to a libc FILE. This can be used to + * write to stdout or stderr, or to a file opened separately. + * + * @param writer The MPack writer. + * @param stdfile The FILE. + * @param close_when_done If true, fclose() will be called on the FILE when it + * is no longer needed. If false, the file will not be flushed or + * closed when writing is done. + * + * @note The writer is buffered. If you want to write other data to the FILE in + * between messages, you must flush it first. + * + * @see mpack_writer_flush_message + */ +void mpack_writer_init_stdfile(mpack_writer_t* writer, FILE* stdfile, bool close_when_done); +#endif + +/** @cond */ + +#define mpack_writer_init_stack_line_ex(line, writer) \ + char mpack_buf_##line[MPACK_STACK_SIZE]; \ + mpack_writer_init(writer, mpack_buf_##line, sizeof(mpack_buf_##line)) + +#define mpack_writer_init_stack_line(line, writer) \ + mpack_writer_init_stack_line_ex(line, writer) + +/* + * Initializes an MPack writer using stack space as a buffer. A flush function + * should be added to the writer to flush the buffer. + * + * This is currently undocumented since it's not entirely useful on its own. + */ + +#define mpack_writer_init_stack(writer) \ + mpack_writer_init_stack_line(__LINE__, (writer)) + +/** @endcond */ + +/** + * Cleans up the MPack writer, flushing and closing the underlying stream, + * if any. Returns the final error state of the writer. + * + * No flushing is performed if the writer is in an error state. The attached + * teardown function is called whether or not the writer is in an error state. + * + * This will assert in tracking mode if the writer is not in an error + * state and has any unclosed compound types. If you want to cancel + * writing in the middle of a document, you need to flag an error on + * the writer before destroying it (such as mpack_error_data). + * + * Note that a writer may raise an error and call your error handler during + * the final flush. It is safe to longjmp or throw out of this error handler, + * but if you do, the writer will not be destroyed, and the teardown function + * will not be called. You can still get the writer's error state, and you + * must call @ref mpack_writer_destroy() again. (The second call is guaranteed + * not to call your error handler again since the writer is already in an error + * state.) + * + * @see mpack_writer_set_error_handler + * @see mpack_writer_set_flush + * @see mpack_writer_set_teardown + * @see mpack_writer_flag_error + * @see mpack_error_data + */ +mpack_error_t mpack_writer_destroy(mpack_writer_t* writer); + +/** + * @} + */ + +/** + * @name Configuration + * @{ + */ + +#if MPACK_COMPATIBILITY +/** + * Sets the version of the MessagePack spec that will be generated. + * + * This can be used to interface with older libraries that do not support + * the newest MessagePack features (such as the @c str8 type.) + * + * @note This requires @ref MPACK_COMPATIBILITY. + */ +MPACK_INLINE void mpack_writer_set_version(mpack_writer_t* writer, mpack_version_t version) { + writer->version = version; +} +#endif + +/** + * Sets the custom pointer to pass to the writer callbacks, such as flush + * or teardown. + * + * @param writer The MPack writer. + * @param context User data to pass to the writer callbacks. + * + * @see mpack_writer_context() + */ +MPACK_INLINE void mpack_writer_set_context(mpack_writer_t* writer, void* context) { + writer->context = context; +} + +/** + * Returns the custom context for writer callbacks. + * + * @see mpack_writer_set_context + * @see mpack_writer_set_flush + */ +MPACK_INLINE void* mpack_writer_context(mpack_writer_t* writer) { + return writer->context; +} + +/** + * Sets the flush function to write out the data when the buffer is full. + * + * If no flush function is used, trying to write past the end of the + * buffer will result in mpack_error_too_big. + * + * This should normally be used with mpack_writer_set_context() to register + * a custom pointer to pass to the flush function. + * + * @param writer The MPack writer. + * @param flush The function to write out data from the buffer. + * + * @see mpack_writer_context() + */ +void mpack_writer_set_flush(mpack_writer_t* writer, mpack_writer_flush_t flush); + +/** + * Sets the error function to call when an error is flagged on the writer. + * + * This should normally be used with mpack_writer_set_context() to register + * a custom pointer to pass to the error function. + * + * See the definition of mpack_writer_error_t for more information about + * what you can do from an error callback. + * + * @see mpack_writer_error_t + * @param writer The MPack writer. + * @param error_fn The function to call when an error is flagged on the writer. + */ +MPACK_INLINE void mpack_writer_set_error_handler(mpack_writer_t* writer, mpack_writer_error_t error_fn) { + writer->error_fn = error_fn; +} + +/** + * Sets the teardown function to call when the writer is destroyed. + * + * This should normally be used with mpack_writer_set_context() to register + * a custom pointer to pass to the teardown function. + * + * @param writer The MPack writer. + * @param teardown The function to call when the writer is destroyed. + */ +MPACK_INLINE void mpack_writer_set_teardown(mpack_writer_t* writer, mpack_writer_teardown_t teardown) { + writer->teardown = teardown; +} + +/** + * @} + */ + +/** + * @name Core Writer Functions + * @{ + */ + +/** + * Flushes any buffered data to the underlying stream. + * + * If the writer is connected to a socket and you are keeping it open, + * you will want to call this after writing a message (or set of + * messages) so that the data is actually sent. + * + * It is not necessary to call this if you are not keeping the writer + * open afterwards. You can just call `mpack_writer_destroy()` and it + * will flush before cleaning up. + * + * This will assert if no flush function is assigned to the writer. + * + * If write tracking is enabled, this will break and flag @ref + * mpack_error_bug if the writer has any open compound types, ensuring + * that no compound types are still open. This prevents a "missing + * finish" bug from causing a never-ending message. + */ +void mpack_writer_flush_message(mpack_writer_t* writer); + +/** + * Returns the number of bytes currently stored in the buffer. This + * may be less than the total number of bytes written if bytes have + * been flushed to an underlying stream. + */ +MPACK_INLINE size_t mpack_writer_buffer_used(mpack_writer_t* writer) { + return (size_t)(writer->position - writer->buffer); +} + +/** + * Returns the amount of space left in the buffer. This may be reset + * after a write if bytes are flushed to an underlying stream. + */ +MPACK_INLINE size_t mpack_writer_buffer_left(mpack_writer_t* writer) { + return (size_t)(writer->end - writer->position); +} + +/** + * Returns the (current) size of the buffer. This may change after a write if + * the flush callback changes the buffer. + */ +MPACK_INLINE size_t mpack_writer_buffer_size(mpack_writer_t* writer) { + return (size_t)(writer->end - writer->buffer); +} + +/** + * Places the writer in the given error state, calling the error callback if one + * is set. + * + * This allows you to externally flag errors, for example if you are validating + * data as you write it, or if you want to cancel writing in the middle of a + * document. (The writer will assert if you try to destroy it without error and + * with unclosed compound types. In this case you should flag mpack_error_data + * before destroying it.) + * + * If the writer is already in an error state, this call is ignored and no + * error callback is called. + * + * @see mpack_writer_destroy + * @see mpack_error_data + */ +void mpack_writer_flag_error(mpack_writer_t* writer, mpack_error_t error); + +/** + * Queries the error state of the MPack writer. + * + * If a writer is in an error state, you should discard all data since the + * last time the error flag was checked. The error flag cannot be cleared. + */ +MPACK_INLINE mpack_error_t mpack_writer_error(mpack_writer_t* writer) { + return writer->error; +} + +/** + * Writes a MessagePack object header (an MPack Tag.) + * + * If the value is a map, array, string, binary or extension type, the + * containing elements or bytes must be written separately and the + * appropriate finish function must be called (as though one of the + * mpack_start_*() functions was called.) + * + * @see mpack_write_bytes() + * @see mpack_finish_map() + * @see mpack_finish_array() + * @see mpack_finish_str() + * @see mpack_finish_bin() + * @see mpack_finish_ext() + * @see mpack_finish_type() + */ +void mpack_write_tag(mpack_writer_t* writer, mpack_tag_t tag); + +/** + * @} + */ + +/** + * @name Integers + * @{ + */ + +/** Writes an 8-bit integer in the most efficient packing available. */ +void mpack_write_i8(mpack_writer_t* writer, int8_t value); + +/** Writes a 16-bit integer in the most efficient packing available. */ +void mpack_write_i16(mpack_writer_t* writer, int16_t value); + +/** Writes a 32-bit integer in the most efficient packing available. */ +void mpack_write_i32(mpack_writer_t* writer, int32_t value); + +/** Writes a 64-bit integer in the most efficient packing available. */ +void mpack_write_i64(mpack_writer_t* writer, int64_t value); + +/** Writes an integer in the most efficient packing available. */ +MPACK_INLINE void mpack_write_int(mpack_writer_t* writer, int64_t value) { + mpack_write_i64(writer, value); +} + +/** Writes an 8-bit unsigned integer in the most efficient packing available. */ +void mpack_write_u8(mpack_writer_t* writer, uint8_t value); + +/** Writes an 16-bit unsigned integer in the most efficient packing available. */ +void mpack_write_u16(mpack_writer_t* writer, uint16_t value); + +/** Writes an 32-bit unsigned integer in the most efficient packing available. */ +void mpack_write_u32(mpack_writer_t* writer, uint32_t value); + +/** Writes an 64-bit unsigned integer in the most efficient packing available. */ +void mpack_write_u64(mpack_writer_t* writer, uint64_t value); + +/** Writes an unsigned integer in the most efficient packing available. */ +MPACK_INLINE void mpack_write_uint(mpack_writer_t* writer, uint64_t value) { + mpack_write_u64(writer, value); +} + +/** + * @} + */ + +/** + * @name Other Basic Types + * @{ + */ + +#if MPACK_FLOAT +/** Writes a float. */ +void mpack_write_float(mpack_writer_t* writer, float value); +#else +/** Writes a float from a raw uint32_t. */ +void mpack_write_raw_float(mpack_writer_t* writer, uint32_t raw_value); +#endif + +#if MPACK_DOUBLE +/** Writes a double. */ +void mpack_write_double(mpack_writer_t* writer, double value); +#else +/** Writes a double from a raw uint64_t. */ +void mpack_write_raw_double(mpack_writer_t* writer, uint64_t raw_value); +#endif + +/** Writes a boolean. */ +void mpack_write_bool(mpack_writer_t* writer, bool value); + +/** Writes a boolean with value true. */ +void mpack_write_true(mpack_writer_t* writer); + +/** Writes a boolean with value false. */ +void mpack_write_false(mpack_writer_t* writer); + +/** Writes a nil. */ +void mpack_write_nil(mpack_writer_t* writer); + +/** Write a pre-encoded messagepack object */ +void mpack_write_object_bytes(mpack_writer_t* writer, const char* data, size_t bytes); + +#if MPACK_EXTENSIONS +/** + * Writes a timestamp. + * + * @note This requires @ref MPACK_EXTENSIONS. + * + * @param writer The writer + * @param seconds The (signed) number of seconds since 1970-01-01T00:00:00Z. + * @param nanoseconds The additional number of nanoseconds from 0 to 999,999,999 inclusive. + */ +void mpack_write_timestamp(mpack_writer_t* writer, int64_t seconds, uint32_t nanoseconds); + +/** + * Writes a timestamp with the given number of seconds (and zero nanoseconds). + * + * @note This requires @ref MPACK_EXTENSIONS. + * + * @param writer The writer + * @param seconds The (signed) number of seconds since 1970-01-01T00:00:00Z. + */ +MPACK_INLINE void mpack_write_timestamp_seconds(mpack_writer_t* writer, int64_t seconds) { + mpack_write_timestamp(writer, seconds, 0); +} + +/** + * Writes a timestamp. + * + * @note This requires @ref MPACK_EXTENSIONS. + */ +MPACK_INLINE void mpack_write_timestamp_struct(mpack_writer_t* writer, mpack_timestamp_t timestamp) { + mpack_write_timestamp(writer, timestamp.seconds, timestamp.nanoseconds); +} +#endif + +/** + * @} + */ + +/** + * @name Map and Array Functions + * @{ + */ + +/** + * Opens an array. + * + * `count` elements must follow, and mpack_finish_array() must be called + * when done. + * + * If you do not know the number of elements to be written ahead of time, call + * mpack_build_array() instead. + * + * @see mpack_finish_array() + * @see mpack_build_array() to count the number of elements automatically + */ +void mpack_start_array(mpack_writer_t* writer, uint32_t count); + +/** + * Opens a map. + * + * `count * 2` elements must follow, and mpack_finish_map() must be called + * when done. + * + * If you do not know the number of elements to be written ahead of time, call + * mpack_build_map() instead. + * + * Remember that while map elements in MessagePack are implicitly ordered, + * they are not ordered in JSON. If you need elements to be read back + * in the order they are written, consider use an array instead. + * + * @see mpack_finish_map() + * @see mpack_build_map() to count the number of key/value pairs automatically + */ +void mpack_start_map(mpack_writer_t* writer, uint32_t count); + +MPACK_INLINE void mpack_builder_compound_push(mpack_writer_t* writer) { + MPACK_UNUSED(writer); + + #if MPACK_BUILDER + mpack_build_t* build = writer->builder.current_build; + if (build != NULL) { + ++build->nested_compound_elements; + } + #endif +} + +MPACK_INLINE void mpack_builder_compound_pop(mpack_writer_t* writer) { + MPACK_UNUSED(writer); + + #if MPACK_BUILDER + mpack_build_t* build = writer->builder.current_build; + if (build != NULL) { + mpack_assert(build->nested_compound_elements > 0); + --build->nested_compound_elements; + } + #endif +} + +/** + * Finishes writing an array. + * + * This should be called only after a corresponding call to mpack_start_array() + * and after the array contents are written. + * + * In debug mode (or if MPACK_WRITE_TRACKING is not 0), this will track writes + * to ensure that the correct number of elements are written. + * + * @see mpack_start_array() + */ +MPACK_INLINE void mpack_finish_array(mpack_writer_t* writer) { + mpack_writer_track_pop(writer, mpack_type_array); + mpack_builder_compound_pop(writer); +} + +/** + * Finishes writing a map. + * + * This should be called only after a corresponding call to mpack_start_map() + * and after the map contents are written. + * + * In debug mode (or if MPACK_WRITE_TRACKING is not 0), this will track writes + * to ensure that the correct number of elements are written. + * + * @see mpack_start_map() + */ +MPACK_INLINE void mpack_finish_map(mpack_writer_t* writer) { + mpack_writer_track_pop(writer, mpack_type_map); + mpack_builder_compound_pop(writer); +} + +/** + * Starts building an array. + * + * Elements must follow, and mpack_complete_array() must be called when done. The + * number of elements is determined automatically. + * + * If you know ahead of time the number of elements in the array, it is more + * efficient to call mpack_start_array() instead, even if you are already + * within another open build. + * + * Builder containers can be nested within normal (known size) containers and + * vice versa. You can call mpack_build_array(), then mpack_start_array() + * inside it, then mpack_build_array() inside that, and so forth. + * + * @see mpack_complete_array() to complete this array + * @see mpack_start_array() if you already know the size of the array + * @see mpack_build_map() for implementation details + */ +void mpack_build_array(struct mpack_writer_t* writer); + +/** + * Starts building a map. + * + * An even number of elements must follow, and mpack_complete_map() must be + * called when done. The number of elements is determined automatically. + * + * If you know ahead of time the number of elements in the map, it is more + * efficient to call mpack_start_map() instead, even if you are already within + * another open build. + * + * Builder containers can be nested within normal (known size) containers and + * vice versa. You can call mpack_build_map(), then mpack_start_map() inside + * it, then mpack_build_map() inside that, and so forth. + * + * A writer in build mode diverts writes to a builder buffer that allocates as + * needed. Once the last map or array being built is completed, the deferred + * message is composed with computed array and map sizes into the writer. + * Builder maps and arrays are encoded exactly the same as ordinary maps and + * arrays in the final message. + * + * This indirect encoding is costly, as it incurs at least an extra copy of all + * data written within a builder (but not additional copies for nested + * builders.) Expect a speed penalty of half or more. + * + * A good strategy is to use this during early development when your messages + * are constantly changing, and then closer to release when your message + * formats have stabilized, replace all your build calls with start calls with + * pre-computed sizes. Or don't, if you find the builder has little impact on + * performance, because even with builders MPack is extremely fast. + * + * @note When an array or map starts being built, nothing will be flushed + * until it is completed. If you are building a large message that + * does not fit in the output stream, you won't get an error about it + * until everything is written. + * + * @see mpack_complete_map() to complete this map + * @see mpack_start_map() if you already know the size of the map + */ +void mpack_build_map(struct mpack_writer_t* writer); + +/** + * Completes an array being built. + * + * @see mpack_build_array() + */ +void mpack_complete_array(struct mpack_writer_t* writer); + +/** + * Completes a map being built. + * + * @see mpack_build_map() + */ +void mpack_complete_map(struct mpack_writer_t* writer); + +/** + * @} + */ + +/** + * @name Data Helpers + * @{ + */ + +/** + * Writes a string. + * + * To stream a string in chunks, use mpack_start_str() instead. + * + * MPack does not care about the underlying encoding, but UTF-8 is highly + * recommended, especially for compatibility with JSON. You should consider + * calling mpack_write_utf8() instead, especially if you will be reading + * it back as UTF-8. + * + * You should not call mpack_finish_str() after calling this; this + * performs both start and finish. + */ +void mpack_write_str(mpack_writer_t* writer, const char* str, uint32_t length); + +/** + * Writes a string, ensuring that it is valid UTF-8. + * + * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or + * WTF-8. Only pure UTF-8 is allowed. + * + * You should not call mpack_finish_str() after calling this; this + * performs both start and finish. + * + * @throws mpack_error_invalid if the string is not valid UTF-8 + */ +void mpack_write_utf8(mpack_writer_t* writer, const char* str, uint32_t length); + +/** + * Writes a null-terminated string. (The null-terminator is not written.) + * + * MPack does not care about the underlying encoding, but UTF-8 is highly + * recommended, especially for compatibility with JSON. You should consider + * calling mpack_write_utf8_cstr() instead, especially if you will be reading + * it back as UTF-8. + * + * You should not call mpack_finish_str() after calling this; this + * performs both start and finish. + */ +void mpack_write_cstr(mpack_writer_t* writer, const char* cstr); + +/** + * Writes a null-terminated string, or a nil node if the given cstr pointer + * is NULL. (The null-terminator is not written.) + * + * MPack does not care about the underlying encoding, but UTF-8 is highly + * recommended, especially for compatibility with JSON. You should consider + * calling mpack_write_utf8_cstr_or_nil() instead, especially if you will + * be reading it back as UTF-8. + * + * You should not call mpack_finish_str() after calling this; this + * performs both start and finish. + */ +void mpack_write_cstr_or_nil(mpack_writer_t* writer, const char* cstr); + +/** + * Writes a null-terminated string, ensuring that it is valid UTF-8. (The + * null-terminator is not written.) + * + * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or + * WTF-8. Only pure UTF-8 is allowed. + * + * You should not call mpack_finish_str() after calling this; this + * performs both start and finish. + * + * @throws mpack_error_invalid if the string is not valid UTF-8 + */ +void mpack_write_utf8_cstr(mpack_writer_t* writer, const char* cstr); + +/** + * Writes a null-terminated string ensuring that it is valid UTF-8, or + * writes nil if the given cstr pointer is NULL. (The null-terminator + * is not written.) + * + * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or + * WTF-8. Only pure UTF-8 is allowed. + * + * You should not call mpack_finish_str() after calling this; this + * performs both start and finish. + * + * @throws mpack_error_invalid if the string is not valid UTF-8 + */ +void mpack_write_utf8_cstr_or_nil(mpack_writer_t* writer, const char* cstr); + +/** + * Writes a binary blob. + * + * To stream a binary blob in chunks, use mpack_start_bin() instead. + * + * You should not call mpack_finish_bin() after calling this; this + * performs both start and finish. + */ +void mpack_write_bin(mpack_writer_t* writer, const char* data, uint32_t count); + +#if MPACK_EXTENSIONS +/** + * Writes an extension type. + * + * To stream an extension blob in chunks, use mpack_start_ext() instead. + * + * Extension types [0, 127] are available for application-specific types. Extension + * types [-128, -1] are reserved for future extensions of MessagePack. + * + * You should not call mpack_finish_ext() after calling this; this + * performs both start and finish. + * + * @note This requires @ref MPACK_EXTENSIONS. + */ +void mpack_write_ext(mpack_writer_t* writer, int8_t exttype, const char* data, uint32_t count); +#endif + +/** + * @} + */ + +/** + * @name Chunked Data Functions + * @{ + */ + +/** + * Opens a string. `count` bytes should be written with calls to + * mpack_write_bytes(), and mpack_finish_str() should be called + * when done. + * + * To write an entire string at once, use mpack_write_str() or + * mpack_write_cstr() instead. + * + * MPack does not care about the underlying encoding, but UTF-8 is highly + * recommended, especially for compatibility with JSON. + */ +void mpack_start_str(mpack_writer_t* writer, uint32_t count); + +/** + * Opens a binary blob. `count` bytes should be written with calls to + * mpack_write_bytes(), and mpack_finish_bin() should be called + * when done. + */ +void mpack_start_bin(mpack_writer_t* writer, uint32_t count); + +#if MPACK_EXTENSIONS +/** + * Opens an extension type. `count` bytes should be written with calls + * to mpack_write_bytes(), and mpack_finish_ext() should be called + * when done. + * + * Extension types [0, 127] are available for application-specific types. Extension + * types [-128, -1] are reserved for future extensions of MessagePack. + * + * @note This requires @ref MPACK_EXTENSIONS. + */ +void mpack_start_ext(mpack_writer_t* writer, int8_t exttype, uint32_t count); +#endif + +/** + * Writes a portion of bytes for a string, binary blob or extension type which + * was opened by mpack_write_tag() or one of the mpack_start_*() functions. + * + * This can be called multiple times to write the data in chunks, as long as + * the total amount of bytes written matches the count given when the compound + * type was started. + * + * The corresponding mpack_finish_*() function must be called when done. + * + * To write an entire string, binary blob or extension type at + * once, use one of the mpack_write_*() functions instead. + * + * @see mpack_write_tag() + * @see mpack_start_str() + * @see mpack_start_bin() + * @see mpack_start_ext() + * @see mpack_finish_str() + * @see mpack_finish_bin() + * @see mpack_finish_ext() + * @see mpack_finish_type() + */ +void mpack_write_bytes(mpack_writer_t* writer, const char* data, size_t count); + +/** + * Finishes writing a string. + * + * This should be called only after a corresponding call to mpack_start_str() + * and after the string bytes are written with mpack_write_bytes(). + * + * This will track writes to ensure that the correct number of elements are written. + * + * @see mpack_start_str() + * @see mpack_write_bytes() + */ +MPACK_INLINE void mpack_finish_str(mpack_writer_t* writer) { + mpack_writer_track_pop(writer, mpack_type_str); +} + +/** + * Finishes writing a binary blob. + * + * This should be called only after a corresponding call to mpack_start_bin() + * and after the binary bytes are written with mpack_write_bytes(). + * + * This will track writes to ensure that the correct number of bytes are written. + * + * @see mpack_start_bin() + * @see mpack_write_bytes() + */ +MPACK_INLINE void mpack_finish_bin(mpack_writer_t* writer) { + mpack_writer_track_pop(writer, mpack_type_bin); +} + +#if MPACK_EXTENSIONS +/** + * Finishes writing an extended type binary data blob. + * + * This should be called only after a corresponding call to mpack_start_bin() + * and after the binary bytes are written with mpack_write_bytes(). + * + * This will track writes to ensure that the correct number of bytes are written. + * + * @note This requires @ref MPACK_EXTENSIONS. + * + * @see mpack_start_ext() + * @see mpack_write_bytes() + */ +MPACK_INLINE void mpack_finish_ext(mpack_writer_t* writer) { + mpack_writer_track_pop(writer, mpack_type_ext); +} +#endif + +/** + * Finishes writing the given compound type. + * + * This will track writes to ensure that the correct number of elements + * or bytes are written. + * + * This can be called with the appropriate type instead the corresponding + * mpack_finish_*() function if you want to finish a dynamic type. + */ +MPACK_INLINE void mpack_finish_type(mpack_writer_t* writer, mpack_type_t type) { + mpack_writer_track_pop(writer, type); +} + +/** + * @} + */ + +#if MPACK_HAS_GENERIC && !defined(__cplusplus) + +/** + * @name Type-Generic Writers + * @{ + */ + +/** + * @def mpack_write(writer, value) + * + * Type-generic writer for primitive types. + * + * The compiler will dispatch to an appropriate write function based + * on the type of the @a value parameter. + * + * @note This requires C11 `_Generic` support. (A set of inline overloads + * are used in C++ to provide the same functionality.) + * + * @warning In C11, the indentifiers `true`, `false` and `NULL` are + * all of type `int`, not `bool` or `void*`! They will emit unexpected + * types when passed uncast, so be careful when using them. + */ +#if MPACK_FLOAT + #define MPACK_WRITE_GENERIC_FLOAT float: mpack_write_float, +#else + #define MPACK_WRITE_GENERIC_FLOAT /*nothing*/ +#endif +#if MPACK_DOUBLE + #define MPACK_WRITE_GENERIC_DOUBLE double: mpack_write_double, +#else + #define MPACK_WRITE_GENERIC_DOUBLE /*nothing*/ +#endif +#define mpack_write(writer, value) \ + _Generic(((void)0, value), \ + int8_t: mpack_write_i8, \ + int16_t: mpack_write_i16, \ + int32_t: mpack_write_i32, \ + int64_t: mpack_write_i64, \ + uint8_t: mpack_write_u8, \ + uint16_t: mpack_write_u16, \ + uint32_t: mpack_write_u32, \ + uint64_t: mpack_write_u64, \ + bool: mpack_write_bool, \ + MPACK_WRITE_GENERIC_FLOAT \ + MPACK_WRITE_GENERIC_DOUBLE \ + char *: mpack_write_cstr_or_nil, \ + const char *: mpack_write_cstr_or_nil \ + )(writer, value) + +/** + * @def mpack_write_kv(writer, key, value) + * + * Type-generic writer for key-value pairs of null-terminated string + * keys and primitive values. + * + * @warning @a writer may be evaluated multiple times. + * + * @warning In C11, the indentifiers `true`, `false` and `NULL` are + * all of type `int`, not `bool` or `void*`! They will emit unexpected + * types when passed uncast, so be careful when using them. + * + * @param writer The writer. + * @param key A null-terminated C string. + * @param value A primitive type supported by mpack_write(). + */ +#define mpack_write_kv(writer, key, value) do { \ + mpack_write_cstr(writer, key); \ + mpack_write(writer, value); \ +} while (0) + +/** + * @} + */ + +#endif // MPACK_HAS_GENERIC && !defined(__cplusplus) + +// The rest of this file contains C++ overloads, so we end extern "C" here. +MPACK_EXTERN_C_END + +#if defined(__cplusplus) || defined(MPACK_DOXYGEN) + +/** + * @name C++ write overloads + * @{ + */ + +/* + * C++ generic writers for primitive values + */ + +#ifdef MPACK_DOXYGEN +#undef mpack_write +#undef mpack_write_kv +#endif + +MPACK_INLINE void mpack_write(mpack_writer_t* writer, int8_t value) { + mpack_write_i8(writer, value); +} + +MPACK_INLINE void mpack_write(mpack_writer_t* writer, int16_t value) { + mpack_write_i16(writer, value); +} + +MPACK_INLINE void mpack_write(mpack_writer_t* writer, int32_t value) { + mpack_write_i32(writer, value); +} + +MPACK_INLINE void mpack_write(mpack_writer_t* writer, int64_t value) { + mpack_write_i64(writer, value); +} + +MPACK_INLINE void mpack_write(mpack_writer_t* writer, uint8_t value) { + mpack_write_u8(writer, value); +} + +MPACK_INLINE void mpack_write(mpack_writer_t* writer, uint16_t value) { + mpack_write_u16(writer, value); +} + +MPACK_INLINE void mpack_write(mpack_writer_t* writer, uint32_t value) { + mpack_write_u32(writer, value); +} + +MPACK_INLINE void mpack_write(mpack_writer_t* writer, uint64_t value) { + mpack_write_u64(writer, value); +} + +MPACK_INLINE void mpack_write(mpack_writer_t* writer, bool value) { + mpack_write_bool(writer, value); +} + +MPACK_INLINE void mpack_write(mpack_writer_t* writer, float value) { + mpack_write_float(writer, value); +} + +MPACK_INLINE void mpack_write(mpack_writer_t* writer, double value) { + mpack_write_double(writer, value); +} + +MPACK_INLINE void mpack_write(mpack_writer_t* writer, char *value) { + mpack_write_cstr_or_nil(writer, value); +} + +MPACK_INLINE void mpack_write(mpack_writer_t* writer, const char *value) { + mpack_write_cstr_or_nil(writer, value); +} + +/* C++ generic write for key-value pairs */ + +MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, int8_t value) { + mpack_write_cstr(writer, key); + mpack_write_i8(writer, value); +} + +MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, int16_t value) { + mpack_write_cstr(writer, key); + mpack_write_i16(writer, value); +} + +MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, int32_t value) { + mpack_write_cstr(writer, key); + mpack_write_i32(writer, value); +} + +MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, int64_t value) { + mpack_write_cstr(writer, key); + mpack_write_i64(writer, value); +} + +MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, uint8_t value) { + mpack_write_cstr(writer, key); + mpack_write_u8(writer, value); +} + +MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, uint16_t value) { + mpack_write_cstr(writer, key); + mpack_write_u16(writer, value); +} + +MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, uint32_t value) { + mpack_write_cstr(writer, key); + mpack_write_u32(writer, value); +} + +MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, uint64_t value) { + mpack_write_cstr(writer, key); + mpack_write_u64(writer, value); +} + +MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, bool value) { + mpack_write_cstr(writer, key); + mpack_write_bool(writer, value); +} + +MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, float value) { + mpack_write_cstr(writer, key); + mpack_write_float(writer, value); +} + +MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, double value) { + mpack_write_cstr(writer, key); + mpack_write_double(writer, value); +} + +MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, char *value) { + mpack_write_cstr(writer, key); + mpack_write_cstr_or_nil(writer, value); +} + +MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, const char *value) { + mpack_write_cstr(writer, key); + mpack_write_cstr_or_nil(writer, value); +} + +/** + * @} + */ + +#endif /* __cplusplus */ + +/** + * @} + */ + +MPACK_SILENCE_WARNINGS_END + +#endif // MPACK_WRITER + +#endif + +/* mpack/mpack-reader.h.h */ + +/** + * @file + * + * Declares the core MPack Tag Reader. + */ + +#ifndef MPACK_READER_H +#define MPACK_READER_H 1 + +/* #include "mpack-common.h" */ + +MPACK_SILENCE_WARNINGS_BEGIN +MPACK_EXTERN_C_BEGIN + +#if MPACK_READER + +#if MPACK_READ_TRACKING +struct mpack_track_t; +#endif + +// The denominator to determine whether a read is a small +// fraction of the buffer size. +#define MPACK_READER_SMALL_FRACTION_DENOMINATOR 32 + +/** + * @defgroup reader Reader API + * + * The MPack Reader API contains functions for imperatively reading dynamically + * typed data from a MessagePack stream. + * + * See @ref docs/reader.md for examples. + * + * @note If you are not writing code for an embedded device (or otherwise do + * not need maximum performance with minimal memory usage), you should not use + * this. You probably want to use the @link node Node API@endlink instead. + * + * This forms the basis of the @link expect Expect API@endlink, which can be + * used to interpret the stream of elements in expected types and value ranges. + * + * @{ + */ + +/** + * @def MPACK_READER_MINIMUM_BUFFER_SIZE + * + * The minimum buffer size for a reader with a fill function. + */ +#define MPACK_READER_MINIMUM_BUFFER_SIZE 32 + +/** + * A buffered MessagePack decoder. + * + * The decoder wraps an existing buffer and, optionally, a fill function. + * This allows efficiently decoding data from existing memory buffers, files, + * streams, etc. + * + * All read operations are synchronous; they will block until the + * requested data is fully read, or an error occurs. + * + * This structure is opaque; its fields should not be accessed outside + * of MPack. + */ +typedef struct mpack_reader_t mpack_reader_t; + +/** + * The MPack reader's fill function. It should fill the buffer with at + * least one byte and at most the given @c count, returning the number + * of bytes written to the buffer. + * + * In case of error, it should flag an appropriate error on the reader + * (usually @ref mpack_error_io), or simply return zero. If zero is + * returned, mpack_error_io is raised. + * + * @note When reading from a stream, you should only copy and return + * the bytes that are immediately available. It is always safe to return + * less than the requested count as long as some non-zero number of bytes + * are read; if more bytes are needed, the read function will simply be + * called again. + * + * @see mpack_reader_context() + */ +typedef size_t (*mpack_reader_fill_t)(mpack_reader_t* reader, char* buffer, size_t count); + +/** + * The MPack reader's skip function. It should discard the given number + * of bytes from the source (for example by seeking forward.) + * + * In case of error, it should flag an appropriate error on the reader. + * + * @see mpack_reader_context() + */ +typedef void (*mpack_reader_skip_t)(mpack_reader_t* reader, size_t count); + +/** + * An error handler function to be called when an error is flagged on + * the reader. + * + * The error handler will only be called once on the first error flagged; + * any subsequent reads and errors are ignored, and the reader is + * permanently in that error state. + * + * MPack is safe against non-local jumps out of error handler callbacks. + * This means you are allowed to longjmp or throw an exception (in C++, + * Objective-C, or with SEH) out of this callback. + * + * Bear in mind when using longjmp that local non-volatile variables that + * have changed are undefined when setjmp() returns, so you can't put the + * reader on the stack in the same activation frame as the setjmp without + * declaring it volatile. + * + * You must still eventually destroy the reader. It is not destroyed + * automatically when an error is flagged. It is safe to destroy the + * reader within this error callback, but you will either need to perform + * a non-local jump, or store something in your context to identify + * that the reader is destroyed since any future accesses to it cause + * undefined behavior. + */ +typedef void (*mpack_reader_error_t)(mpack_reader_t* reader, mpack_error_t error); + +/** + * A teardown function to be called when the reader is destroyed. + */ +typedef void (*mpack_reader_teardown_t)(mpack_reader_t* reader); + +/* Hide internals from documentation */ +/** @cond */ + +struct mpack_reader_t { + void* context; /* Context for reader callbacks */ + mpack_reader_fill_t fill; /* Function to read bytes into the buffer */ + mpack_reader_error_t error_fn; /* Function to call on error */ + mpack_reader_teardown_t teardown; /* Function to teardown the context on destroy */ + mpack_reader_skip_t skip; /* Function to skip bytes from the source */ + + char* buffer; /* Writeable byte buffer */ + size_t size; /* Size of the buffer */ + + const char* data; /* Current data pointer (in the buffer, if it is used) */ + const char* end; /* The end of available data (in the buffer, if it is used) */ + + mpack_error_t error; /* Error state */ + + #if MPACK_READ_TRACKING + mpack_track_t track; /* Stack of map/array/str/bin/ext reads */ + #endif +}; + +/** @endcond */ + +/** + * @name Lifecycle Functions + * @{ + */ + +/** + * Initializes an MPack reader with the given buffer. The reader does + * not assume ownership of the buffer, but the buffer must be writeable + * if a fill function will be used to refill it. + * + * @param reader The MPack reader. + * @param buffer The buffer with which to read MessagePack data. + * @param size The size of the buffer. + * @param count The number of bytes already in the buffer. + */ +void mpack_reader_init(mpack_reader_t* reader, char* buffer, size_t size, size_t count); + +/** + * Initializes an MPack reader directly into an error state. Use this if you + * are writing a wrapper to mpack_reader_init() which can fail its setup. + */ +void mpack_reader_init_error(mpack_reader_t* reader, mpack_error_t error); + +/** + * Initializes an MPack reader to parse a pre-loaded contiguous chunk of data. The + * reader does not assume ownership of the data. + * + * @param reader The MPack reader. + * @param data The data to parse. + * @param count The number of bytes pointed to by data. + */ +void mpack_reader_init_data(mpack_reader_t* reader, const char* data, size_t count); + +#if MPACK_STDIO +/** + * Initializes an MPack reader that reads from a file. + * + * The file will be automatically opened and closed by the reader. + */ +void mpack_reader_init_filename(mpack_reader_t* reader, const char* filename); + +/** + * Deprecated. + * + * \deprecated Renamed to mpack_reader_init_filename(). + */ +MPACK_INLINE void mpack_reader_init_file(mpack_reader_t* reader, const char* filename) { + mpack_reader_init_filename(reader, filename); +} + +/** + * Initializes an MPack reader that reads from a libc FILE. This can be used to + * read from stdin, or from a file opened separately. + * + * @param reader The MPack reader. + * @param stdfile The FILE. + * @param close_when_done If true, fclose() will be called on the FILE when it + * is no longer needed. If false, the file will not be closed when + * reading is done. + * + * @warning The reader is buffered. It will read data in advance of parsing it, + * and it may read more data than it parsed. See mpack_reader_remaining() to + * access the extra data. + */ +void mpack_reader_init_stdfile(mpack_reader_t* reader, FILE* stdfile, bool close_when_done); +#endif + +/** + * @def mpack_reader_init_stack(reader) + * @hideinitializer + * + * Initializes an MPack reader using stack space as a buffer. A fill function + * should be added to the reader to fill the buffer. + * + * @see mpack_reader_set_fill + */ + +/** @cond */ +#define mpack_reader_init_stack_line_ex(line, reader) \ + char mpack_buf_##line[MPACK_STACK_SIZE]; \ + mpack_reader_init((reader), mpack_buf_##line, sizeof(mpack_buf_##line), 0) + +#define mpack_reader_init_stack_line(line, reader) \ + mpack_reader_init_stack_line_ex(line, reader) +/** @endcond */ + +#define mpack_reader_init_stack(reader) \ + mpack_reader_init_stack_line(__LINE__, (reader)) + +/** + * Cleans up the MPack reader, ensuring that all compound elements + * have been completely read. Returns the final error state of the + * reader. + * + * This will assert in tracking mode if the reader is not in an error + * state and has any incomplete reads. If you want to cancel reading + * in the middle of a document, you need to flag an error on the reader + * before destroying it (such as mpack_error_data). + * + * @see mpack_read_tag() + * @see mpack_reader_flag_error() + * @see mpack_error_data + */ +mpack_error_t mpack_reader_destroy(mpack_reader_t* reader); + +/** + * @} + */ + +/** + * @name Callbacks + * @{ + */ + +/** + * Sets the custom pointer to pass to the reader callbacks, such as fill + * or teardown. + * + * @param reader The MPack reader. + * @param context User data to pass to the reader callbacks. + * + * @see mpack_reader_context() + */ +MPACK_INLINE void mpack_reader_set_context(mpack_reader_t* reader, void* context) { + reader->context = context; +} + +/** + * Returns the custom context for reader callbacks. + * + * @see mpack_reader_set_context + * @see mpack_reader_set_fill + * @see mpack_reader_set_skip + */ +MPACK_INLINE void* mpack_reader_context(mpack_reader_t* reader) { + return reader->context; +} + +/** + * Sets the fill function to refill the data buffer when it runs out of data. + * + * If no fill function is used, truncated MessagePack data results in + * mpack_error_invalid (since the buffer is assumed to contain a + * complete MessagePack object.) + * + * If a fill function is used, truncated MessagePack data usually + * results in mpack_error_io (since the fill function fails to get + * the missing data.) + * + * This should normally be used with mpack_reader_set_context() to register + * a custom pointer to pass to the fill function. + * + * @param reader The MPack reader. + * @param fill The function to fetch additional data into the buffer. + */ +void mpack_reader_set_fill(mpack_reader_t* reader, mpack_reader_fill_t fill); + +/** + * Sets the skip function to discard bytes from the source stream. + * + * It's not necessary to implement this function. If the stream is not + * seekable, don't set a skip callback. The reader will fall back to + * using the fill function instead. + * + * This should normally be used with mpack_reader_set_context() to register + * a custom pointer to pass to the skip function. + * + * The skip function is ignored in size-optimized builds to reduce code + * size. Data will be skipped with the fill function when necessary. + * + * @param reader The MPack reader. + * @param skip The function to discard bytes from the source stream. + */ +void mpack_reader_set_skip(mpack_reader_t* reader, mpack_reader_skip_t skip); + +/** + * Sets the error function to call when an error is flagged on the reader. + * + * This should normally be used with mpack_reader_set_context() to register + * a custom pointer to pass to the error function. + * + * See the definition of mpack_reader_error_t for more information about + * what you can do from an error callback. + * + * @see mpack_reader_error_t + * @param reader The MPack reader. + * @param error_fn The function to call when an error is flagged on the reader. + */ +MPACK_INLINE void mpack_reader_set_error_handler(mpack_reader_t* reader, mpack_reader_error_t error_fn) { + reader->error_fn = error_fn; +} + +/** + * Sets the teardown function to call when the reader is destroyed. + * + * This should normally be used with mpack_reader_set_context() to register + * a custom pointer to pass to the teardown function. + * + * @param reader The MPack reader. + * @param teardown The function to call when the reader is destroyed. + */ +MPACK_INLINE void mpack_reader_set_teardown(mpack_reader_t* reader, mpack_reader_teardown_t teardown) { + reader->teardown = teardown; +} + +/** + * @} + */ + +/** + * @name Core Reader Functions + * @{ + */ + +/** + * Queries the error state of the MPack reader. + * + * If a reader is in an error state, you should discard all data since the + * last time the error flag was checked. The error flag cannot be cleared. + */ +MPACK_INLINE mpack_error_t mpack_reader_error(mpack_reader_t* reader) { + return reader->error; +} + +/** + * Places the reader in the given error state, calling the error callback if one + * is set. + * + * This allows you to externally flag errors, for example if you are validating + * data as you read it. + * + * If the reader is already in an error state, this call is ignored and no + * error callback is called. + */ +void mpack_reader_flag_error(mpack_reader_t* reader, mpack_error_t error); + +/** + * Places the reader in the given error state if the given error is not mpack_ok, + * returning the resulting error state of the reader. + * + * This allows you to externally flag errors, for example if you are validating + * data as you read it. + * + * If the given error is mpack_ok or if the reader is already in an error state, + * this call is ignored and the actual error state of the reader is returned. + */ +MPACK_INLINE mpack_error_t mpack_reader_flag_if_error(mpack_reader_t* reader, mpack_error_t error) { + if (error != mpack_ok) + mpack_reader_flag_error(reader, error); + return mpack_reader_error(reader); +} + +/** + * Returns bytes left in the reader's buffer. + * + * If you are done reading MessagePack data but there is other interesting data + * following it, the reader may have buffered too much data. The number of bytes + * remaining in the buffer and a pointer to the position of those bytes can be + * queried here. + * + * If you know the length of the MPack chunk beforehand, it's better to instead + * have your fill function limit the data it reads so that the reader does not + * have extra data. In this case you can simply check that this returns zero. + * + * Returns 0 if the reader is in an error state. + * + * @param reader The MPack reader from which to query remaining data. + * @param data [out] A pointer to the remaining data, or NULL. + * @return The number of bytes remaining in the buffer. + */ +size_t mpack_reader_remaining(mpack_reader_t* reader, const char** data); + +/** + * Reads a MessagePack object header (an MPack tag.) + * + * If an error occurs, the reader is placed in an error state and a + * nil tag is returned. If the reader is already in an error state, + * a nil tag is returned. + * + * If the type is compound (i.e. is a map, array, string, binary or + * extension type), additional reads are required to get the contained + * data, and the corresponding done function must be called when done. + * + * @note Maps in JSON are unordered, so it is recommended not to expect + * a specific ordering for your map values in case your data is converted + * to/from JSON. + * + * @see mpack_read_bytes() + * @see mpack_done_array() + * @see mpack_done_map() + * @see mpack_done_str() + * @see mpack_done_bin() + * @see mpack_done_ext() + */ +mpack_tag_t mpack_read_tag(mpack_reader_t* reader); + +/** + * Parses the next MessagePack object header (an MPack tag) without + * advancing the reader. + * + * If an error occurs, the reader is placed in an error state and a + * nil tag is returned. If the reader is already in an error state, + * a nil tag is returned. + * + * @note Maps in JSON are unordered, so it is recommended not to expect + * a specific ordering for your map values in case your data is converted + * to/from JSON. + * + * @see mpack_read_tag() + * @see mpack_discard() + */ +mpack_tag_t mpack_peek_tag(mpack_reader_t* reader); + +/** + * @} + */ + +/** + * @name String and Data Functions + * @{ + */ + +/** + * Skips bytes from the underlying stream. This is used only to + * skip the contents of a string, binary blob or extension object. + */ +void mpack_skip_bytes(mpack_reader_t* reader, size_t count); + +/** + * Reads bytes from a string, binary blob or extension object, copying + * them into the given buffer. + * + * A str, bin or ext must have been opened by a call to mpack_read_tag() + * which yielded one of these types, or by a call to an expect function + * such as mpack_expect_str() or mpack_expect_bin(). + * + * If an error occurs, the buffer contents are undefined. + * + * This can be called multiple times for a single str, bin or ext + * to read the data in chunks. The total data read must add up + * to the size of the object. + * + * @param reader The MPack reader + * @param p The buffer in which to copy the bytes + * @param count The number of bytes to read + */ +void mpack_read_bytes(mpack_reader_t* reader, char* p, size_t count); + +/** + * Reads bytes from a string, ensures that the string is valid UTF-8, + * and copies the bytes into the given buffer. + * + * A string must have been opened by a call to mpack_read_tag() which + * yielded a string, or by a call to an expect function such as + * mpack_expect_str(). + * + * The given byte count must match the complete size of the string as + * returned by the tag or expect function. You must ensure that the + * buffer fits the data. + * + * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or + * WTF-8. Only pure UTF-8 is allowed. + * + * If an error occurs, the buffer contents are undefined. + * + * Unlike mpack_read_bytes(), this cannot be used to read the data in + * chunks (since this might split a character's UTF-8 bytes, and the + * reader does not keep track of the UTF-8 decoding state between reads.) + * + * @throws mpack_error_type if the string contains invalid UTF-8. + */ +void mpack_read_utf8(mpack_reader_t* reader, char* p, size_t byte_count); + +/** + * Reads bytes from a string, ensures that the string contains no NUL + * bytes, copies the bytes into the given buffer and adds a null-terminator. + * + * A string must have been opened by a call to mpack_read_tag() which + * yielded a string, or by a call to an expect function such as + * mpack_expect_str(). + * + * The given byte count must match the size of the string as returned + * by the tag or expect function. The string will only be copied if + * the buffer is large enough to store it. + * + * If an error occurs, the buffer will contain an empty string. + * + * @note If you know the object will be a string before reading it, + * it is highly recommended to use mpack_expect_cstr() instead. + * Alternatively you could use mpack_peek_tag() and call + * mpack_expect_cstr() if it's a string. + * + * @throws mpack_error_too_big if the string plus null-terminator is larger than the given buffer size + * @throws mpack_error_type if the string contains a null byte. + * + * @see mpack_peek_tag() + * @see mpack_expect_cstr() + * @see mpack_expect_utf8_cstr() + */ +void mpack_read_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count); + +/** + * Reads bytes from a string, ensures that the string is valid UTF-8 + * with no NUL bytes, copies the bytes into the given buffer and adds a + * null-terminator. + * + * A string must have been opened by a call to mpack_read_tag() which + * yielded a string, or by a call to an expect function such as + * mpack_expect_str(). + * + * The given byte count must match the size of the string as returned + * by the tag or expect function. The string will only be copied if + * the buffer is large enough to store it. + * + * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or + * WTF-8. Only pure UTF-8 is allowed, but without the NUL character, since + * it cannot be represented in a null-terminated string. + * + * If an error occurs, the buffer will contain an empty string. + * + * @note If you know the object will be a string before reading it, + * it is highly recommended to use mpack_expect_utf8_cstr() instead. + * Alternatively you could use mpack_peek_tag() and call + * mpack_expect_utf8_cstr() if it's a string. + * + * @throws mpack_error_too_big if the string plus null-terminator is larger than the given buffer size + * @throws mpack_error_type if the string contains invalid UTF-8 or a null byte. + * + * @see mpack_peek_tag() + * @see mpack_expect_utf8_cstr() + */ +void mpack_read_utf8_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count); + +#ifdef MPACK_MALLOC +/** @cond */ +// This can optionally add a null-terminator, but it does not check +// whether the data contains null bytes. This must be done separately +// in a cstring read function (possibly as part of a UTF-8 check.) +char* mpack_read_bytes_alloc_impl(mpack_reader_t* reader, size_t count, bool null_terminated); +/** @endcond */ + +/** + * Reads bytes from a string, binary blob or extension object, allocating + * storage for them and returning the allocated pointer. + * + * The allocated string must be freed with MPACK_FREE() (or simply free() + * if MPack's allocator hasn't been customized.) + * + * Returns NULL if any error occurs, or if count is zero. + */ +MPACK_INLINE char* mpack_read_bytes_alloc(mpack_reader_t* reader, size_t count) { + return mpack_read_bytes_alloc_impl(reader, count, false); +} +#endif + +/** + * Reads bytes from a string, binary blob or extension object in-place in + * the buffer. This can be used to avoid copying the data. + * + * A str, bin or ext must have been opened by a call to mpack_read_tag() + * which yielded one of these types, or by a call to an expect function + * such as mpack_expect_str() or mpack_expect_bin(). + * + * If the bytes are from a string, the string is not null-terminated! Use + * mpack_read_cstr() to copy the string into a buffer and add a null-terminator. + * + * The returned pointer is invalidated on the next read, or when the buffer + * is destroyed. + * + * The reader will move data around in the buffer if needed to ensure that + * the pointer can always be returned, so this should only be used if + * count is very small compared to the buffer size. If you need to check + * whether a small size is reasonable (for example you intend to handle small and + * large sizes differently), you can call mpack_should_read_bytes_inplace(). + * + * This can be called multiple times for a single str, bin or ext + * to read the data in chunks. The total data read must add up + * to the size of the object. + * + * NULL is returned if the reader is in an error state. + * + * @throws mpack_error_too_big if the requested size is larger than the buffer size + * + * @see mpack_should_read_bytes_inplace() + */ +const char* mpack_read_bytes_inplace(mpack_reader_t* reader, size_t count); + +/** + * Reads bytes from a string in-place in the buffer and ensures they are + * valid UTF-8. This can be used to avoid copying the data. + * + * A string must have been opened by a call to mpack_read_tag() which + * yielded a string, or by a call to an expect function such as + * mpack_expect_str(). + * + * The string is not null-terminated! Use mpack_read_utf8_cstr() to + * copy the string into a buffer and add a null-terminator. + * + * The returned pointer is invalidated on the next read, or when the buffer + * is destroyed. + * + * The reader will move data around in the buffer if needed to ensure that + * the pointer can always be returned, so this should only be used if + * count is very small compared to the buffer size. If you need to check + * whether a small size is reasonable (for example you intend to handle small and + * large sizes differently), you can call mpack_should_read_bytes_inplace(). + * + * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or + * WTF-8. Only pure UTF-8 is allowed. + * + * Unlike mpack_read_bytes_inplace(), this cannot be used to read the data in + * chunks (since this might split a character's UTF-8 bytes, and the + * reader does not keep track of the UTF-8 decoding state between reads.) + * + * NULL is returned if the reader is in an error state. + * + * @throws mpack_error_type if the string contains invalid UTF-8 + * @throws mpack_error_too_big if the requested size is larger than the buffer size + * + * @see mpack_should_read_bytes_inplace() + */ +const char* mpack_read_utf8_inplace(mpack_reader_t* reader, size_t count); + +/** + * Returns true if it's a good idea to read the given number of bytes + * in-place. + * + * If the read will be larger than some small fraction of the buffer size, + * this will return false to avoid shuffling too much data back and forth + * in the buffer. + * + * Use this if you're expecting arbitrary size data, and you want to read + * in-place for the best performance when possible but will fall back to + * a normal read if the data is too large. + * + * @see mpack_read_bytes_inplace() + */ +MPACK_INLINE bool mpack_should_read_bytes_inplace(mpack_reader_t* reader, size_t count) { + return (reader->size == 0 || count <= reader->size / MPACK_READER_SMALL_FRACTION_DENOMINATOR); +} + +#if MPACK_EXTENSIONS +/** + * Reads a timestamp contained in an ext object of the given size, closing the + * ext type. + * + * An ext object of exttype @ref MPACK_EXTTYPE_TIMESTAMP must have been opened + * by a call to e.g. mpack_read_tag() or mpack_expect_ext(). + * + * You must NOT call mpack_done_ext() after calling this. A timestamp ext + * object can only contain a single timestamp value, so this calls + * mpack_done_ext() automatically. + * + * @note This requires @ref MPACK_EXTENSIONS. + * + * @throws mpack_error_invalid if the size is not one of the supported + * timestamp sizes, or if the nanoseconds are out of range. + */ +mpack_timestamp_t mpack_read_timestamp(mpack_reader_t* reader, size_t size); +#endif + +/** + * @} + */ + +/** + * @name Core Reader Functions + * @{ + */ + +#if MPACK_READ_TRACKING +/** + * Finishes reading the given type. + * + * This will track reads to ensure that the correct number of elements + * or bytes are read. + */ +void mpack_done_type(mpack_reader_t* reader, mpack_type_t type); +#else +MPACK_INLINE void mpack_done_type(mpack_reader_t* reader, mpack_type_t type) { + MPACK_UNUSED(reader); + MPACK_UNUSED(type); +} +#endif + +/** + * Finishes reading an array. + * + * This will track reads to ensure that the correct number of elements are read. + */ +MPACK_INLINE void mpack_done_array(mpack_reader_t* reader) { + mpack_done_type(reader, mpack_type_array); +} + +/** + * @fn mpack_done_map(mpack_reader_t* reader) + * + * Finishes reading a map. + * + * This will track reads to ensure that the correct number of elements are read. + */ +MPACK_INLINE void mpack_done_map(mpack_reader_t* reader) { + mpack_done_type(reader, mpack_type_map); +} + +/** + * @fn mpack_done_str(mpack_reader_t* reader) + * + * Finishes reading a string. + * + * This will track reads to ensure that the correct number of bytes are read. + */ +MPACK_INLINE void mpack_done_str(mpack_reader_t* reader) { + mpack_done_type(reader, mpack_type_str); +} + +/** + * @fn mpack_done_bin(mpack_reader_t* reader) + * + * Finishes reading a binary data blob. + * + * This will track reads to ensure that the correct number of bytes are read. + */ +MPACK_INLINE void mpack_done_bin(mpack_reader_t* reader) { + mpack_done_type(reader, mpack_type_bin); +} + +#if MPACK_EXTENSIONS +/** + * @fn mpack_done_ext(mpack_reader_t* reader) + * + * Finishes reading an extended type binary data blob. + * + * This will track reads to ensure that the correct number of bytes are read. + * + * @note This requires @ref MPACK_EXTENSIONS. + */ +MPACK_INLINE void mpack_done_ext(mpack_reader_t* reader) { + mpack_done_type(reader, mpack_type_ext); +} +#endif + +/** + * Reads and discards the next object. This will read and discard all + * contained data as well if it is a compound type. + */ +void mpack_discard(mpack_reader_t* reader); + +/** + * @} + */ + +/** @cond */ + +#if MPACK_DEBUG && MPACK_STDIO +/** + * @name Debugging Functions + * @{ + */ +/* + * Converts a blob of MessagePack to a pseudo-JSON string for debugging + * purposes, placing the result in the given buffer with a null-terminator. + * + * If the buffer does not have enough space, the result will be truncated (but + * it is guaranteed to be null-terminated.) + * + * This is only available in debug mode, and only if stdio is available (since + * it uses snprintf().) It's strictly for debugging purposes. + */ +void mpack_print_data_to_buffer(const char* data, size_t data_size, char* buffer, size_t buffer_size); + +/* + * Converts a node to pseudo-JSON for debugging purposes, calling the given + * callback as many times as is necessary to output the character data. + * + * No null-terminator or trailing newline will be written. + * + * This is only available in debug mode, and only if stdio is available (since + * it uses snprintf().) It's strictly for debugging purposes. + */ +void mpack_print_data_to_callback(const char* data, size_t size, mpack_print_callback_t callback, void* context); + +/* + * Converts a blob of MessagePack to pseudo-JSON for debugging purposes + * and pretty-prints it to the given file. + */ +void mpack_print_data_to_file(const char* data, size_t len, FILE* file); + +/* + * Converts a blob of MessagePack to pseudo-JSON for debugging purposes + * and pretty-prints it to stdout. + */ +MPACK_INLINE void mpack_print_data_to_stdout(const char* data, size_t len) { + mpack_print_data_to_file(data, len, stdout); +} + +/* + * Converts the MessagePack contained in the given `FILE*` to pseudo-JSON for + * debugging purposes, calling the given callback as many times as is necessary + * to output the character data. + */ +void mpack_print_stdfile_to_callback(FILE* file, mpack_print_callback_t callback, void* context); + +/* + * Deprecated. + * + * \deprecated Renamed to mpack_print_data_to_stdout(). + */ +MPACK_INLINE void mpack_print(const char* data, size_t len) { + mpack_print_data_to_stdout(data, len); +} + +/** + * @} + */ +#endif + +/** @endcond */ + +/** + * @} + */ + + + +#if MPACK_INTERNAL + +bool mpack_reader_ensure_straddle(mpack_reader_t* reader, size_t count); + +/* + * Ensures there are at least @c count bytes left in the + * data, raising an error and returning false if more + * data cannot be made available. + */ +MPACK_INLINE bool mpack_reader_ensure(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!"); + + if (count <= (size_t)(reader->end - reader->data)) + return true; + return mpack_reader_ensure_straddle(reader, count); +} + +void mpack_read_native_straddle(mpack_reader_t* reader, char* p, size_t count); + +// Reads count bytes into p, deferring to mpack_read_native_straddle() if more +// bytes are needed than are available in the buffer. +MPACK_INLINE void mpack_read_native(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 (count > (size_t)(reader->end - reader->data)) { + mpack_read_native_straddle(reader, p, count); + } else { + mpack_memcpy(p, reader->data, count); + reader->data += count; + } +} + +#if MPACK_READ_TRACKING +#define MPACK_READER_TRACK(reader, error_expr) \ + (((reader)->error == mpack_ok) ? mpack_reader_flag_if_error((reader), (error_expr)) : (reader)->error) +#else +#define MPACK_READER_TRACK(reader, error_expr) (MPACK_UNUSED(reader), mpack_ok) +#endif + +MPACK_INLINE mpack_error_t mpack_reader_track_element(mpack_reader_t* reader) { + return MPACK_READER_TRACK(reader, mpack_track_element(&reader->track, true)); +} + +MPACK_INLINE mpack_error_t mpack_reader_track_peek_element(mpack_reader_t* reader) { + return MPACK_READER_TRACK(reader, mpack_track_peek_element(&reader->track, true)); +} + +MPACK_INLINE mpack_error_t mpack_reader_track_bytes(mpack_reader_t* reader, size_t count) { + MPACK_UNUSED(count); + return MPACK_READER_TRACK(reader, mpack_track_bytes(&reader->track, true, count)); +} + +MPACK_INLINE mpack_error_t mpack_reader_track_str_bytes_all(mpack_reader_t* reader, size_t count) { + MPACK_UNUSED(count); + return MPACK_READER_TRACK(reader, mpack_track_str_bytes_all(&reader->track, true, count)); +} + +#endif + + + +#endif + +MPACK_EXTERN_C_END +MPACK_SILENCE_WARNINGS_END + +#endif + + +/* mpack/mpack-expect.h.h */ + +/** + * @file + * + * Declares the MPack static Expect API. + */ + +#ifndef MPACK_EXPECT_H +#define MPACK_EXPECT_H 1 + +/* #include "mpack-reader.h" */ + +MPACK_SILENCE_WARNINGS_BEGIN +MPACK_EXTERN_C_BEGIN + +#if MPACK_EXPECT + +#if !MPACK_READER +#error "MPACK_EXPECT requires MPACK_READER." +#endif + +/** + * @defgroup expect Expect API + * + * The MPack Expect API allows you to easily read MessagePack data when you + * expect it to follow a predefined schema. + * + * @note If you are not writing code for an embedded device (or otherwise do + * not need maximum performance with minimal memory usage), you should not use + * this. You probably want to use the @link node Node API@endlink instead. + * + * See @ref docs/expect.md for examples. + * + * The main purpose of the Expect API is convenience, so the API is lax. It + * automatically converts between similar types where there is no loss of + * precision. + * + * When using any of the expect functions, if the type or value of what was + * read does not match what is expected, @ref mpack_error_type is raised. + * + * @{ + */ + +/** + * @name Basic Number Functions + * @{ + */ + +/** + * Reads an 8-bit unsigned integer. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in an 8-bit unsigned int. + * + * Returns zero if an error occurs. + */ +uint8_t mpack_expect_u8(mpack_reader_t* reader); + +/** + * Reads a 16-bit unsigned integer. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 16-bit unsigned int. + * + * Returns zero if an error occurs. + */ +uint16_t mpack_expect_u16(mpack_reader_t* reader); + +/** + * Reads a 32-bit unsigned integer. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 32-bit unsigned int. + * + * Returns zero if an error occurs. + */ +uint32_t mpack_expect_u32(mpack_reader_t* reader); + +/** + * Reads a 64-bit unsigned integer. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 64-bit unsigned int. + * + * Returns zero if an error occurs. + */ +uint64_t mpack_expect_u64(mpack_reader_t* reader); + +/** + * Reads an 8-bit signed integer. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in an 8-bit signed int. + * + * Returns zero if an error occurs. + */ +int8_t mpack_expect_i8(mpack_reader_t* reader); + +/** + * Reads a 16-bit signed integer. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 16-bit signed int. + * + * Returns zero if an error occurs. + */ +int16_t mpack_expect_i16(mpack_reader_t* reader); + +/** + * Reads a 32-bit signed integer. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 32-bit signed int. + * + * Returns zero if an error occurs. + */ +int32_t mpack_expect_i32(mpack_reader_t* reader); + +/** + * Reads a 64-bit signed integer. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 64-bit signed int. + * + * Returns zero if an error occurs. + */ +int64_t mpack_expect_i64(mpack_reader_t* reader); + +#if MPACK_FLOAT +/** + * Reads a number, returning the value as a float. The underlying value can be an + * integer, float or double; the value is converted to a float. + * + * @note Reading a double or a large integer with this function can incur a + * loss of precision. + * + * @throws mpack_error_type if the underlying value is not a float, double or integer. + */ +float mpack_expect_float(mpack_reader_t* reader); +#endif + +#if MPACK_DOUBLE +/** + * Reads a number, returning the value as a double. The underlying value can be an + * integer, float or double; the value is converted to a double. + * + * @note Reading a very large integer with this function can incur a + * loss of precision. + * + * @throws mpack_error_type if the underlying value is not a float, double or integer. + */ +double mpack_expect_double(mpack_reader_t* reader); +#endif + +#if MPACK_FLOAT +/** + * Reads a float. The underlying value must be a float, not a double or an integer. + * This ensures no loss of precision can occur. + * + * @throws mpack_error_type if the underlying value is not a float. + */ +float mpack_expect_float_strict(mpack_reader_t* reader); +#endif + +#if MPACK_DOUBLE +/** + * Reads a double. The underlying value must be a float or double, not an integer. + * This ensures no loss of precision can occur. + * + * @throws mpack_error_type if the underlying value is not a float or double. + */ +double mpack_expect_double_strict(mpack_reader_t* reader); +#endif + +#if !MPACK_FLOAT +/** + * Reads a float as a raw uint32_t. The underlying value must be a float, not a + * double or an integer. + * + * @throws mpack_error_type if the underlying value is not a float. + */ +uint32_t mpack_expect_raw_float(mpack_reader_t* reader); +#endif + +#if !MPACK_DOUBLE +/** + * Reads a double as a raw uint64_t. The underlying value must be a double, not a + * float or an integer. + * + * @throws mpack_error_type if the underlying value is not a double. + */ +uint64_t mpack_expect_raw_double(mpack_reader_t* reader); +#endif + +/** + * @} + */ + +/** + * @name Ranged Number Functions + * @{ + */ + +/** + * Reads an 8-bit unsigned integer, ensuring that it falls within the given range. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in an 8-bit unsigned int. + * + * Returns min_value if an error occurs. + */ +uint8_t mpack_expect_u8_range(mpack_reader_t* reader, uint8_t min_value, uint8_t max_value); + +/** + * Reads a 16-bit unsigned integer, ensuring that it falls within the given range. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 16-bit unsigned int. + * + * Returns min_value if an error occurs. + */ +uint16_t mpack_expect_u16_range(mpack_reader_t* reader, uint16_t min_value, uint16_t max_value); + +/** + * Reads a 32-bit unsigned integer, ensuring that it falls within the given range. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 32-bit unsigned int. + * + * Returns min_value if an error occurs. + */ +uint32_t mpack_expect_u32_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value); + +/** + * Reads a 64-bit unsigned integer, ensuring that it falls within the given range. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 64-bit unsigned int. + * + * Returns min_value if an error occurs. + */ +uint64_t mpack_expect_u64_range(mpack_reader_t* reader, uint64_t min_value, uint64_t max_value); + +/** + * Reads an unsigned integer, ensuring that it falls within the given range. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in an unsigned int. + * + * Returns min_value if an error occurs. + */ +MPACK_INLINE unsigned int mpack_expect_uint_range(mpack_reader_t* reader, unsigned int min_value, unsigned int max_value) { + // This should be true at compile-time, so this just wraps the 32-bit + // function. We fallback to 64-bit if for some reason sizeof(int) isn't 4. + if (sizeof(unsigned int) == 4) + return (unsigned int)mpack_expect_u32_range(reader, (uint32_t)min_value, (uint32_t)max_value); + return (unsigned int)mpack_expect_u64_range(reader, min_value, max_value); +} + +/** + * Reads an 8-bit unsigned integer, ensuring that it is at most @a max_value. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in an 8-bit unsigned int. + * + * Returns 0 if an error occurs. + */ +MPACK_INLINE uint8_t mpack_expect_u8_max(mpack_reader_t* reader, uint8_t max_value) { + return mpack_expect_u8_range(reader, 0, max_value); +} + +/** + * Reads a 16-bit unsigned integer, ensuring that it is at most @a max_value. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 16-bit unsigned int. + * + * Returns 0 if an error occurs. + */ +MPACK_INLINE uint16_t mpack_expect_u16_max(mpack_reader_t* reader, uint16_t max_value) { + return mpack_expect_u16_range(reader, 0, max_value); +} + +/** + * Reads a 32-bit unsigned integer, ensuring that it is at most @a max_value. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 32-bit unsigned int. + * + * Returns 0 if an error occurs. + */ +MPACK_INLINE uint32_t mpack_expect_u32_max(mpack_reader_t* reader, uint32_t max_value) { + return mpack_expect_u32_range(reader, 0, max_value); +} + +/** + * Reads a 64-bit unsigned integer, ensuring that it is at most @a max_value. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 64-bit unsigned int. + * + * Returns 0 if an error occurs. + */ +MPACK_INLINE uint64_t mpack_expect_u64_max(mpack_reader_t* reader, uint64_t max_value) { + return mpack_expect_u64_range(reader, 0, max_value); +} + +/** + * Reads an unsigned integer, ensuring that it is at most @a max_value. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in an unsigned int. + * + * Returns 0 if an error occurs. + */ +MPACK_INLINE unsigned int mpack_expect_uint_max(mpack_reader_t* reader, unsigned int max_value) { + return mpack_expect_uint_range(reader, 0, max_value); +} + +/** + * Reads an 8-bit signed integer, ensuring that it falls within the given range. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in an 8-bit signed int. + * + * Returns min_value if an error occurs. + */ +int8_t mpack_expect_i8_range(mpack_reader_t* reader, int8_t min_value, int8_t max_value); + +/** + * Reads a 16-bit signed integer, ensuring that it falls within the given range. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 16-bit signed int. + * + * Returns min_value if an error occurs. + */ +int16_t mpack_expect_i16_range(mpack_reader_t* reader, int16_t min_value, int16_t max_value); + +/** + * Reads a 32-bit signed integer, ensuring that it falls within the given range. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 32-bit signed int. + * + * Returns min_value if an error occurs. + */ +int32_t mpack_expect_i32_range(mpack_reader_t* reader, int32_t min_value, int32_t max_value); + +/** + * Reads a 64-bit signed integer, ensuring that it falls within the given range. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 64-bit signed int. + * + * Returns min_value if an error occurs. + */ +int64_t mpack_expect_i64_range(mpack_reader_t* reader, int64_t min_value, int64_t max_value); + +/** + * Reads a signed integer, ensuring that it falls within the given range. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a signed int. + * + * Returns min_value if an error occurs. + */ +MPACK_INLINE int mpack_expect_int_range(mpack_reader_t* reader, int min_value, int max_value) { + // This should be true at compile-time, so this just wraps the 32-bit + // function. We fallback to 64-bit if for some reason sizeof(int) isn't 4. + if (sizeof(int) == 4) + return (int)mpack_expect_i32_range(reader, (int32_t)min_value, (int32_t)max_value); + return (int)mpack_expect_i64_range(reader, min_value, max_value); +} + +/** + * Reads an 8-bit signed integer, ensuring that it is at least zero and at + * most @a max_value. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in an 8-bit signed int. + * + * Returns 0 if an error occurs. + */ +MPACK_INLINE int8_t mpack_expect_i8_max(mpack_reader_t* reader, int8_t max_value) { + return mpack_expect_i8_range(reader, 0, max_value); +} + +/** + * Reads a 16-bit signed integer, ensuring that it is at least zero and at + * most @a max_value. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 16-bit signed int. + * + * Returns 0 if an error occurs. + */ +MPACK_INLINE int16_t mpack_expect_i16_max(mpack_reader_t* reader, int16_t max_value) { + return mpack_expect_i16_range(reader, 0, max_value); +} + +/** + * Reads a 32-bit signed integer, ensuring that it is at least zero and at + * most @a max_value. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 32-bit signed int. + * + * Returns 0 if an error occurs. + */ +MPACK_INLINE int32_t mpack_expect_i32_max(mpack_reader_t* reader, int32_t max_value) { + return mpack_expect_i32_range(reader, 0, max_value); +} + +/** + * Reads a 64-bit signed integer, ensuring that it is at least zero and at + * most @a max_value. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a 64-bit signed int. + * + * Returns 0 if an error occurs. + */ +MPACK_INLINE int64_t mpack_expect_i64_max(mpack_reader_t* reader, int64_t max_value) { + return mpack_expect_i64_range(reader, 0, max_value); +} + +/** + * Reads an int, ensuring that it is at least zero and at most @a max_value. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a signed int. + * + * Returns 0 if an error occurs. + */ +MPACK_INLINE int mpack_expect_int_max(mpack_reader_t* reader, int max_value) { + return mpack_expect_int_range(reader, 0, max_value); +} + +#if MPACK_FLOAT +/** + * Reads a number, ensuring that it falls within the given range and returning + * the value as a float. The underlying value can be an integer, float or + * double; the value is converted to a float. + * + * @note Reading a double or a large integer with this function can incur a + * loss of precision. + * + * @throws mpack_error_type if the underlying value is not a float, double or integer. + */ +float mpack_expect_float_range(mpack_reader_t* reader, float min_value, float max_value); +#endif + +#if MPACK_DOUBLE +/** + * Reads a number, ensuring that it falls within the given range and returning + * the value as a double. The underlying value can be an integer, float or + * double; the value is converted to a double. + * + * @note Reading a very large integer with this function can incur a + * loss of precision. + * + * @throws mpack_error_type if the underlying value is not a float, double or integer. + */ +double mpack_expect_double_range(mpack_reader_t* reader, double min_value, double max_value); +#endif + +/** + * @} + */ + + + +// These are additional Basic Number functions that wrap inline range functions. + +/** + * @name Basic Number Functions + * @{ + */ + +/** + * Reads an unsigned int. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in an unsigned int. + * + * Returns zero if an error occurs. + */ +MPACK_INLINE unsigned int mpack_expect_uint(mpack_reader_t* reader) { + + // This should be true at compile-time, so this just wraps the 32-bit function. + if (sizeof(unsigned int) == 4) + return (unsigned int)mpack_expect_u32(reader); + + // Otherwise we wrap the max function to ensure it fits. + return (unsigned int)mpack_expect_u64_max(reader, MPACK_UINT_MAX); + +} + +/** + * Reads a signed int. + * + * The underlying type may be an integer type of any size and signedness, + * as long as the value can be represented in a signed int. + * + * Returns zero if an error occurs. + */ +MPACK_INLINE int mpack_expect_int(mpack_reader_t* reader) { + + // This should be true at compile-time, so this just wraps the 32-bit function. + if (sizeof(int) == 4) + return (int)mpack_expect_i32(reader); + + // Otherwise we wrap the range function to ensure it fits. + return (int)mpack_expect_i64_range(reader, MPACK_INT_MIN, MPACK_INT_MAX); + +} + +/** + * @} + */ + + + +/** + * @name Matching Number Functions + * @{ + */ + +/** + * Reads an unsigned integer, ensuring that it exactly matches the given value. + * + * mpack_error_type is raised if the value is not representable as an unsigned + * integer or if it does not exactly match the given value. + */ +void mpack_expect_uint_match(mpack_reader_t* reader, uint64_t value); + +/** + * Reads a signed integer, ensuring that it exactly matches the given value. + * + * mpack_error_type is raised if the value is not representable as a signed + * integer or if it does not exactly match the given value. + */ +void mpack_expect_int_match(mpack_reader_t* reader, int64_t value); + +/** + * @} + */ + +/** + * @name Other Basic Types + * @{ + */ + +/** + * Reads a nil, raising @ref mpack_error_type if the value is not nil. + */ +void mpack_expect_nil(mpack_reader_t* reader); + +/** + * Reads a boolean. + * + * @note Integers will raise mpack_error_type; the value must be strictly a boolean. + */ +bool mpack_expect_bool(mpack_reader_t* reader); + +/** + * Reads a boolean, raising @ref mpack_error_type if its value is not @c true. + */ +void mpack_expect_true(mpack_reader_t* reader); + +/** + * Reads a boolean, raising @ref mpack_error_type if its value is not @c false. + */ +void mpack_expect_false(mpack_reader_t* reader); + +/** + * @} + */ + +/** + * @name Extension Functions + * @{ + */ + +#if MPACK_EXTENSIONS +/** + * Reads a timestamp. + * + * @note This requires @ref MPACK_EXTENSIONS. + */ +mpack_timestamp_t mpack_expect_timestamp(mpack_reader_t* reader); + +/** + * Reads a timestamp in seconds, truncating the nanoseconds (if any). + * + * @note This requires @ref MPACK_EXTENSIONS. + */ +int64_t mpack_expect_timestamp_truncate(mpack_reader_t* reader); +#endif + +/** + * @} + */ + +/** + * @name Compound Types + * @{ + */ + +/** + * Reads the start of a map, returning its element count. + * + * A number of values follow equal to twice the element count of the map, + * alternating between keys and values. @ref mpack_done_map() must be called + * once all elements have been read. + * + * @note Maps in JSON are unordered, so it is recommended not to expect + * a specific ordering for your map values in case your data is converted + * to/from JSON. + * + * @warning This call is dangerous! It does not have a size limit, and it + * does not have any way of checking whether there is enough data in the + * message (since the data could be coming from a stream.) When looping + * through the map's contents, you must check for errors on each iteration + * of the loop. Otherwise an attacker could craft a message declaring a map + * of a billion elements which would throw your parsing code into an + * infinite loop! You should strongly consider using mpack_expect_map_max() + * with a safe maximum size instead. + * + * @throws mpack_error_type if the value is not a map. + */ +uint32_t mpack_expect_map(mpack_reader_t* reader); + +/** + * Reads the start of a map with a number of elements in the given range, returning + * its element count. + * + * A number of values follow equal to twice the element count of the map, + * alternating between keys and values. @ref mpack_done_map() must be called + * once all elements have been read. + * + * @note Maps in JSON are unordered, so it is recommended not to expect + * a specific ordering for your map values in case your data is converted + * to/from JSON. + * + * min_count is returned if an error occurs. + * + * @throws mpack_error_type if the value is not a map or if its size does + * not fall within the given range. + */ +uint32_t mpack_expect_map_range(mpack_reader_t* reader, uint32_t min_count, uint32_t max_count); + +/** + * Reads the start of a map with a number of elements at most @a max_count, + * returning its element count. + * + * A number of values follow equal to twice the element count of the map, + * alternating between keys and values. @ref mpack_done_map() must be called + * once all elements have been read. + * + * @note Maps in JSON are unordered, so it is recommended not to expect + * a specific ordering for your map values in case your data is converted + * to/from JSON. + * + * Zero is returned if an error occurs. + * + * @throws mpack_error_type if the value is not a map or if its size is + * greater than max_count. + */ +MPACK_INLINE uint32_t mpack_expect_map_max(mpack_reader_t* reader, uint32_t max_count) { + return mpack_expect_map_range(reader, 0, max_count); +} + +/** + * Reads the start of a map of the exact size given. + * + * A number of values follow equal to twice the element count of the map, + * alternating between keys and values. @ref mpack_done_map() must be called + * once all elements have been read. + * + * @note Maps in JSON are unordered, so it is recommended not to expect + * a specific ordering for your map values in case your data is converted + * to/from JSON. + * + * @throws mpack_error_type if the value is not a map or if its size + * does not match the given count. + */ +void mpack_expect_map_match(mpack_reader_t* reader, uint32_t count); + +/** + * Reads a nil node or the start of a map, returning whether a map was + * read and placing its number of key/value pairs in count. + * + * If a map was read, a number of values follow equal to twice the element count + * of the map, alternating between keys and values. @ref mpack_done_map() should + * also be called once all elements have been read (only if a map was read.) + * + * @note Maps in JSON are unordered, so it is recommended not to expect + * a specific ordering for your map values in case your data is converted + * to/from JSON. + * + * @warning This call is dangerous! It does not have a size limit, and it + * does not have any way of checking whether there is enough data in the + * message (since the data could be coming from a stream.) When looping + * through the map's contents, you must check for errors on each iteration + * of the loop. Otherwise an attacker could craft a message declaring a map + * of a billion elements which would throw your parsing code into an + * infinite loop! You should strongly consider using mpack_expect_map_max_or_nil() + * with a safe maximum size instead. + * + * @returns @c true if a map was read successfully; @c false if nil was read + * or an error occurred. + * @throws mpack_error_type if the value is not a nil or map. + */ +bool mpack_expect_map_or_nil(mpack_reader_t* reader, uint32_t* count); + +/** + * Reads a nil node or the start of a map with a number of elements at most + * max_count, returning whether a map was read and placing its number of + * key/value pairs in count. + * + * If a map was read, a number of values follow equal to twice the element count + * of the map, alternating between keys and values. @ref mpack_done_map() should + * anlso be called once all elements have been read (only if a map was read.) + * + * @note Maps in JSON are unordered, so it is recommended not to expect + * a specific ordering for your map values in case your data is converted + * to/from JSON. Consider using mpack_expect_key_cstr() or mpack_expect_key_uint() + * to switch on the key; see @ref docs/expect.md for examples. + * + * @returns @c true if a map was read successfully; @c false if nil was read + * or an error occurred. + * @throws mpack_error_type if the value is not a nil or map. + */ +bool mpack_expect_map_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count); + +/** + * Reads the start of an array, returning its element count. + * + * A number of values follow equal to the element count of the array. + * @ref mpack_done_array() must be called once all elements have been read. + * + * @warning This call is dangerous! It does not have a size limit, and it + * does not have any way of checking whether there is enough data in the + * message (since the data could be coming from a stream.) When looping + * through the array's contents, you must check for errors on each iteration + * of the loop. Otherwise an attacker could craft a message declaring an array + * of a billion elements which would throw your parsing code into an + * infinite loop! You should strongly consider using mpack_expect_array_max() + * with a safe maximum size instead. + */ +uint32_t mpack_expect_array(mpack_reader_t* reader); + +/** + * Reads the start of an array with a number of elements in the given range, + * returning its element count. + * + * A number of values follow equal to the element count of the array. + * @ref mpack_done_array() must be called once all elements have been read. + * + * min_count is returned if an error occurs. + * + * @throws mpack_error_type if the value is not an array or if its size does + * not fall within the given range. + */ +uint32_t mpack_expect_array_range(mpack_reader_t* reader, uint32_t min_count, uint32_t max_count); + +/** + * Reads the start of an array with a number of elements at most @a max_count, + * returning its element count. + * + * A number of values follow equal to the element count of the array. + * @ref mpack_done_array() must be called once all elements have been read. + * + * Zero is returned if an error occurs. + * + * @throws mpack_error_type if the value is not an array or if its size is + * greater than max_count. + */ +MPACK_INLINE uint32_t mpack_expect_array_max(mpack_reader_t* reader, uint32_t max_count) { + return mpack_expect_array_range(reader, 0, max_count); +} + +/** + * Reads the start of an array of the exact size given. + * + * A number of values follow equal to the element count of the array. + * @ref mpack_done_array() must be called once all elements have been read. + * + * @throws mpack_error_type if the value is not an array or if its size does + * not match the given count. + */ +void mpack_expect_array_match(mpack_reader_t* reader, uint32_t count); + +/** + * Reads a nil node or the start of an array, returning whether an array was + * read and placing its number of elements in count. + * + * If an array was read, a number of values follow equal to the element count + * of the array. @ref mpack_done_array() should also be called once all elements + * have been read (only if an array was read.) + * + * @warning This call is dangerous! It does not have a size limit, and it + * does not have any way of checking whether there is enough data in the + * message (since the data could be coming from a stream.) When looping + * through the array's contents, you must check for errors on each iteration + * of the loop. Otherwise an attacker could craft a message declaring an array + * of a billion elements which would throw your parsing code into an + * infinite loop! You should strongly consider using mpack_expect_array_max_or_nil() + * with a safe maximum size instead. + * + * @returns @c true if an array was read successfully; @c false if nil was read + * or an error occurred. + * @throws mpack_error_type if the value is not a nil or array. + */ +bool mpack_expect_array_or_nil(mpack_reader_t* reader, uint32_t* count); + +/** + * Reads a nil node or the start of an array with a number of elements at most + * max_count, returning whether an array was read and placing its number of + * key/value pairs in count. + * + * If an array was read, a number of values follow equal to the element count + * of the array. @ref mpack_done_array() should also be called once all elements + * have been read (only if an array was read.) + * + * @returns @c true if an array was read successfully; @c false if nil was read + * or an error occurred. + * @throws mpack_error_type if the value is not a nil or array. + */ +bool mpack_expect_array_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count); + +#ifdef MPACK_MALLOC +/** + * @hideinitializer + * + * Reads the start of an array and allocates storage for it, placing its + * size in out_count. A number of objects follow equal to the element count + * of the array. You must call @ref mpack_done_array() when done (even + * if the element count is zero.) + * + * If an error occurs, NULL is returned and the reader is placed in an + * error state. + * + * If the count is zero, NULL is returned. This does not indicate error. + * You should not check the return value for NULL to check for errors; only + * check the reader's error state. + * + * The allocated array must be freed with MPACK_FREE() (or simply free() + * if MPack's allocator hasn't been customized.) + * + * @throws mpack_error_type if the value is not an array or if its size is + * greater than max_count. + */ +#define mpack_expect_array_alloc(reader, Type, max_count, out_count) \ + ((Type*)mpack_expect_array_alloc_impl(reader, sizeof(Type), max_count, out_count, false)) + +/** + * @hideinitializer + * + * Reads a nil node or the start of an array and allocates storage for it, + * placing its size in out_count. A number of objects follow equal to the element + * count of the array if a non-empty array was read. + * + * If an error occurs, NULL is returned and the reader is placed in an + * error state. + * + * If a nil node was read, NULL is returned. If an empty array was read, + * mpack_done_array() is called automatically and NULL is returned. These + * do not indicate error. You should not check the return value for NULL + * to check for errors; only check the reader's error state. + * + * The allocated array must be freed with MPACK_FREE() (or simply free() + * if MPack's allocator hasn't been customized.) + * + * @warning You must call @ref mpack_done_array() if and only if a non-zero + * element count is read. This function does not differentiate between nil + * and an empty array. + * + * @throws mpack_error_type if the value is not an array or if its size is + * greater than max_count. + */ +#define mpack_expect_array_or_nil_alloc(reader, Type, max_count, out_count) \ + ((Type*)mpack_expect_array_alloc_impl(reader, sizeof(Type), max_count, out_count, true)) +#endif + +/** + * @} + */ + +/** @cond */ +#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); +#endif +/** @endcond */ + + +/** + * @name String Functions + * @{ + */ + +/** + * Reads the start of a string, returning its size in bytes. + * + * The bytes follow and must be read separately with mpack_read_bytes() + * or mpack_read_bytes_inplace(). mpack_done_str() must be called + * once all bytes have been read. + * + * NUL bytes are allowed in the string, and no encoding checks are done. + * + * mpack_error_type is raised if the value is not a string. + */ +uint32_t mpack_expect_str(mpack_reader_t* reader); + +/** + * Reads a string of at most the given size, writing it into the + * given buffer and returning its size in bytes. + * + * This does not add a null-terminator! Use mpack_expect_cstr() to + * add a null-terminator. + * + * NUL bytes are allowed in the string, and no encoding checks are done. + */ +size_t mpack_expect_str_buf(mpack_reader_t* reader, char* buf, size_t bufsize); + +/** + * Reads a string into the given buffer, ensuring it is a valid UTF-8 string + * and returning its size in bytes. + * + * This does not add a null-terminator! Use mpack_expect_utf8_cstr() to + * add a null-terminator. + * + * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or + * WTF-8. Only pure UTF-8 is allowed. + * + * NUL bytes are allowed in the string (as they are in UTF-8.) + * + * Raises mpack_error_too_big if there is not enough room for the string. + * Raises mpack_error_type if the value is not a string or is not a valid UTF-8 string. + */ +size_t mpack_expect_utf8(mpack_reader_t* reader, char* buf, size_t bufsize); + +/** + * Reads the start of a string, raising an error if its length is not + * at most the given number of bytes (not including any null-terminator.) + * + * The bytes follow and must be read separately with mpack_read_bytes() + * or mpack_read_bytes_inplace(). @ref mpack_done_str() must be called + * once all bytes have been read. + * + * @throws mpack_error_type If the value is not a string. + * @throws mpack_error_too_big If the string's length in bytes is larger than the given maximum size. + */ +MPACK_INLINE uint32_t mpack_expect_str_max(mpack_reader_t* reader, uint32_t maxsize) { + uint32_t length = mpack_expect_str(reader); + if (length > maxsize) { + mpack_reader_flag_error(reader, mpack_error_too_big); + return 0; + } + return length; +} + +/** + * Reads the start of a string, raising an error if its length is not + * exactly the given number of bytes (not including any null-terminator.) + * + * The bytes follow and must be read separately with mpack_read_bytes() + * or mpack_read_bytes_inplace(). @ref mpack_done_str() must be called + * once all bytes have been read. + * + * mpack_error_type is raised if the value is not a string or if its + * length does not match. + */ +MPACK_INLINE void mpack_expect_str_length(mpack_reader_t* reader, uint32_t count) { + if (mpack_expect_str(reader) != count) + mpack_reader_flag_error(reader, mpack_error_type); +} + +/** + * Reads a string, ensuring it exactly matches the given string. + * + * Remember that maps are unordered in JSON. Don't use this for map keys + * unless the map has only a single key! + */ +void mpack_expect_str_match(mpack_reader_t* reader, const char* str, size_t length); + +/** + * Reads a string into the given buffer, ensures it has no null bytes, + * and adds a null-terminator at the end. + * + * Raises mpack_error_too_big if there is not enough room for the string and null-terminator. + * Raises mpack_error_type if the value is not a string or contains a null byte. + */ +void mpack_expect_cstr(mpack_reader_t* reader, char* buf, size_t size); + +/** + * Reads a string into the given buffer, ensures it is a valid UTF-8 string + * without NUL characters, and adds a null-terminator at the end. + * + * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or + * WTF-8. Only pure UTF-8 is allowed, but without the NUL character, since + * it cannot be represented in a null-terminated string. + * + * Raises mpack_error_too_big if there is not enough room for the string and null-terminator. + * Raises mpack_error_type if the value is not a string or is not a valid UTF-8 string. + */ +void mpack_expect_utf8_cstr(mpack_reader_t* reader, char* buf, size_t size); + +#ifdef MPACK_MALLOC +/** + * Reads a string with the given total maximum size (including space for a + * null-terminator), allocates storage for it, ensures it has no null-bytes, + * and adds a null-terminator at the end. You assume ownership of the + * returned pointer if reading succeeds. + * + * The allocated string must be freed with MPACK_FREE() (or simply free() + * if MPack's allocator hasn't been customized.) + * + * @throws mpack_error_too_big If the string plus null-terminator is larger than the given maxsize. + * @throws mpack_error_type If the value is not a string or contains a null byte. + */ +char* mpack_expect_cstr_alloc(mpack_reader_t* reader, size_t maxsize); + +/** + * Reads a string with the given total maximum size (including space for a + * null-terminator), allocates storage for it, ensures it is valid UTF-8 + * with no null-bytes, and adds a null-terminator at the end. You assume + * ownership of the returned pointer if reading succeeds. + * + * The length in bytes of the string, not including the null-terminator, + * will be written to size. + * + * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or + * WTF-8. Only pure UTF-8 is allowed, but without the NUL character, since + * it cannot be represented in a null-terminated string. + * + * The allocated string must be freed with MPACK_FREE() (or simply free() + * if MPack's allocator hasn't been customized.) + * if you want a null-terminator. + * + * @throws mpack_error_too_big If the string plus null-terminator is larger + * than the given maxsize. + * @throws mpack_error_type If the value is not a string or contains + * invalid UTF-8 or a null byte. + */ +char* mpack_expect_utf8_cstr_alloc(mpack_reader_t* reader, size_t maxsize); +#endif + +/** + * Reads a string, ensuring it exactly matches the given null-terminated + * string. + * + * Remember that maps are unordered in JSON. Don't use this for map keys + * unless the map has only a single key! + */ +MPACK_INLINE void mpack_expect_cstr_match(mpack_reader_t* reader, const char* cstr) { + mpack_assert(cstr != NULL, "cstr pointer is NULL"); + mpack_expect_str_match(reader, cstr, mpack_strlen(cstr)); +} + +/** + * @} + */ + +/** + * @name Binary Data + * @{ + */ + +/** + * Reads the start of a binary blob, returning its size in bytes. + * + * The bytes follow and must be read separately with mpack_read_bytes() + * or mpack_read_bytes_inplace(). @ref mpack_done_bin() must be called + * once all bytes have been read. + * + * mpack_error_type is raised if the value is not a binary blob. + */ +uint32_t mpack_expect_bin(mpack_reader_t* reader); + +/** + * Reads the start of a binary blob, raising an error if its length is not + * at most the given number of bytes. + * + * The bytes follow and must be read separately with mpack_read_bytes() + * or mpack_read_bytes_inplace(). @ref mpack_done_bin() must be called + * once all bytes have been read. + * + * mpack_error_type is raised if the value is not a binary blob or if its + * length does not match. + */ +MPACK_INLINE uint32_t mpack_expect_bin_max(mpack_reader_t* reader, uint32_t maxsize) { + uint32_t length = mpack_expect_bin(reader); + if (length > maxsize) { + mpack_reader_flag_error(reader, mpack_error_type); + return 0; + } + return length; +} + +/** + * Reads the start of a binary blob, raising an error if its length is not + * exactly the given number of bytes. + * + * The bytes follow and must be read separately with mpack_read_bytes() + * or mpack_read_bytes_inplace(). @ref mpack_done_bin() must be called + * once all bytes have been read. + * + * @throws mpack_error_type if the value is not a binary blob or if its size + * does not match. + */ +MPACK_INLINE void mpack_expect_bin_size(mpack_reader_t* reader, uint32_t count) { + if (mpack_expect_bin(reader) != count) + mpack_reader_flag_error(reader, mpack_error_type); +} + +/** + * Reads a binary blob into the given buffer, returning its size in bytes. + * + * For compatibility, this will accept if the underlying type is string or + * binary (since in MessagePack 1.0, strings and binary data were combined + * under the "raw" type which became string in 1.1.) + */ +size_t mpack_expect_bin_buf(mpack_reader_t* reader, char* buf, size_t size); + +/** + * Reads a binary blob with the exact given size into the given buffer. + * + * For compatibility, this will accept if the underlying type is string or + * binary (since in MessagePack 1.0, strings and binary data were combined + * under the "raw" type which became string in 1.1.) + * + * @throws mpack_error_type if the value is not a binary blob or if its size + * does not match. + */ +void mpack_expect_bin_size_buf(mpack_reader_t* reader, char* buf, uint32_t size); + +/** + * Reads a binary blob with the given total maximum size, allocating storage for it. + */ +char* mpack_expect_bin_alloc(mpack_reader_t* reader, size_t maxsize, size_t* size); + +/** + * @} + */ + +/** + * @name Extension Functions + * @{ + */ + +#if MPACK_EXTENSIONS +/** + * Reads the start of an extension blob, returning its size in bytes and + * placing the type into @p type. + * + * The bytes follow and must be read separately with mpack_read_bytes() + * or mpack_read_bytes_inplace(). @ref mpack_done_ext() must be called + * once all bytes have been read. + * + * @p type will be a user-defined type in the range [0,127] or a reserved type + * in the range [-128,-2]. + * + * mpack_error_type is raised if the value is not an extension blob. The @p + * type value is zero if an error occurs. + * + * @note This cannot be used to match a timestamp. @ref mpack_error_type will + * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or + * mpack_expect_timestamp_truncate() instead. + * + * @note This requires @ref MPACK_EXTENSIONS. + * + * @warning Be careful when using reserved types. They may no longer be ext + * types in the future, and previously valid data containing reserved types may + * become invalid in the future. + */ +uint32_t mpack_expect_ext(mpack_reader_t* reader, int8_t* type); + +/** + * Reads the start of an extension blob, raising an error if its length is not + * at most the given number of bytes and placing the type into @p type. + * + * The bytes follow and must be read separately with mpack_read_bytes() + * or mpack_read_bytes_inplace(). @ref mpack_done_ext() must be called + * once all bytes have been read. + * + * mpack_error_type is raised if the value is not an extension blob or if its + * length does not match. The @p type value is zero if an error is raised. + * + * @p type will be a user-defined type in the range [0,127] or a reserved type + * in the range [-128,-2]. + * + * @note This cannot be used to match a timestamp. @ref mpack_error_type will + * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or + * mpack_expect_timestamp_truncate() instead. + * + * @note This requires @ref MPACK_EXTENSIONS. + * + * @warning Be careful when using reserved types. They may no longer be ext + * types in the future, and previously valid data containing reserved types may + * become invalid in the future. + * + * @see mpack_expect_ext() + */ +MPACK_INLINE uint32_t mpack_expect_ext_max(mpack_reader_t* reader, int8_t* type, uint32_t maxsize) { + uint32_t length = mpack_expect_ext(reader, type); + if (length > maxsize) { + mpack_reader_flag_error(reader, mpack_error_type); + return 0; + } + return length; +} + +/** + * Reads the start of an extension blob, raising an error if its length is not + * exactly the given number of bytes and placing the type into @p type. + * + * The bytes follow and must be read separately with mpack_read_bytes() + * or mpack_read_bytes_inplace(). @ref mpack_done_ext() must be called + * once all bytes have been read. + * + * mpack_error_type is raised if the value is not an extension blob or if its + * length does not match. The @p type value is zero if an error is raised. + * + * @p type will be a user-defined type in the range [0,127] or a reserved type + * in the range [-128,-2]. + * + * @note This cannot be used to match a timestamp. @ref mpack_error_type will + * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or + * mpack_expect_timestamp_truncate() instead. + * + * @note This requires @ref MPACK_EXTENSIONS. + * + * @warning Be careful when using reserved types. They may no longer be ext + * types in the future, and previously valid data containing reserved types may + * become invalid in the future. + * + * @see mpack_expect_ext() + */ +MPACK_INLINE void mpack_expect_ext_size(mpack_reader_t* reader, int8_t* type, uint32_t count) { + if (mpack_expect_ext(reader, type) != count) { + *type = 0; + mpack_reader_flag_error(reader, mpack_error_type); + } +} + +/** + * Reads an extension blob into the given buffer, returning its size in bytes + * and placing the type into @p type. + * + * mpack_error_type is raised if the value is not an extension blob or if its + * length does not match. The @p type value is zero if an error is raised. + * + * @p type will be a user-defined type in the range [0,127] or a reserved type + * in the range [-128,-2]. + * + * @note This cannot be used to match a timestamp. @ref mpack_error_type will + * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or + * mpack_expect_timestamp_truncate() instead. + * + * @warning Be careful when using reserved types. They may no longer be ext + * types in the future, and previously valid data containing reserved types may + * become invalid in the future. + * + * @note This requires @ref MPACK_EXTENSIONS. + * + * @see mpack_expect_ext() + */ +size_t mpack_expect_ext_buf(mpack_reader_t* reader, int8_t* type, char* buf, size_t size); +#endif + +#if MPACK_EXTENSIONS && defined(MPACK_MALLOC) +/** + * Reads an extension blob with the given total maximum size, allocating + * storage for it, and placing the type into @p type. + * + * mpack_error_type is raised if the value is not an extension blob or if its + * length does not match. The @p type value is zero if an error is raised. + * + * @p type will be a user-defined type in the range [0,127] or a reserved type + * in the range [-128,-2]. + * + * @note This cannot be used to match a timestamp. @ref mpack_error_type will + * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or + * mpack_expect_timestamp_truncate() instead. + * + * @warning Be careful when using reserved types. They may no longer be ext + * types in the future, and previously valid data containing reserved types may + * become invalid in the future. + * + * @note This requires @ref MPACK_EXTENSIONS and @ref MPACK_MALLOC. + * + * @see mpack_expect_ext() + */ +char* mpack_expect_ext_alloc(mpack_reader_t* reader, int8_t* type, size_t maxsize, size_t* size); +#endif + +/** + * @} + */ + +/** + * @name Special Functions + * @{ + */ + +/** + * Reads a MessagePack object header (an MPack tag), expecting it to exactly + * match the given tag. + * + * If the type is compound (i.e. is a map, array, string, binary or + * extension type), additional reads are required to get the contained + * data, and the corresponding done function must be called when done. + * + * @throws mpack_error_type if the tag does not match + * + * @see mpack_read_bytes() + * @see mpack_done_array() + * @see mpack_done_map() + * @see mpack_done_str() + * @see mpack_done_bin() + * @see mpack_done_ext() + */ +void mpack_expect_tag(mpack_reader_t* reader, mpack_tag_t tag); + +/** + * Expects a string matching one of the strings in the given array, + * returning its array index. + * + * If the value does not match any of the given strings, + * @ref mpack_error_type is flagged. Use mpack_expect_enum_optional() + * if you want to allow other values than the given strings. + * + * If any error occurs or the reader is in an error state, @a count + * is returned. + * + * This can be used to quickly parse a string into an enum when the + * enum values range from 0 to @a count-1. If the last value in the + * enum is a special "count" value, it can be passed as the count, + * and the return value can be cast directly to the enum type. + * + * @code{.c} + * typedef enum { APPLE , BANANA , ORANGE , COUNT} fruit_t; + * const char* fruits[] = {"apple", "banana", "orange"}; + * + * fruit_t fruit = (fruit_t)mpack_expect_enum(reader, fruits, COUNT); + * @endcode + * + * See @ref docs/expect.md for more examples. + * + * The maximum string length is the size of the buffer (strings are read in-place.) + * + * @param reader The reader + * @param strings An array of expected strings of length count + * @param count The number of strings + * @return The index of the matched string, or @a count in case of error + */ +size_t mpack_expect_enum(mpack_reader_t* reader, const char* strings[], size_t count); + +/** + * Expects a string matching one of the strings in the given array + * returning its array index, or @a count if no strings match. + * + * If the value is not a string, or it does not match any of the + * given strings, @a count is returned and no error is flagged. + * + * If any error occurs or the reader is in an error state, @a count + * is returned. + * + * This can be used to quickly parse a string into an enum when the + * enum values range from 0 to @a count-1. If the last value in the + * enum is a special "count" value, it can be passed as the count, + * and the return value can be cast directly to the enum type. + * + * @code{.c} + * typedef enum { APPLE , BANANA , ORANGE , COUNT} fruit_t; + * const char* fruits[] = {"apple", "banana", "orange"}; + * + * fruit_t fruit = (fruit_t)mpack_expect_enum_optional(reader, fruits, COUNT); + * @endcode + * + * See @ref docs/expect.md for more examples. + * + * The maximum string length is the size of the buffer (strings are read in-place.) + * + * @param reader The reader + * @param strings An array of expected strings of length count + * @param count The number of strings + * + * @return The index of the matched string, or @a count if it does not + * match or an error occurs + */ +size_t mpack_expect_enum_optional(mpack_reader_t* reader, const char* strings[], size_t count); + +/** + * Expects an unsigned integer map key between 0 and count-1, marking it + * as found in the given bool array and returning it. + * + * This is a helper for switching among int keys in a map. It is + * typically used with an enum to define the key values. It should + * be called in the expression of a switch() statement. See @ref + * docs/expect.md for an example. + * + * The found array must be cleared before expecting the first key. If the + * flag for a given key is already set when found (i.e. the map contains a + * duplicate key), mpack_error_invalid is flagged. + * + * If the key is not a non-negative integer, or if the key is @a count or + * larger, @a count is returned and no error is flagged. If you want an error + * on unrecognized keys, flag an error in the default case in your switch; + * otherwise you must call mpack_discard() to discard its content. + * + * @param reader The reader + * @param found An array of bool flags of length count + * @param count The number of values in the found array, and one more than the + * maximum allowed key + * + * @see @ref docs/expect.md + */ +size_t mpack_expect_key_uint(mpack_reader_t* reader, bool found[], size_t count); + +/** + * Expects a string map key matching one of the strings in the given key list, + * marking it as found in the given bool array and returning its index. + * + * This is a helper for switching among string keys in a map. It is + * typically used with an enum with names matching the strings in the + * array to define the key indices. It should be called in the expression + * of a switch() statement. See @ref docs/expect.md for an example. + * + * The found array must be cleared before expecting the first key. If the + * flag for a given key is already set when found (i.e. the map contains a + * duplicate key), mpack_error_invalid is flagged. + * + * If the key is unrecognized, count is returned and no error is flagged. If + * you want an error on unrecognized keys, flag an error in the default case + * in your switch; otherwise you must call mpack_discard() to discard its content. + * + * The maximum key length is the size of the buffer (keys are read in-place.) + * + * @param reader The reader + * @param keys An array of expected string keys of length count + * @param found An array of bool flags of length count + * @param count The number of values in the keys and found arrays + * + * @see @ref docs/expect.md + */ +size_t mpack_expect_key_cstr(mpack_reader_t* reader, const char* keys[], + bool found[], size_t count); + +/** + * @} + */ + +/** + * @} + */ + +#endif + +MPACK_EXTERN_C_END +MPACK_SILENCE_WARNINGS_END + +#endif + + + +/* mpack/mpack-node.h.h */ + +/** + * @file + * + * Declares the MPack dynamic Node API. + */ + +#ifndef MPACK_NODE_H +#define MPACK_NODE_H 1 + +/* #include "mpack-reader.h" */ + +MPACK_SILENCE_WARNINGS_BEGIN +MPACK_EXTERN_C_BEGIN + +#if MPACK_NODE + +/** + * @defgroup node Node API + * + * The MPack Node API allows you to parse a chunk of MessagePack into a + * dynamically typed data structure, providing random access to the parsed + * data. + * + * See @ref docs/node.md for examples. + * + * @{ + */ + +/** + * A handle to node data in a parsed MPack tree. + * + * Nodes represent either primitive values or compound types. If a + * node is a compound type, it contains a pointer to its child nodes, + * or a pointer to its underlying data. + * + * Nodes are immutable. + * + * @note @ref mpack_node_t is an opaque reference to the node data, not the + * node data itself. (It contains pointers to both the node data and the tree.) + * It is passed by value in the Node API. + */ +typedef struct mpack_node_t mpack_node_t; + +/** + * The storage for nodes in an MPack tree. + * + * You only need to use this if you intend to provide your own storage + * for nodes instead of letting the tree allocate it. + * + * @ref mpack_node_data_t is 16 bytes on most common architectures (32-bit + * and 64-bit.) + */ +typedef struct mpack_node_data_t mpack_node_data_t; + +/** + * An MPack tree parser to parse a blob or stream of MessagePack. + * + * When a message is parsed, the tree contains a single root node which + * contains all parsed data. The tree and its nodes are immutable. + */ +typedef struct mpack_tree_t mpack_tree_t; + +/** + * An error handler function to be called when an error is flagged on + * the tree. + * + * The error handler will only be called once on the first error flagged; + * any subsequent node reads and errors are ignored, and the tree is + * permanently in that error state. + * + * MPack is safe against non-local jumps out of error handler callbacks. + * This means you are allowed to longjmp or throw an exception (in C++, + * Objective-C, or with SEH) out of this callback. + * + * Bear in mind when using longjmp that local non-volatile variables that + * have changed are undefined when setjmp() returns, so you can't put the + * tree on the stack in the same activation frame as the setjmp without + * declaring it volatile. + * + * You must still eventually destroy the tree. It is not destroyed + * automatically when an error is flagged. It is safe to destroy the + * tree within this error callback, but you will either need to perform + * a non-local jump, or store something in your context to identify + * that the tree is destroyed since any future accesses to it cause + * undefined behavior. + */ +typedef void (*mpack_tree_error_t)(mpack_tree_t* tree, mpack_error_t error); + +/** + * The MPack tree's read function. It should fill the buffer with as many bytes + * as are immediately available up to the given @c count, returning the number + * of bytes written to the buffer. + * + * In case of error, it should flag an appropriate error on the reader + * (usually @ref mpack_error_io.) + * + * The blocking or non-blocking behaviour of the read should match whether you + * are using mpack_tree_parse() or mpack_tree_try_parse(). + * + * If you are using mpack_tree_parse(), the read should block until at least + * one byte is read. If you return 0, mpack_tree_parse() will raise @ref + * mpack_error_io. + * + * If you are using mpack_tree_try_parse(), the read function can always + * return 0, and must never block waiting for data (otherwise + * mpack_tree_try_parse() would be equivalent to mpack_tree_parse().) + * When you return 0, mpack_tree_try_parse() will return false without flagging + * an error. + */ +typedef size_t (*mpack_tree_read_t)(mpack_tree_t* tree, char* buffer, size_t count); + +/** + * A teardown function to be called when the tree is destroyed. + */ +typedef void (*mpack_tree_teardown_t)(mpack_tree_t* tree); + + + +/* Hide internals from documentation */ +/** @cond */ + +struct mpack_node_t { + mpack_node_data_t* data; + mpack_tree_t* tree; +}; + +struct mpack_node_data_t { + mpack_type_t type; + + /* + * The element count if the type is an array; + * the number of key/value pairs if the type is map; + * or the number of bytes if the type is str, bin or ext. + */ + uint32_t len; + + union { + bool b; /* The value if the type is bool. */ + + #if MPACK_FLOAT + float f; /* The value if the type is float. */ + #else + uint32_t f; /*< The raw value if the type is float. */ + #endif + + #if MPACK_DOUBLE + double d; /* The value if the type is double. */ + #else + uint64_t d; /*< The raw value if the type is double. */ + #endif + + int64_t i; /* The value if the type is signed int. */ + uint64_t u; /* The value if the type is unsigned int. */ + size_t offset; /* The byte offset for str, bin and ext */ + + mpack_node_data_t* children; /* The children for map or array */ + } value; +}; + +typedef struct mpack_tree_page_t { + struct mpack_tree_page_t* next; + mpack_node_data_t nodes[1]; // variable size +} mpack_tree_page_t; + +typedef enum mpack_tree_parse_state_t { + mpack_tree_parse_state_not_started, + mpack_tree_parse_state_in_progress, + mpack_tree_parse_state_parsed, +} mpack_tree_parse_state_t; + +typedef struct mpack_level_t { + mpack_node_data_t* child; + size_t left; // children left in level +} mpack_level_t; + +typedef struct mpack_tree_parser_t { + mpack_tree_parse_state_t state; + + // We keep track of the number of "possible nodes" left in the data rather + // than the number of bytes. + // + // When a map or array is parsed, we ensure at least one byte for each child + // exists and subtract them right away. This ensures that if ever a map or + // array declares more elements than could possibly be contained in the data, + // we will error out immediately rather than allocating storage for them. + // + // For example malicious data that repeats 0xDE 0xFF 0xFF (start of a map + // with 65536 key-value pairs) would otherwise cause us to run out of + // memory. With this, the parser can allocate at most as many nodes as + // there are bytes in the data (plus the paging overhead, 12%.) An error + // will be flagged immediately if and when there isn't enough data left to + // fully read all children of all open compound types on the parsing stack. + // + // Once an entire message has been parsed (and there are no nodes left to + // parse whose bytes have been subtracted), this matches the number of left + // over bytes in the data. + size_t possible_nodes_left; + + mpack_node_data_t* nodes; // next node in current page/pool + size_t nodes_left; // nodes left in current page/pool + + size_t current_node_reserved; + size_t level; + + #ifdef MPACK_MALLOC + // It's much faster to allocate the initial parsing stack inline within the + // parser. We replace it with a heap allocation if we need to grow it. + mpack_level_t* stack; + size_t stack_capacity; + bool stack_owned; + mpack_level_t stack_local[MPACK_NODE_INITIAL_DEPTH]; + #else + // Without malloc(), we have to reserve a parsing stack the maximum allowed + // parsing depth. + mpack_level_t stack[MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC]; + #endif +} mpack_tree_parser_t; + +struct mpack_tree_t { + mpack_tree_error_t error_fn; /* Function to call on error */ + mpack_tree_read_t read_fn; /* Function to call to read more data */ + mpack_tree_teardown_t teardown; /* Function to teardown the context on destroy */ + void* context; /* Context for tree callbacks */ + + mpack_node_data_t nil_node; /* a nil node to be returned in case of error */ + mpack_node_data_t missing_node; /* a missing node to be returned in optional lookups */ + mpack_error_t error; + + #ifdef MPACK_MALLOC + char* buffer; + size_t buffer_capacity; + #endif + + const char* data; + size_t data_length; // length of data (and content of buffer, if used) + + size_t size; // size in bytes of tree (usually matches data_length, but not if tree has trailing data) + size_t node_count; // total number of nodes in tree (across all pages) + + size_t max_size; // maximum message size + size_t max_nodes; // maximum nodes in a message + + mpack_tree_parser_t parser; + mpack_node_data_t* root; + + mpack_node_data_t* pool; // pool, or NULL if no pool provided + size_t pool_count; + + #ifdef MPACK_MALLOC + mpack_tree_page_t* next; + #endif +}; + +// internal functions + +MPACK_INLINE mpack_node_t mpack_node(mpack_tree_t* tree, mpack_node_data_t* data) { + mpack_node_t node; + node.data = data; + node.tree = tree; + return node; +} + +MPACK_INLINE mpack_node_data_t* mpack_node_child(mpack_node_t node, size_t child) { + return node.data->value.children + child; +} + +MPACK_INLINE mpack_node_t mpack_tree_nil_node(mpack_tree_t* tree) { + return mpack_node(tree, &tree->nil_node); +} + +MPACK_INLINE mpack_node_t mpack_tree_missing_node(mpack_tree_t* tree) { + return mpack_node(tree, &tree->missing_node); +} + +/** @endcond */ + + + +/** + * @name Tree Initialization + * @{ + */ + +#ifdef MPACK_MALLOC +/** + * Initializes a tree parser with the given data. + * + * Configure the tree if desired, then call mpack_tree_parse() to parse it. The + * tree will allocate pages of nodes as needed and will free them when + * destroyed. + * + * The tree must be destroyed with mpack_tree_destroy(). + * + * Any string or blob data types reference the original data, so the given data + * pointer must remain valid until after the tree is destroyed. + */ +void mpack_tree_init_data(mpack_tree_t* tree, const char* data, size_t length); + +/** + * Deprecated. + * + * \deprecated Renamed to mpack_tree_init_data(). + */ +MPACK_INLINE void mpack_tree_init(mpack_tree_t* tree, const char* data, size_t length) { + mpack_tree_init_data(tree, data, length); +} + +/** + * Initializes a tree parser from an unbounded stream, or a stream of + * unknown length. + * + * The parser can be used to read a single message from a stream of unknown + * length, or multiple messages from an unbounded stream, allowing it to + * be used for RPC communication. Call @ref mpack_tree_parse() to parse + * a message from a blocking stream, or @ref mpack_tree_try_parse() for a + * non-blocking stream. + * + * The stream will use a growable internal buffer to store the most recent + * message, as well as allocated pages of nodes for the parse tree. + * + * Maximum allowances for message size and node count must be specified in this + * function (since the stream is unbounded.) They can be changed later with + * @ref mpack_tree_set_limits(). + * + * @param tree The tree parser + * @param read_fn The read function + * @param context The context for the read function + * @param max_message_size The maximum size of a message in bytes + * @param max_message_nodes The maximum number of nodes per message. See + * @ref mpack_node_data_t for the size of nodes. + * + * @see mpack_tree_read_t + * @see mpack_reader_context() + */ +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); +#endif + +/** + * Initializes a tree parser with the given data, using the given node data + * pool to store the results. + * + * Configure the tree if desired, then call mpack_tree_parse() to parse it. + * + * If the data does not fit in the pool, @ref mpack_error_too_big will be flagged + * on the tree. + * + * The tree must be destroyed with mpack_tree_destroy(), even if parsing fails. + */ +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); + +/** + * Initializes an MPack tree directly into an error state. Use this if you + * are writing a wrapper to another mpack_tree_init*() function which + * can fail its setup. + */ +void mpack_tree_init_error(mpack_tree_t* tree, mpack_error_t error); + +#if MPACK_STDIO +/** + * Initializes a tree to parse the given file. The tree must be destroyed with + * mpack_tree_destroy(), even if parsing fails. + * + * The file is opened, loaded fully into memory, and closed before this call + * returns. + * + * @param tree The tree to initialize + * @param filename The filename passed to fopen() to read the file + * @param max_bytes The maximum size of file to load, or 0 for unlimited size. + */ +void mpack_tree_init_filename(mpack_tree_t* tree, const char* filename, size_t max_bytes); + +/** + * Deprecated. + * + * \deprecated Renamed to mpack_tree_init_filename(). + */ +MPACK_INLINE void mpack_tree_init_file(mpack_tree_t* tree, const char* filename, size_t max_bytes) { + mpack_tree_init_filename(tree, filename, max_bytes); +} + +/** + * Initializes a tree to parse the given libc FILE. This can be used to + * read from stdin, or from a file opened separately. + * + * The tree must be destroyed with mpack_tree_destroy(), even if parsing fails. + * + * The FILE is fully loaded fully into memory (and closed if requested) before + * this call returns. + * + * @param tree The tree to initialize. + * @param stdfile The FILE. + * @param max_bytes The maximum size of file to load, or 0 for unlimited size. + * @param close_when_done If true, fclose() will be called on the FILE when it + * is no longer needed. If false, the file will not be closed when + * reading is done. + * + * @warning The tree will read all data in the FILE before parsing it. If this + * is used on stdin, the parser will block until it is closed, even if + * a complete message has been written to it! + */ +void mpack_tree_init_stdfile(mpack_tree_t* tree, FILE* stdfile, size_t max_bytes, bool close_when_done); +#endif + +/** + * @} + */ + +/** + * @name Tree Functions + * @{ + */ + +/** + * Sets the maximum byte size and maximum number of nodes allowed per message. + * + * The default is SIZE_MAX (no limit) unless @ref mpack_tree_init_stream() is + * called (where maximums are required.) + * + * If a pool of nodes is used, the node limit is the lesser of this limit and + * the pool size. + * + * @param tree The tree parser + * @param max_message_size The maximum size of a message in bytes + * @param max_message_nodes The maximum number of nodes per message. See + * @ref mpack_node_data_t for the size of nodes. + */ +void mpack_tree_set_limits(mpack_tree_t* tree, size_t max_message_size, + size_t max_message_nodes); + +/** + * Parses a MessagePack message into a tree of immutable nodes. + * + * If successful, the root node will be available under @ref mpack_tree_root(). + * If not, an appropriate error will be flagged. + * + * This can be called repeatedly to parse a series of messages from a data + * source. When this is called, all previous nodes from this tree and their + * contents (including the root node) are invalidated. + * + * If this is called with a stream (see @ref mpack_tree_init_stream()), the + * stream must block until data is available. (Otherwise, if this is called on + * a non-blocking stream, parsing will fail with @ref mpack_error_io when the + * fill function returns 0.) + * + * There is no way to recover a tree in an error state. It must be destroyed. + */ +void mpack_tree_parse(mpack_tree_t* tree); + +/** + * Attempts to parse a MessagePack message from a non-blocking stream into a + * tree of immutable nodes. + * + * A non-blocking read function must have been passed to the tree in + * mpack_tree_init_stream(). + * + * If this returns true, a message is available under + * @ref mpack_tree_root(). The tree nodes and data will be valid until + * the next time a parse is started. + * + * If this returns false, no message is available, because either not enough + * data is available yet or an error has occurred. You must check the tree for + * errors whenever this returns false. If there is no error, you should try + * again later when more data is available. (You will want to select()/poll() + * on the underlying socket or use some other asynchronous mechanism to + * determine when it has data.) + * + * There is no way to recover a tree in an error state. It must be destroyed. + * + * @see mpack_tree_init_stream() + */ +bool mpack_tree_try_parse(mpack_tree_t* tree); + +/** + * Returns the root node of the tree, if the tree is not in an error state. + * Returns a nil node otherwise. + * + * @warning You must call mpack_tree_parse() before calling this. If + * @ref mpack_tree_parse() was never called, the tree will assert. + */ +mpack_node_t mpack_tree_root(mpack_tree_t* tree); + +/** + * Returns the error state of the tree. + */ +MPACK_INLINE mpack_error_t mpack_tree_error(mpack_tree_t* tree) { + return tree->error; +} + +/** + * Returns the size in bytes of the current parsed message. + * + * If there is something in the buffer after the MessagePack object, this can + * be used to find it. + * + * This is zero if an error occurred during tree parsing (since the + * portion of the data that the first complete object occupies cannot + * be determined if the data is invalid or corrupted.) + */ +MPACK_INLINE size_t mpack_tree_size(mpack_tree_t* tree) { + return tree->size; +} + +/** + * Destroys the tree. + */ +mpack_error_t mpack_tree_destroy(mpack_tree_t* tree); + +/** + * Sets the custom pointer to pass to the tree callbacks, such as teardown. + * + * @param tree The MPack tree. + * @param context User data to pass to the tree callbacks. + * + * @see mpack_reader_context() + */ +MPACK_INLINE void mpack_tree_set_context(mpack_tree_t* tree, void* context) { + tree->context = context; +} + +/** + * Returns the custom context for tree callbacks. + * + * @see mpack_tree_set_context + * @see mpack_tree_init_stream + */ +MPACK_INLINE void* mpack_tree_context(mpack_tree_t* tree) { + return tree->context; +} + +/** + * Sets the error function to call when an error is flagged on the tree. + * + * This should normally be used with mpack_tree_set_context() to register + * a custom pointer to pass to the error function. + * + * See the definition of mpack_tree_error_t for more information about + * what you can do from an error callback. + * + * @see mpack_tree_error_t + * @param tree The MPack tree. + * @param error_fn The function to call when an error is flagged on the tree. + */ +MPACK_INLINE void mpack_tree_set_error_handler(mpack_tree_t* tree, mpack_tree_error_t error_fn) { + tree->error_fn = error_fn; +} + +/** + * Sets the teardown function to call when the tree is destroyed. + * + * This should normally be used with mpack_tree_set_context() to register + * a custom pointer to pass to the teardown function. + * + * @param tree The MPack tree. + * @param teardown The function to call when the tree is destroyed. + */ +MPACK_INLINE void mpack_tree_set_teardown(mpack_tree_t* tree, mpack_tree_teardown_t teardown) { + tree->teardown = teardown; +} + +/** + * Places the tree in the given error state, calling the error callback if one + * is set. + * + * This allows you to externally flag errors, for example if you are validating + * data as you read it. + * + * If the tree is already in an error state, this call is ignored and no + * error callback is called. + */ +void mpack_tree_flag_error(mpack_tree_t* tree, mpack_error_t error); + +/** + * @} + */ + +/** + * @name Node Core Functions + * @{ + */ + +/** + * Places the node's tree in the given error state, calling the error callback + * if one is set. + * + * This allows you to externally flag errors, for example if you are validating + * data as you read it. + * + * If the tree is already in an error state, this call is ignored and no + * error callback is called. + */ +void mpack_node_flag_error(mpack_node_t node, mpack_error_t error); + +/** + * Returns the error state of the node's tree. + */ +MPACK_INLINE mpack_error_t mpack_node_error(mpack_node_t node) { + return mpack_tree_error(node.tree); +} + +/** + * Returns a tag describing the given node, or a nil tag if the + * tree is in an error state. + */ +mpack_tag_t mpack_node_tag(mpack_node_t node); + +/** @cond */ + +#if MPACK_DEBUG && MPACK_STDIO +/* + * Converts a node to a pseudo-JSON string for debugging purposes, placing the + * result in the given buffer with a null-terminator. + * + * If the buffer does not have enough space, the result will be truncated (but + * it is guaranteed to be null-terminated.) + * + * This is only available in debug mode, and only if stdio is available (since + * it uses snprintf().) It's strictly for debugging purposes. + */ +void mpack_node_print_to_buffer(mpack_node_t node, char* buffer, size_t buffer_size); + +/* + * Converts a node to pseudo-JSON for debugging purposes, calling the given + * callback as many times as is necessary to output the character data. + * + * No null-terminator or trailing newline will be written. + * + * This is only available in debug mode, and only if stdio is available (since + * it uses snprintf().) It's strictly for debugging purposes. + */ +void mpack_node_print_to_callback(mpack_node_t node, mpack_print_callback_t callback, void* context); + +/* + * Converts a node to pseudo-JSON for debugging purposes + * and pretty-prints it to the given file. + * + * This is only available in debug mode, and only if stdio is available (since + * it uses snprintf().) It's strictly for debugging purposes. + */ +void mpack_node_print_to_file(mpack_node_t node, FILE* file); + +/* + * Converts a node to pseudo-JSON for debugging purposes + * and pretty-prints it to stdout. + * + * This is only available in debug mode, and only if stdio is available (since + * it uses snprintf().) It's strictly for debugging purposes. + */ +MPACK_INLINE void mpack_node_print_to_stdout(mpack_node_t node) { + mpack_node_print_to_file(node, stdout); +} + +/* + * Deprecated. + * + * \deprecated Renamed to mpack_node_print_to_stdout(). + */ +MPACK_INLINE void mpack_node_print(mpack_node_t node) { + mpack_node_print_to_stdout(node); +} +#endif + +/** @endcond */ + +/** + * @} + */ + +/** + * @name Node Primitive Value Functions + * @{ + */ + +/** + * Returns the type of the node. + */ +mpack_type_t mpack_node_type(mpack_node_t node); + +/** + * Returns true if the given node is a nil node; false otherwise. + * + * To ensure that a node is nil and flag an error otherwise, use + * mpack_node_nil(). + */ +bool mpack_node_is_nil(mpack_node_t node); + +/** + * Returns true if the given node handle indicates a missing node; false otherwise. + * + * To ensure that a node is missing and flag an error otherwise, use + * mpack_node_missing(). + */ +bool mpack_node_is_missing(mpack_node_t node); + +/** + * Checks that the given node is of nil type, raising @ref mpack_error_type + * otherwise. + * + * Use mpack_node_is_nil() to return whether the node is nil. + */ +void mpack_node_nil(mpack_node_t node); + +/** + * Checks that the given node indicates a missing node, raising @ref + * mpack_error_type otherwise. + * + * Use mpack_node_is_missing() to return whether the node is missing. + */ +void mpack_node_missing(mpack_node_t node); + +/** + * Returns the bool value of the node. If this node is not of the correct + * type, false is returned and mpack_error_type is raised. + */ +bool mpack_node_bool(mpack_node_t node); + +/** + * Checks if the given node is of bool type with value true, raising + * mpack_error_type otherwise. + */ +void mpack_node_true(mpack_node_t node); + +/** + * Checks if the given node is of bool type with value false, raising + * mpack_error_type otherwise. + */ +void mpack_node_false(mpack_node_t node); + +/** + * Returns the 8-bit unsigned value of the node. If this node is not + * of a compatible type, @ref mpack_error_type is raised and zero is returned. + */ +uint8_t mpack_node_u8(mpack_node_t node); + +/** + * Returns the 8-bit signed value of the node. If this node is not + * of a compatible type, @ref mpack_error_type is raised and zero is returned. + */ +int8_t mpack_node_i8(mpack_node_t node); + +/** + * Returns the 16-bit unsigned value of the node. If this node is not + * of a compatible type, @ref mpack_error_type is raised and zero is returned. + */ +uint16_t mpack_node_u16(mpack_node_t node); + +/** + * Returns the 16-bit signed value of the node. If this node is not + * of a compatible type, @ref mpack_error_type is raised and zero is returned. + */ +int16_t mpack_node_i16(mpack_node_t node); + +/** + * Returns the 32-bit unsigned value of the node. If this node is not + * of a compatible type, @ref mpack_error_type is raised and zero is returned. + */ +uint32_t mpack_node_u32(mpack_node_t node); + +/** + * Returns the 32-bit signed value of the node. If this node is not + * of a compatible type, @ref mpack_error_type is raised and zero is returned. + */ +int32_t mpack_node_i32(mpack_node_t node); + +/** + * Returns the 64-bit unsigned value of the node. If this node is not + * of a compatible type, @ref mpack_error_type is raised, and zero is returned. + */ +uint64_t mpack_node_u64(mpack_node_t node); + +/** + * Returns the 64-bit signed value of the node. If this node is not + * of a compatible type, @ref mpack_error_type is raised and zero is returned. + */ +int64_t mpack_node_i64(mpack_node_t node); + +/** + * Returns the unsigned int value of the node. + * + * Returns zero if an error occurs. + * + * @throws mpack_error_type If the node is not an integer type or does not fit in the range of an unsigned int + */ +unsigned int mpack_node_uint(mpack_node_t node); + +/** + * Returns the int value of the node. + * + * Returns zero if an error occurs. + * + * @throws mpack_error_type If the node is not an integer type or does not fit in the range of an int + */ +int mpack_node_int(mpack_node_t node); + +#if MPACK_FLOAT +/** + * Returns the float value of the node. The underlying value can be an + * integer, float or double; the value is converted to a float. + * + * @note Reading a double or a large integer with this function can incur a + * loss of precision. + * + * @throws mpack_error_type if the underlying value is not a float, double or integer. + */ +float mpack_node_float(mpack_node_t node); +#endif + +#if MPACK_DOUBLE +/** + * Returns the double value of the node. The underlying value can be an + * integer, float or double; the value is converted to a double. + * + * @note Reading a very large integer with this function can incur a + * loss of precision. + * + * @throws mpack_error_type if the underlying value is not a float, double or integer. + */ +double mpack_node_double(mpack_node_t node); +#endif + +#if MPACK_FLOAT +/** + * Returns the float value of the node. The underlying value must be a float, + * not a double or an integer. This ensures no loss of precision can occur. + * + * @throws mpack_error_type if the underlying value is not a float. + */ +float mpack_node_float_strict(mpack_node_t node); +#endif + +#if MPACK_DOUBLE +/** + * Returns the double value of the node. The underlying value must be a float + * or double, not an integer. This ensures no loss of precision can occur. + * + * @throws mpack_error_type if the underlying value is not a float or double. + */ +double mpack_node_double_strict(mpack_node_t node); +#endif + +#if !MPACK_FLOAT +/** + * Returns the float value of the node as a raw uint32_t. The underlying value + * must be a float, not a double or an integer. + * + * @throws mpack_error_type if the underlying value is not a float. + */ +uint32_t mpack_node_raw_float(mpack_node_t node); +#endif + +#if !MPACK_DOUBLE +/** + * Returns the double value of the node as a raw uint64_t. The underlying value + * must be a double, not a float or an integer. + * + * @throws mpack_error_type if the underlying value is not a float or double. + */ +uint64_t mpack_node_raw_double(mpack_node_t node); +#endif + + +#if MPACK_EXTENSIONS +/** + * Returns a timestamp. + * + * @note This requires @ref MPACK_EXTENSIONS. + * + * @throws mpack_error_type if the underlying value is not a timestamp. + */ +mpack_timestamp_t mpack_node_timestamp(mpack_node_t node); + +/** + * Returns a timestamp's (signed) seconds since 1970-01-01T00:00:00Z. + * + * @note This requires @ref MPACK_EXTENSIONS. + * + * @throws mpack_error_type if the underlying value is not a timestamp. + */ +int64_t mpack_node_timestamp_seconds(mpack_node_t node); + +/** + * Returns a timestamp's additional nanoseconds. + * + * @note This requires @ref MPACK_EXTENSIONS. + * + * @return A nanosecond count between 0 and 999,999,999 inclusive. + * @throws mpack_error_type if the underlying value is not a timestamp. + */ +uint32_t mpack_node_timestamp_nanoseconds(mpack_node_t node); +#endif + +/** + * @} + */ + +/** + * @name Node String and Data Functions + * @{ + */ + +/** + * Checks that the given node contains a valid UTF-8 string. + * + * If the string is invalid, this flags an error, which would cause subsequent calls + * to mpack_node_str() to return NULL and mpack_node_strlen() to return zero. So you + * can check the node for error immediately after calling this, or you can call those + * functions to use the data anyway and check for errors later. + * + * @throws mpack_error_type If this node is not a string or does not contain valid UTF-8. + * + * @param node The string node to test + * + * @see mpack_node_str() + * @see mpack_node_strlen() + */ +void mpack_node_check_utf8(mpack_node_t node); + +/** + * Checks that the given node contains a valid UTF-8 string with no NUL bytes. + * + * This does not check that the string has a null-terminator! It only checks whether + * the string could safely be represented as a C-string by appending a null-terminator. + * (If the string does already contain a null-terminator, this will flag an error.) + * + * This is performed automatically by other UTF-8 cstr helper functions. Only + * call this if you will do something else with the data directly, but you still + * want to ensure it will be valid as a UTF-8 C-string. + * + * @throws mpack_error_type If this node is not a string, does not contain valid UTF-8, + * or contains a NUL byte. + * + * @param node The string node to test + * + * @see mpack_node_str() + * @see mpack_node_strlen() + * @see mpack_node_copy_utf8_cstr() + * @see mpack_node_utf8_cstr_alloc() + */ +void mpack_node_check_utf8_cstr(mpack_node_t node); + +#if MPACK_EXTENSIONS +/** + * Returns the extension type of the given ext node. + * + * This returns zero if the tree is in an error state. + * + * @note This requires @ref MPACK_EXTENSIONS. + */ +int8_t mpack_node_exttype(mpack_node_t node); +#endif + +/** + * Returns the number of bytes in the given bin node. + * + * This returns zero if the tree is in an error state. + * + * If this node is not a bin, @ref mpack_error_type is raised and zero is returned. + */ +size_t mpack_node_bin_size(mpack_node_t node); + +/** + * Returns the length of the given str, bin or ext node. + * + * This returns zero if the tree is in an error state. + * + * If this node is not a str, bin or ext, @ref mpack_error_type is raised and zero + * is returned. + */ +uint32_t mpack_node_data_len(mpack_node_t node); + +/** + * Returns the length in bytes of the given string node. This does not + * include any null-terminator. + * + * This returns zero if the tree is in an error state. + * + * If this node is not a str, @ref mpack_error_type is raised and zero is returned. + */ +size_t mpack_node_strlen(mpack_node_t node); + +/** + * Returns a pointer to the data contained by this node, ensuring the node is a + * string. + * + * @warning Strings are not null-terminated! Use one of the cstr functions + * to get a null-terminated string. + * + * The pointer is valid as long as the data backing the tree is valid. + * + * If this node is not a string, @ref mpack_error_type is raised and @c NULL is returned. + * + * @see mpack_node_copy_cstr() + * @see mpack_node_cstr_alloc() + * @see mpack_node_utf8_cstr_alloc() + */ +const char* mpack_node_str(mpack_node_t node); + +/** + * Returns a pointer to the data contained by this node. + * + * @note Strings are not null-terminated! Use one of the cstr functions + * to get a null-terminated string. + * + * The pointer is valid as long as the data backing the tree is valid. + * + * If this node is not of a str, bin or ext, @ref mpack_error_type is raised, and + * @c NULL is returned. + * + * @see mpack_node_copy_cstr() + * @see mpack_node_cstr_alloc() + * @see mpack_node_utf8_cstr_alloc() + */ +const char* mpack_node_data(mpack_node_t node); + +/** + * Returns a pointer to the data contained by this bin node. + * + * The pointer is valid as long as the data backing the tree is valid. + * + * If this node is not a bin, @ref mpack_error_type is raised and @c NULL is + * returned. + */ +const char* mpack_node_bin_data(mpack_node_t node); + +/** + * Copies the bytes contained by this node into the given buffer, returning the + * number of bytes in the node. + * + * @throws mpack_error_type If this node is not a str, bin or ext type + * @throws mpack_error_too_big If the string does not fit in the given buffer + * + * @param node The string node from which to copy data + * @param buffer A buffer in which to copy the node's bytes + * @param bufsize The size of the given buffer + * + * @return The number of bytes in the node, or zero if an error occurs. + */ +size_t mpack_node_copy_data(mpack_node_t node, char* buffer, size_t bufsize); + +/** + * Checks that the given node contains a valid UTF-8 string and copies the + * string into the given buffer, returning the number of bytes in the string. + * + * @throws mpack_error_type If this node is not a string + * @throws mpack_error_too_big If the string does not fit in the given buffer + * + * @param node The string node from which to copy data + * @param buffer A buffer in which to copy the node's bytes + * @param bufsize The size of the given buffer + * + * @return The number of bytes in the node, or zero if an error occurs. + */ +size_t mpack_node_copy_utf8(mpack_node_t node, char* buffer, size_t bufsize); + +/** + * Checks that the given node contains a string with no NUL bytes, copies the string + * into the given buffer, and adds a null terminator. + * + * If this node is not of a string type, @ref mpack_error_type is raised. If the string + * does not fit, @ref mpack_error_data is raised. + * + * If any error occurs, the buffer will contain an empty null-terminated string. + * + * @param node The string node from which to copy data + * @param buffer A buffer in which to copy the node's string + * @param size The size of the given buffer + */ +void mpack_node_copy_cstr(mpack_node_t node, char* buffer, size_t size); + +/** + * Checks that the given node contains a valid UTF-8 string with no NUL bytes, + * copies the string into the given buffer, and adds a null terminator. + * + * If this node is not of a string type, @ref mpack_error_type is raised. If the string + * does not fit, @ref mpack_error_data is raised. + * + * If any error occurs, the buffer will contain an empty null-terminated string. + * + * @param node The string node from which to copy data + * @param buffer A buffer in which to copy the node's string + * @param size The size of the given buffer + */ +void mpack_node_copy_utf8_cstr(mpack_node_t node, char* buffer, size_t size); + +#ifdef MPACK_MALLOC +/** + * Allocates a new chunk of data using MPACK_MALLOC() with the bytes + * contained by this node. + * + * The allocated data must be freed with MPACK_FREE() (or simply free() + * if MPack's allocator hasn't been customized.) + * + * @throws mpack_error_type If this node is not a str, bin or ext type + * @throws mpack_error_too_big If the size of the data is larger than the + * given maximum size + * @throws mpack_error_memory If an allocation failure occurs + * + * @param node The node from which to allocate and copy data + * @param maxsize The maximum size to allocate + * + * @return The allocated data, or NULL if any error occurs. + */ +char* mpack_node_data_alloc(mpack_node_t node, size_t maxsize); + +/** + * Allocates a new null-terminated string using MPACK_MALLOC() with the string + * contained by this node. + * + * The allocated string must be freed with MPACK_FREE() (or simply free() + * if MPack's allocator hasn't been customized.) + * + * @throws mpack_error_type If this node is not a string or contains NUL bytes + * @throws mpack_error_too_big If the size of the string plus null-terminator + * is larger than the given maximum size + * @throws mpack_error_memory If an allocation failure occurs + * + * @param node The node from which to allocate and copy string data + * @param maxsize The maximum size to allocate, including the null-terminator + * + * @return The allocated string, or NULL if any error occurs. + */ +char* mpack_node_cstr_alloc(mpack_node_t node, size_t maxsize); + +/** + * Allocates a new null-terminated string using MPACK_MALLOC() with the UTF-8 + * string contained by this node. + * + * The allocated string must be freed with MPACK_FREE() (or simply free() + * if MPack's allocator hasn't been customized.) + * + * @throws mpack_error_type If this node is not a string, is not valid UTF-8, + * or contains NUL bytes + * @throws mpack_error_too_big If the size of the string plus null-terminator + * is larger than the given maximum size + * @throws mpack_error_memory If an allocation failure occurs + * + * @param node The node from which to allocate and copy string data + * @param maxsize The maximum size to allocate, including the null-terminator + * + * @return The allocated string, or NULL if any error occurs. + */ +char* mpack_node_utf8_cstr_alloc(mpack_node_t node, size_t maxsize); +#endif + +/** + * Searches the given string array for a string matching the given + * node and returns its index. + * + * If the node does not match any of the given strings, + * @ref mpack_error_type is flagged. Use mpack_node_enum_optional() + * if you want to allow values other than the given strings. + * + * If any error occurs or if the tree is in an error state, @a count + * is returned. + * + * This can be used to quickly parse a string into an enum when the + * enum values range from 0 to @a count-1. If the last value in the + * enum is a special "count" value, it can be passed as the count, + * and the return value can be cast directly to the enum type. + * + * @code{.c} + * typedef enum { APPLE , BANANA , ORANGE , COUNT} fruit_t; + * const char* fruits[] = {"apple", "banana", "orange"}; + * + * fruit_t fruit = (fruit_t)mpack_node_enum(node, fruits, COUNT); + * @endcode + * + * @param node The node + * @param strings An array of expected strings of length count + * @param count The number of strings + * @return The index of the matched string, or @a count in case of error + */ +size_t mpack_node_enum(mpack_node_t node, const char* strings[], size_t count); + +/** + * Searches the given string array for a string matching the given node, + * returning its index or @a count if no strings match. + * + * If the value is not a string, or it does not match any of the + * given strings, @a count is returned and no error is flagged. + * + * If any error occurs or if the tree is in an error state, @a count + * is returned. + * + * This can be used to quickly parse a string into an enum when the + * enum values range from 0 to @a count-1. If the last value in the + * enum is a special "count" value, it can be passed as the count, + * and the return value can be cast directly to the enum type. + * + * @code{.c} + * typedef enum { APPLE , BANANA , ORANGE , COUNT} fruit_t; + * const char* fruits[] = {"apple", "banana", "orange"}; + * + * fruit_t fruit = (fruit_t)mpack_node_enum_optional(node, fruits, COUNT); + * @endcode + * + * @param node The node + * @param strings An array of expected strings of length count + * @param count The number of strings + * @return The index of the matched string, or @a count in case of error + */ +size_t mpack_node_enum_optional(mpack_node_t node, const char* strings[], size_t count); + +/** + * @} + */ + +/** + * @name Compound Node Functions + * @{ + */ + +/** + * Returns the length of the given array node. Raises mpack_error_type + * and returns 0 if the given node is not an array. + */ +size_t mpack_node_array_length(mpack_node_t node); + +/** + * Returns the node in the given array at the given index. If the node + * is not an array, @ref mpack_error_type is raised and a nil node is returned. + * If the given index is out of bounds, @ref mpack_error_data is raised and + * a nil node is returned. + */ +mpack_node_t mpack_node_array_at(mpack_node_t node, size_t index); + +/** + * Returns the number of key/value pairs in the given map node. Raises + * mpack_error_type and returns 0 if the given node is not a map. + */ +size_t mpack_node_map_count(mpack_node_t node); + +/** + * Returns the key node in the given map at the given index. + * + * A nil node is returned in case of error. + * + * @throws mpack_error_type if the node is not a map + * @throws mpack_error_data if the given index is out of bounds + */ +mpack_node_t mpack_node_map_key_at(mpack_node_t node, size_t index); + +/** + * Returns the value node in the given map at the given index. + * + * A nil node is returned in case of error. + * + * @throws mpack_error_type if the node is not a map + * @throws mpack_error_data if the given index is out of bounds + */ +mpack_node_t mpack_node_map_value_at(mpack_node_t node, size_t index); + +/** + * Returns the value node in the given map for the given integer key. + * + * The key must exist within the map. Use mpack_node_map_int_optional() to + * check for optional keys. + * + * The key must be unique. An error is flagged if the node has multiple + * entries with the given key. + * + * @throws mpack_error_type If the node is not a map + * @throws mpack_error_data If the node does not contain exactly one entry with the given key + * + * @return The value node for the given key, or a nil node in case of error + */ +mpack_node_t mpack_node_map_int(mpack_node_t node, int64_t num); + +/** + * Returns the value node in the given map for the given integer key, or a + * missing node if the map does not contain the given key. + * + * The key must be unique. An error is flagged if the node has multiple + * entries with the given key. + * + * @throws mpack_error_type If the node is not a map + * @throws mpack_error_data If the node contains more than one entry with the given key + * + * @return The value node for the given key, or a missing node if the key does + * not exist, or a nil node in case of error + * + * @see mpack_node_is_missing() + */ +mpack_node_t mpack_node_map_int_optional(mpack_node_t node, int64_t num); + +/** + * Returns the value node in the given map for the given unsigned integer key. + * + * The key must exist within the map. Use mpack_node_map_uint_optional() to + * check for optional keys. + * + * The key must be unique. An error is flagged if the node has multiple + * entries with the given key. + * + * @throws mpack_error_type If the node is not a map + * @throws mpack_error_data If the node does not contain exactly one entry with the given key + * + * @return The value node for the given key, or a nil node in case of error + */ +mpack_node_t mpack_node_map_uint(mpack_node_t node, uint64_t num); + +/** + * Returns the value node in the given map for the given unsigned integer + * key, or a missing node if the map does not contain the given key. + * + * The key must be unique. An error is flagged if the node has multiple + * entries with the given key. + * + * @throws mpack_error_type If the node is not a map + * @throws mpack_error_data If the node contains more than one entry with the given key + * + * @return The value node for the given key, or a missing node if the key does + * not exist, or a nil node in case of error + * + * @see mpack_node_is_missing() + */ +mpack_node_t mpack_node_map_uint_optional(mpack_node_t node, uint64_t num); + +/** + * Returns the value node in the given map for the given string key. + * + * The key must exist within the map. Use mpack_node_map_str_optional() to + * check for optional keys. + * + * The key must be unique. An error is flagged if the node has multiple + * entries with the given key. + * + * @throws mpack_error_type If the node is not a map + * @throws mpack_error_data If the node does not contain exactly one entry with the given key + * + * @return The value node for the given key, or a nil node in case of error + */ +mpack_node_t mpack_node_map_str(mpack_node_t node, const char* str, size_t length); + +/** + * Returns the value node in the given map for the given string key, or a missing + * node if the map does not contain the given key. + * + * The key must be unique. An error is flagged if the node has multiple + * entries with the given key. + * + * @throws mpack_error_type If the node is not a map + * @throws mpack_error_data If the node contains more than one entry with the given key + * + * @return The value node for the given key, or a missing node if the key does + * not exist, or a nil node in case of error + * + * @see mpack_node_is_missing() + */ +mpack_node_t mpack_node_map_str_optional(mpack_node_t node, const char* str, size_t length); + +/** + * Returns the value node in the given map for the given null-terminated + * string key. + * + * The key must exist within the map. Use mpack_node_map_cstr_optional() to + * check for optional keys. + * + * The key must be unique. An error is flagged if the node has multiple + * entries with the given key. + * + * @throws mpack_error_type If the node is not a map + * @throws mpack_error_data If the node does not contain exactly one entry with the given key + * + * @return The value node for the given key, or a nil node in case of error + */ +mpack_node_t mpack_node_map_cstr(mpack_node_t node, const char* cstr); + +/** + * Returns the value node in the given map for the given null-terminated + * string key, or a missing node if the map does not contain the given key. + * + * The key must be unique. An error is flagged if the node has multiple + * entries with the given key. + * + * @throws mpack_error_type If the node is not a map + * @throws mpack_error_data If the node contains more than one entry with the given key + * + * @return The value node for the given key, or a missing node if the key does + * not exist, or a nil node in case of error + * + * @see mpack_node_is_missing() + */ +mpack_node_t mpack_node_map_cstr_optional(mpack_node_t node, const char* cstr); + +/** + * Returns true if the given node map contains exactly one entry with the + * given integer key. + * + * The key must be unique. An error is flagged if the node has multiple + * entries with the given key. + * + * @throws mpack_error_type If the node is not a map + * @throws mpack_error_data If the node contains more than one entry with the given key + */ +bool mpack_node_map_contains_int(mpack_node_t node, int64_t num); + +/** + * Returns true if the given node map contains exactly one entry with the + * given unsigned integer key. + * + * The key must be unique. An error is flagged if the node has multiple + * entries with the given key. + * + * @throws mpack_error_type If the node is not a map + * @throws mpack_error_data If the node contains more than one entry with the given key + */ +bool mpack_node_map_contains_uint(mpack_node_t node, uint64_t num); + +/** + * Returns true if the given node map contains exactly one entry with the + * given string key. + * + * The key must be unique. An error is flagged if the node has multiple + * entries with the given key. + * + * @throws mpack_error_type If the node is not a map + * @throws mpack_error_data If the node contains more than one entry with the given key + */ +bool mpack_node_map_contains_str(mpack_node_t node, const char* str, size_t length); + +/** + * Returns true if the given node map contains exactly one entry with the + * given null-terminated string key. + * + * The key must be unique. An error is flagged if the node has multiple + * entries with the given key. + * + * @throws mpack_error_type If the node is not a map + * @throws mpack_error_data If the node contains more than one entry with the given key + */ +bool mpack_node_map_contains_cstr(mpack_node_t node, const char* cstr); + +/** + * @} + */ + +/** + * @} + */ + +#endif + +MPACK_EXTERN_C_END +MPACK_SILENCE_WARNINGS_END + +#endif + + +#endif + diff --git a/deps/nmx_pool/CMakeLists.txt b/deps/nmx_pool/CMakeLists.txt index 757711b..cb6af19 100644 --- a/deps/nmx_pool/CMakeLists.txt +++ b/deps/nmx_pool/CMakeLists.txt @@ -1,2 +1 @@ -add_definitions(-fPIC) -add_library(nmx_pool STATIC nmx_alloc.c nmx_palloc.c) \ No newline at end of file +add_library(nmx_pool STATIC mempool.c nmx_palloc.c) \ No newline at end of file diff --git a/deps/nmx_pool/mempool.c b/deps/nmx_pool/mempool.c new file mode 100644 index 0000000..d87c651 --- /dev/null +++ b/deps/nmx_pool/mempool.c @@ -0,0 +1,66 @@ +#include "mempool.h" +#include "nmx_palloc.h" + +typedef struct mem_block { + size_t size; + struct mem_block *next; +} mem_block_t; + +struct mem_pool_s{ + mem_block_t *free_list; + nmx_pool_t *pool; + size_t pool_size; +} ; + +mem_pool_t *create_mem_pool(size_t pool_size) { + mem_pool_t *mem_pool = (mem_pool_t *)malloc(sizeof(mem_pool_t)); + mem_pool->pool = nmx_create_pool(pool_size); + mem_pool->free_list = NULL; + mem_pool->pool_size = pool_size; + return mem_pool; +} + +void *mem_alloc(mem_pool_t *mem_pool, size_t size) { + mem_block_t *prev = NULL; + mem_block_t *current = mem_pool->free_list; + + // find free block + while (current != NULL) { + if (current->size >= size) { + if (prev != NULL) { + prev->next = current->next; + } else { + mem_pool->free_list = current->next; + } + return (void *)(current + 1); + } + prev = current; + current = current->next; + } + + // no suitable free block, allocate new block + mem_block_t *new_block = (mem_block_t *)nmx_palloc(mem_pool->pool, sizeof(mem_block_t) + size); + if (new_block == NULL) { + return NULL; + } + new_block->size = size; + return (void *)(new_block + 1); +} + +void mem_free(mem_pool_t *mem_pool, void *ptr) { + if (ptr == NULL) { + return; + } + //try free lagre block + if(nmx_pfree(mem_pool->pool, ptr)) { + return; + } + mem_block_t *block = (mem_block_t *)ptr - 1; + block->next = mem_pool->free_list; + mem_pool->free_list = block; +} + +void destroy_mem_pool(mem_pool_t *mem_pool) { + nmx_destroy_pool(mem_pool->pool); + free(mem_pool); +} \ No newline at end of file diff --git a/deps/nmx_pool/mempool.h b/deps/nmx_pool/mempool.h new file mode 100644 index 0000000..0037fd3 --- /dev/null +++ b/deps/nmx_pool/mempool.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +struct mem_pool_s; +typedef struct mem_pool_s mem_pool_t; + +mem_pool_t *create_mem_pool(size_t pool_size); +void destroy_mem_pool(mem_pool_t *mem_pool); + +void *mem_alloc(mem_pool_t *mem_pool, size_t size); +void mem_free(mem_pool_t *mem_pool, void *ptr); \ No newline at end of file diff --git a/deps/nmx_pool/nmx_palloc.c b/deps/nmx_pool/nmx_palloc.c index 307e2f6..58693e9 100644 --- a/deps/nmx_pool/nmx_palloc.c +++ b/deps/nmx_pool/nmx_palloc.c @@ -3,6 +3,67 @@ #include #include + +typedef struct nmx_pool_large_s nmx_pool_large_t; + +struct nmx_pool_large_s { + nmx_pool_large_t *next; + void *alloc; +}; + +typedef struct { + unsigned char *last; + unsigned char *end; + nmx_pool_t *next; + unsigned int failed; +} nmx_pool_data_t; + +struct nmx_pool_s { + + nmx_pool_data_t d; + size_t max; + nmx_pool_t *current; + nmx_pool_large_t *large; +}; + +#define nmx_free free + +#define nmx_align_ptr(p, a) \ + (unsigned char *) (((unsigned long ) (p) + ((unsigned long ) a - 1)) & ~((unsigned long ) a - 1)) + +void * nmx_alloc(size_t size) +{ + void *p; + p = malloc(size); + return p; +} + + +void *nmx_calloc(size_t size) +{ + void *p; + p = nmx_alloc(size); + if (p) { + memset(p,0,size); + } + return p; +} + +void *nmx_realloc(void *p, size_t size) +{ + if(p) { + return realloc (p, size); + } + return NULL; +} + +void *nmx_memalign(size_t alignment, size_t size) +{ + void *p=NULL; + posix_memalign(&p, alignment, size); + return p; +} + size_t nmx_pagesize = 0; //#define nmx_MAX_ALLOC_FROM_POOL (nmx_pagesize - 1) @@ -11,10 +72,13 @@ size_t nmx_pagesize = 0; #define NMX_POOL_ALIGNMENT 16 + static void *nmx_palloc_block(nmx_pool_t *pool, size_t size); static void *nmx_palloc_large(nmx_pool_t *pool, size_t size); -nmx_pool_t *nmx_create_pool(size_t size) + +nmx_pool_t * +nmx_create_pool(size_t size) { nmx_pool_t *p; @@ -37,7 +101,9 @@ nmx_pool_t *nmx_create_pool(size_t size) return p; } -void nmx_destroy_pool(nmx_pool_t *pool) + +void +nmx_destroy_pool(nmx_pool_t *pool) { nmx_pool_t *p, *n; nmx_pool_large_t *l; @@ -58,7 +124,9 @@ void nmx_destroy_pool(nmx_pool_t *pool) } } -void nmx_reset_pool(nmx_pool_t *pool) + +void +nmx_reset_pool(nmx_pool_t *pool) { nmx_pool_t *p; nmx_pool_large_t *l; @@ -78,7 +146,9 @@ void nmx_reset_pool(nmx_pool_t *pool) pool->large = NULL; } -void *nmx_palloc(nmx_pool_t *pool, size_t size) + +void * +nmx_palloc(nmx_pool_t *pool, size_t size) { unsigned char *m; nmx_pool_t *p; @@ -103,15 +173,19 @@ void *nmx_palloc(nmx_pool_t *pool, size_t size) return nmx_palloc_block(pool, size); } + + return nmx_palloc_large(pool, size); } -void *nmx_pnalloc(nmx_pool_t *pool, size_t size) +void * +nmx_pnalloc(nmx_pool_t *pool, size_t size) { unsigned char *m; nmx_pool_t *p; + if (size <= pool->max) { p = pool->current; @@ -132,6 +206,7 @@ void *nmx_pnalloc(nmx_pool_t *pool, size_t size) return nmx_palloc_block(pool, size); } + return nmx_palloc_large(pool, size); } @@ -171,6 +246,7 @@ nmx_palloc_block(nmx_pool_t *pool, size_t size) return m; } + static void * nmx_palloc_large(nmx_pool_t *pool,size_t size) { @@ -209,7 +285,9 @@ nmx_palloc_large(nmx_pool_t *pool,size_t size) return p; } -void *nmx_pmemalign(nmx_pool_t *pool, size_t size, size_t alignment) + +void * +nmx_pmemalign(nmx_pool_t *pool, size_t size, size_t alignment) { void *p; nmx_pool_large_t *large; @@ -232,7 +310,9 @@ void *nmx_pmemalign(nmx_pool_t *pool, size_t size, size_t alignment) return p; } -int nmx_pfree(nmx_pool_t *pool, void *p) + +int +nmx_pfree(nmx_pool_t *pool, void *p) { nmx_pool_large_t *l; @@ -249,7 +329,8 @@ int nmx_pfree(nmx_pool_t *pool, void *p) return 0; } -void *nmx_pcalloc(nmx_pool_t *pool, size_t size) +void * +nmx_pcalloc(nmx_pool_t *pool, size_t size) { void *p; @@ -259,4 +340,4 @@ void *nmx_pcalloc(nmx_pool_t *pool, size_t size) } return p; -} \ No newline at end of file +} diff --git a/deps/nmx_pool/nmx_palloc.h b/deps/nmx_pool/nmx_palloc.h index 0b69be1..811de78 100644 --- a/deps/nmx_pool/nmx_palloc.h +++ b/deps/nmx_pool/nmx_palloc.h @@ -1,38 +1,15 @@ -#ifndef __nmx_palloc_H_ -#define __nmx_palloc_H_ +#pragma once #include -#include "nmx_alloc.h" - -typedef struct nmx_pool_large_s nmx_pool_large_t; -typedef struct nmx_pool_s nmx_pool_t; - -struct nmx_pool_large_s { - nmx_pool_large_t *next; - void *alloc; -}; - -typedef struct { - unsigned char *last; - unsigned char *end; - nmx_pool_t *next; - unsigned int failed; -} nmx_pool_data_t; - -struct nmx_pool_s { - - nmx_pool_data_t d; - size_t max; - nmx_pool_t *current; - nmx_pool_large_t *large; -}; - /* ====================================== * ++++++++ Library Open API ++++++++++ * ====================================== */ +struct nmx_pool_s; +typedef struct nmx_pool_s nmx_pool_t; + void *nmx_alloc (size_t size); void *nmx_calloc (size_t size); @@ -55,4 +32,4 @@ void *nmx_pmemalign (nmx_pool_t *pool, size_t size, size_t alignment); int nmx_pfree (nmx_pool_t *pool, void *p); -#endif //nmx_palloc.h_H_ + diff --git a/deps/utable/CMakeLists.txt b/deps/utable/CMakeLists.txt new file mode 100644 index 0000000..f25df5c --- /dev/null +++ b/deps/utable/CMakeLists.txt @@ -0,0 +1,17 @@ +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -lm") + +add_library(utable SHARED utable.c utable_ipfix_exporter.c) +target_link_libraries(utable base64 nmx_pool cjson-static yyjson mpack) + +add_executable(ipfix_exporter_example + utable_ipfix_exporter.c + ipfix_exporter_example.cpp +) + +target_link_libraries( + ipfix_exporter_example + utable + pthread +) + +add_subdirectory(test) \ No newline at end of file diff --git a/deps/utable/ipfix_exporter_example.cpp b/deps/utable/ipfix_exporter_example.cpp new file mode 100644 index 0000000..c7811c4 --- /dev/null +++ b/deps/utable/ipfix_exporter_example.cpp @@ -0,0 +1,197 @@ + +#include +#include +#include +#include +#include +#include + +#include "utable.h" +#include "cjson/cJSON.h" + +#define THREAD_MAX 8 +#define TEMPLATE_MAX 13 +struct ipfix_template_id_list +{ + const char *template_name; + int template_id; +}; + +struct ipfix_template_id_list template_id_list[TEMPLATE_MAX] = { + {"BASE", 0}, + {"SSL", 0}, + {"HTTP", 0}, + {"MAIL", 0}, + {"DNS", 0}, + {"DTLS", 0}, + {"QUIC", 0}, + {"FTP", 0}, + {"SIP", 0}, + {"RTP", 0}, + {"SSH", 0}, + {"RDP", 0}, + {"Stratum", 0}}; +int g_udp_sock_fd = 0; +const char *ipfix_schema_json_path = NULL; +struct ipfix_exporter_schema *g_ipfix_schema = NULL; + +static int ipfix_exporter_get_socket_fd(char *collector_ip, uint16_t collector_port) +{ + int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (sock_fd <= 0) + { + return -1; + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(collector_port); + addr.sin_addr.s_addr = inet_addr(collector_ip); + + if (connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) + { + printf("connect error, illegal collector ip or port\n"); + printf("expample: ./ipfix_exporter_example 127.0.0.1 4397"); + close(sock_fd); + return -1; + } + + return sock_fd; +} + +void *ipfix_template_send_thread_loop(void *arg) +{ + int interval_s = (*(int *)arg); + while (1) + { + size_t blob_len = 0; + char *blob = NULL; + for (int i = 0; i < THREAD_MAX; i++) + { + blob = (char *)utable_ipfix_template_flow_get0(g_ipfix_schema, i, &blob_len); + send(g_udp_sock_fd, blob, blob_len, 0); + blob = NULL; + } + + sleep(interval_s); + } + + return NULL; +} + +extern "C" int load_file_to_memory(const char *file_name, unsigned char **pp_out, size_t *out_sz); +void ipfix_exporter_test_utable_init(struct utable *table, int index, const char *file_name) +{ + size_t json_size = 0; + unsigned char *json_str = NULL; + load_file_to_memory(file_name, &json_str, &json_size); + if (json_str == NULL || json_size == 0) + { + return; + } + + cJSON *root = NULL; + root = cJSON_Parse((const char *)json_str); + + cJSON *template_item = cJSON_GetArrayItem(cJSON_GetObjectItem(root, "templates"), index); + cJSON *template_key_array = cJSON_GetObjectItem(template_item, "elements"); + for (int i = 0; i < cJSON_GetArraySize(template_key_array); i++) + { + char *template_key = cJSON_GetArrayItem(template_key_array, i)->valuestring; + cJSON *elements_array = cJSON_GetObjectItem(root, template_key); + for (int j = 0; j < cJSON_GetArraySize(elements_array); j++) + { + cJSON *element = cJSON_GetArrayItem(elements_array, j); + char *element_key = cJSON_GetObjectItem(element, "element_name")->valuestring; + if (strcmp(cJSON_GetObjectItem(element, "element_type")->valuestring, "string") == 0) + { + char temp[128] = {0}; + snprintf(temp, 128, "%s_%s_%d", element_key, "string", cJSON_GetObjectItem(element, "element_id")->valueint); + utable_add_cstring(table, element_key, temp, strlen(temp)); + } + else if (strcmp(cJSON_GetObjectItem(element, "element_type")->valuestring, "unsigned64") == 0 || + strcmp(cJSON_GetObjectItem(element, "element_type")->valuestring, "unsigned32") == 0 || + strcmp(cJSON_GetObjectItem(element, "element_type")->valuestring, "unsigned16") == 0 || + strcmp(cJSON_GetObjectItem(element, "element_type")->valuestring, "unsigned8") == 0) + { + utable_add_integer(table, element_key, cJSON_GetObjectItem(element, "element_id")->valueint); + } + } + } + + free(json_str); + cJSON_Delete(root); +} + +void *ipfix_worker_thread_data_flow_send(void *arg) +{ + uint16_t worker_id = (*(uint16_t *)arg); + while (1) + { + for (int i = 0; i < TEMPLATE_MAX; i++) + { + struct utable *table = utable_new(); + ipfix_exporter_test_utable_init(table, i, ipfix_schema_json_path); + utable_delete_item(table, "decoded_as"); + utable_add_cstring(table, "decoded_as", template_id_list[i].template_name, strlen(template_id_list[i].template_name)); + + size_t blob_len = 0; + char *blob = NULL; + utable_ipfix_data_flow_exporter(table, g_ipfix_schema, template_id_list[i].template_id, worker_id, &blob, &blob_len); + send(g_udp_sock_fd, blob, blob_len, 0); + free(blob); + blob = NULL; + utable_free(table); + } + + sleep(5); + } + + return NULL; +} + +// ./ipfix_exporter_example ipfix_schema.json 127.0.0.1 4397 +extern "C" int main(int argc, char *argv[]) +{ + if (argc != 4) + { + printf("expample: ./ipfix_exporter_example ipfix_schema.json 127.0.0.1 4397\n"); + return -1; + } + + ipfix_schema_json_path = argv[1]; + g_ipfix_schema = utable_ipfix_exporter_schema_new(ipfix_schema_json_path, 1, THREAD_MAX); + if (g_ipfix_schema == NULL) + { + printf("ipfix_exporter_schema_init error, illegal ipfix_schema_json_path: %s\n", ipfix_schema_json_path); + return -1; + } + + for (int i = 0; i < TEMPLATE_MAX; i++) + { + template_id_list[i].template_id = utable_ipfix_template_get(g_ipfix_schema, template_id_list[i].template_name); + } + + g_udp_sock_fd = ipfix_exporter_get_socket_fd(argv[2], atoi(argv[3])); + int interval_s = 100; + pthread_t template_thread_id; + pthread_create(&template_thread_id, NULL, ipfix_template_send_thread_loop, (void *)&interval_s); + + uint16_t worker_id[THREAD_MAX]; + pthread_t pid[THREAD_MAX]; + for (int i = 0; i < THREAD_MAX; i++) + { + worker_id[i] = i; + pthread_create(&pid[i], NULL, ipfix_worker_thread_data_flow_send, (void *)&worker_id[i]); + } + + sleep(1000); + utable_ipfix_exporter_schema_free(g_ipfix_schema); + pthread_join(template_thread_id, NULL); + for (int i = 0; i < THREAD_MAX; i++) + { + pthread_join(pid[i], NULL); + } + close(g_udp_sock_fd); + return 0; +} \ No newline at end of file diff --git a/deps/utable/test/CMakeLists.txt b/deps/utable/test/CMakeLists.txt new file mode 100644 index 0000000..5466fc9 --- /dev/null +++ b/deps/utable/test/CMakeLists.txt @@ -0,0 +1,31 @@ +include_directories(${CMAKE_SOURCE_DIR}/deps) + +add_executable(gtest_utable + unit_test_utable.cpp +) + +target_link_libraries( + gtest_utable + utable + gtest +) + +add_executable(gtest_ipfix_exporter + unit_test_ipfix_exporter.cpp +) + +target_link_libraries( + gtest_ipfix_exporter + utable + gtest +) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/conf DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) + +include(GoogleTest) +gtest_discover_tests(gtest_utable) + +gtest_discover_tests(gtest_ipfix_exporter + TEST_LIST gtest_ipfix_exporter_tests +) +set_tests_properties(${gtest_ipfix_exporter_tests} PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/) \ No newline at end of file diff --git a/deps/utable/test/conf/ipfix_http_test.json b/deps/utable/test/conf/ipfix_http_test.json new file mode 100644 index 0000000..6f21161 --- /dev/null +++ b/deps/utable/test/conf/ipfix_http_test.json @@ -0,0 +1,59 @@ +{ + "flags_identify_info": [1,2,3], + "security_rule_list": ["aaa","bbb", "ccc"], + "tcp_rtt_ms": 61, + "http_version": "http1", + "http_request_line": "GET /storeApi/systemApp/checkAppStatus.json?store=com.tcl.appmarket2&mac=3C:59:1E:DC:F8:F4&accessKeyId=1472540256617×tamp=1636812592882&signature=4fQy1gdPkmW2J4I9vy0MYPVPlpY%3D&lang=en_AU&type=1&jsonParam=[{\"appPkg\":\"com.google.android.youtube\",\"versionCode\":101253134},{\"appPkg\":\"com.tcl.youtube\",\"versionCode\":20},{\"appPkg\":\"com.tcl.initsetup\",\"versionCode\":1},{\"appPkg\":\"com.android.providers.calendar\",\"versionCode\":22},{\"appPkg\":\"com.android.providers.media\",\"versionCode\":800},{\"appPkg\":\"com.tcl.android.SystemDemo\",\"versionCode\":1004},{\"appPkg\":\"com.android.wallpapercropper\",\"versionCode\":22},{\"appPkg\":\"tv.huan.twitter\",\"versionCode\":20151216},{\"appPkg\":\"com.tcl.cyberui.resource\",\"versionCode\":1},{\"appPkg\":\"com.tcl.factory.view\",\"versionCode\":2},{\"appPkg\":\"com.golive.tc\",\"versionCode\":1025},{\"appPkg\":\"com.android.htmlviewer\",\"versionCode\":22},{\"appPkg\":\"com.tcl.pvr.pvrplayer\",\"versionCode\":61115},{\"appPkg\":\"com.tcl.packageinstaller.service.renew\",\"versionCode\":5},{\"appPkg\":\"com.android.providers.downloads\",\"versionCode\":22},{\"appPkg\":\"com.google.android.pano.packageinstaller\",\"versionCode\":22},{\"appPkg\":\"com.tcl.common.viewer\",\"versionCode\":9180010},{\"appPkg\":\"com.tcl.mediabrowser\",\"versionCode\":61005},{\"appPkg\":\"com.tcl.mediacenter\",\"versionCode\":11002},{\"appPkg\":\"com.android.inputmethod.pinyin\",\"versionCode\":22},{\"appPkg\":\"com.android.defcontainer\",\"versionCode\":22},{\"appPkg\":\"com.android.vending\",\"versionCode\":80403286},{\"appPkg\":\"com.android.pacprocessor\",\"versionCode\":22},{\"appPkg\":\"com.android.certinstaller\",\"versionCode\":22},{\"appPkg\":\"android\",\"versionCode\":22},{\"appPkg\":\"com.android.contacts\",\"versionCode\":22},{\"appPkg\":\"com.tcl.browser\",\"versionCode\":630770},{\"appPkg\":\"tv.huan.deezer\",\"versionCode\":20160707},{\"appPkg\":\"com.tcl.adservice\",\"versionCode\":2005},{\"appPkg\":\"com.tcl.inputmethod.international\",\"versionCode\":3},{\"appPkg\":\"tw.com.double2.GameCenter\",\"versionCode\":98},{\"appPkg\":\"com.tcl.appmarket2\",\"versionCode\":2022000900},{\"appPkg\":\"com.android.backupconfirm\",\"versionCode\":22},{\"appPkg\":\"com.tcl.xian.StartandroidService\",\"versionCode\":1003},{\"appPkg\":\"vng.zing.tv\",\"versionCode\":203},{\"appPkg\":\"com.android.calendar\",\"versionCode\":22},{\"appPkg\":\"com.google.android.gsf.notouch\",\"versionCode\":1},{\"appPkg\":\"com.android.providers.settings\",\"versionCode\":22},{\"appPkg\":\"com.tcl.versionUpdateApp\",\"versionCode\":2001},{\"appPkg\":\"com.android.sharedstoragebackup\",\"versionCode\":22},{\"appPkg\":\"com.icflix.androidtv\",\"versionCode\":17},{\"appPkg\":\"com.android.dreams.basic\",\"versionCode\":22},{\"appPkg\":\"com.tcl.t_cast\",\"versionCode\":61003},{\"appPkg\":\"com.tcl.triava\",\"versionCode\":8003},{\"appPkg\":\"com.android.paramcollect\",\"versionCode\":22},{\"appPkg\":\"com.tcl.update\",\"versionCode\":10000008},{\"appPkg\":\"com.android.webview\",\"versionCode\":300002},{\"appPkg\":\"com.android.inputdevices\",\"versionCode\":22},{\"appPkg\":\"com.android.wfdsink\",\"versionCode\":1},{\"appPkg\":\"com.android.musicfx\",\"versionCode\":10400},{\"appPkg\":\"com.tcl.MultiScreenInteraction_TV\",\"versionCode\":20170224},{\"appPkg\":\"com.zing.tv.plus\",\"versionCode\":1},{\"appPkg\":\"com.android.keychain\",\"versionCode\":22},{\"appPkg\":\"com.tcl.cyberui\",\"versionCode\":1133},{\"appPkg\":\"com.google.android.gms\",\"versionCode\":8301846},{\"appPkg\":\"com.google.android.gsf\",\"versionCode\":22},{\"appPkg\":\"com.google.android.partnersetup\",\"versionCode\":22},{\"appPkg\":\"com.android.packageinstaller\",\"versionCode\":22},{\"appPkg\":\"com.android.proxyhandler\",\"versionCode\":22},{\"appPkg\":\"com.tcl.bluetoothlistenalone\",\"versionCode\":22},{\"appPkg\":\"com.android.managedprovisioning\",\"versionCode\":22},{\"appPkg\":\"com.android.dreams.phototable\",\"versionCode\":22},{\"appPkg\":\"com.tcl.virtualkey\",\"versionCode\":1},{\"appPkg\":\"com.tcl.m\",\"versionCode\":1},{\"appPkg\":\"com.google.android.backuptransport\",\"versionCode\":22},{\"appPkg\":\"com.android.systemui2\",\"versionCode\":22},{\"appPkg\":\"com.tcl.cyberui.resource.zing\",\"versionCode\":1},{\"appPkg\":\"com.tcl.tv\",\"versionCode\":61004},{\"appPkg\":\"com.google.android.tv.voiceinput\",\"versionCode\":1633564},{\"appPkg\":\"com.tcl.settings\",\"versionCode\":1},{\"appPkg\":\"com.google.android.youtube.tv\",\"versionCode\":10207100},{\"appPkg\":\"com.tcl.sticker\",\"versionCode\":2},{\"appPkg\":\"com.android.wallpaper\",\"versionCode\":22},{\"appPkg\":\"com.google.android.tv.frameworkpackagestubs\",\"versionCode\":1},{\"appPkg\":\"com.android.mcast\",\"versionCode\":1},{\"appPkg\":\"com.android.shell\",\"versionCode\":22},{\"appPkg\":\"cn.com.realsil.a2dpsinkservice\",\"versionCode\":1},{\"appPkg\":\"com.tcl.common.mediaplayer\",\"versionCode\":61005},{\"appPkg\":\"com.android.location.fused\",\"versionCode\":22},{\"appPkg\":\"com.android.systemui\",\"versionCode\":22},{\"appPkg\":\"com.example.tvcapture\",\"versionCode\":2},{\"appPkg\":\"com.yupptv.tcl\",\"versionCode\":6},{\"appPkg\":\"vng.zing.mp3\",\"versionCode\":17},{\"appPkg\":\"com.tcl.common.tvfaultdiagnosis\",\"versionCode\":3},{\"appPkg\":\"com.android.bluetooth\",\"versionCode\":22},{\"appPkg\":\"com.android.providers.contacts\",\"versionCode\":22},{\"appPkg\":\"com.android.captiveportallogin\",\"versionCode\":22}] HTTP/1.1", + "http_request_content_type": "text/xml", + "http_user_agent": "Dalvik/2.1.0 (Linux; U; Android 5.1.1; MStar Android TV Build/LMY47V)", + "http_host": "storeapi.app-vtion.com", + "http_url": "storeapi.app-vtion.com/storeApi/systemApp/checkAppStatus.json?store=com.tcl.appmarket2&mac=3C:59:1E:DC:F8:F4&accessKeyId=1472540256617×tamp=1636812592882&signature=4fQy1gdPkmW2J4I9vy0MYPVPlpY=&lang=en_AU&type=1&jsonParam=[{\"appPkg\":\"com.google.android.youtube\",\"versionCode\":101253134},{\"appPkg\":\"com.tcl.youtube\",\"versionCode\":20},{\"appPkg\":\"com.tcl.initsetup\",\"versionCode\":1},{\"appPkg\":\"com.android.providers.calendar\",\"versionCode\":22},{\"appPkg\":\"com.android.providers.media\",\"versionCode\":800},{\"appPkg\":\"com.tcl.android.SystemDemo\",\"versionCode\":1004},{\"appPkg\":\"com.android.wallpapercropper\",\"versionCode\":22},{\"appPkg\":\"tv.huan.twitter\",\"versionCode\":20151216},{\"appPkg\":\"com.tcl.cyberui.resource\",\"versionCode\":1},{\"appPkg\":\"com.tcl.factory.view\",\"versionCode\":2},{\"appPkg\":\"com.golive.tc\",\"versionCode\":1025},{\"appPkg\":\"com.android.htmlviewer\",\"versionCode\":22},{\"appPkg\":\"com.tcl.pvr.pvrplayer\",\"versionCode\":61115},{\"appPkg\":\"com.tcl.packageinstaller.service.renew\",\"versionCode\":5},{\"appPkg\":\"com.android.providers.downloads\",\"versionCode\":22},{\"appPkg\":\"com.google.android.pano.packageinstaller\",\"versionCode\":22},{\"appPkg\":\"com.tcl.common.viewer\",\"versionCode\":9180010},{\"appPkg\":\"com.tcl.mediabrowser\",\"versionCode\":61005},{\"appPkg\":\"com.tcl.mediacenter\",\"versionCode\":11002},{\"appPkg\":\"com.android.inputmethod.pinyin\",\"versionCode\":22},{\"appPkg\":\"com.android.defcontainer\",\"versionCode\":22},{\"appPkg\":\"com.android.vending\",\"versionCode\":80403286},{\"appPkg\":\"com.android.pacprocessor\",\"versionCode\":22},{\"appPkg\":\"com.android.certinstaller\",\"versionCode\":22},{\"appPkg\":\"android\",\"versionCode\":22},{\"appPkg\":\"com.android.contacts\",\"versionCode\":22},{\"appPkg\":\"com.tcl.browser\",\"versionCode\":630770},{\"appPkg\":\"tv.huan.deezer\",\"versionCode\":20160707},{\"appPkg\":\"com.tcl.adservice\",\"versionCode\":2005},{\"appPkg\":\"com.tcl.inputmethod.international\",\"versionCode\":3},{\"appPkg\":\"tw.com.double2.GameCenter\",\"versionCode\":98},{\"appPkg\":\"com.tcl.appmarket2\",\"versionCode\":2022000900},{\"appPkg\":\"com.android.backupconfirm\",\"versionCode\":22},{\"appPkg\":\"com.tcl.xian.StartandroidService\",\"versionCode\":1003},{\"appPkg\":\"vng.zing.tv\",\"versionCode\":203},{\"appPkg\":\"com.android.calendar\",\"versionCode\":22},{\"appPkg\":\"com.google.android.gsf.notouch\",\"versionCode\":1},{\"appPkg\":\"com.android.providers.settings\",\"versionCode\":22},{\"appPkg\":\"com.tcl.versionUpdateApp\",\"versionCode\":2001},{\"appPkg\":\"com.android.sharedstoragebackup\",\"versionCode\":22},{\"appPkg\":\"com.icflix.androidtv\",\"versionCode\":17},{\"appPkg\":\"com.android.dreams.basic\",\"versionCode\":22},{\"appPkg\":\"com.tcl.t_cast\",\"versionCode\":61003},{\"appPkg\":\"com.tcl.triava\",\"versionCode\":8003},{\"appPkg\":\"com.android.paramcollect\",\"versionCode\":22},{\"appPkg\":\"com.tcl.update\",\"versionCode\":10000008},{\"appPkg\":\"com.android.webview\",\"versionCode\":300002},{\"appPkg\":\"com.android.inputdevices\",\"versionCode\":22},{\"appPkg\":\"com.android.wfdsink\",\"versionCode\":1},{\"appPkg\":\"com.android.musicfx\",\"versionCode\":10400},{\"appPkg\":\"com.tcl.MultiScreenInteraction_TV\",\"versionCode\":20170224},{\"appPkg\":\"com.zing.tv.plus\",\"versionCode\":1},{\"appPkg\":\"com.android.keychain\",\"versionCode\":22},{\"appPkg\":\"com.tcl.cyberui\",\"versionCode\":1133},{\"appPkg\":\"com.google.android.gms\",\"versionCode\":8301846},{\"appPkg\":\"com.google.android.gsf\",\"versionCode\":22},{\"appPkg\":\"com.google.android.partnersetup\",\"versionCode\":22},{\"appPkg\":\"com.android.packageinstaller\",\"versionCode\":22},{\"appPkg\":\"com.android.proxyhandler\",\"versionCode\":22},{\"appPkg\":\"com.tcl.bluetoothlistenalone\",\"versionCode\":22},{\"appPkg\":\"com.android.managedprovisioning\",\"versionCode\":22},{\"appPkg\":\"com.android.dreams.phototable\",\"versionCode\":22},{\"appPkg\":\"com.tcl.virtualkey\",\"versionCode\":1},{\"appPkg\":\"com.tcl.m\",\"versionCode\":1},{\"appPkg\":\"com.google.android.backuptransport\",\"versionCode\":22},{\"appPkg\":\"com.android.systemui2\",\"versionCode\":22},{\"appPkg\":\"com.tcl.cyberui.resource.zing\",\"versionCode\":1},{\"appPkg\":\"com.tcl.tv\",\"versionCode\":61004},{\"appPkg\":\"com.google.android.tv.voiceinput\",\"versionCode\":1633564},{\"appPkg\":\"com.tcl.settings\",\"versionCode\":1},{\"appPkg\":\"com.google.android.youtube.tv\",\"versionCode\":10207100},{\"appPkg\":\"com.tcl.sticker\",\"versionCode\":2},{\"appPkg\":\"com.android.wallpaper\",\"versionCode\":22},{\"appPkg\":\"com.google.android.tv.frameworkpackagestubs\",\"versionCode\":1},{\"appPkg\":\"com.android.mcast\",\"versionCode\":1},{\"appPkg\":\"com.android.shell\",\"versionCode\":22},{\"appPkg\":\"cn.com.realsil.a2dpsinkservice\",\"versionCode\":1},{\"appPkg\":\"com.tcl.common.mediaplayer\",\"versionCode\":61005},{\"appPkg\":\"com.android.location.fused\",\"versionCode\":22},{\"appPkg\":\"com.android.systemui\",\"versionCode\":22},{\"appPkg\":\"com.example.tvcapture\",\"versionCode\":2},{\"appPkg\":\"com.yupptv.tcl\",\"versionCode\":6},{\"appPkg\":\"vng.zing.mp3\",\"versionCode\":17},{\"appPkg\":\"com.tcl.common.tvfaultdiagnosis\",\"versionCode\":3},{\"appPkg\":\"com.android.bluetooth\",\"versionCode\":22},{\"appPkg\":\"com.android.providers.contacts\",\"versionCode\":22},{\"appPkg\":\"com.android.captiveportallogin\",\"versionCode\":22}]", + "http_status_code": 200, + "http_response_line": "HTTP/1.1 200 OK", + "http_response_content_type": "application/json;charset=UTF-8", + "http_response_latency_ms": 71, + "http_session_duration_ms": 71, + "in_src_mac": "d4:c1:c8:8f:ac:f0", + "in_dest_mac": "d4:c1:c8:98:c7:60", + "out_src_mac": "d4:c1:c8:98:c7:60", + "out_dest_mac": "d4:c1:c8:8f:ac:f0", + "tcp_client_isn": 1992830355, + "tcp_server_isn": 385667414, + "address_type": 4, + "client_ip": "196.189.24.193", + "server_ip": "13.213.119.237", + "client_port": 22214, + "server_port": 80, + "in_link_id": 0, + "out_link_id": 0, + "start_timestamp_ms": 1702547221100, + "end_timestamp_ms": 1702547221276, + "duration_ms": 176, + "sent_pkts": 10, + "sent_bytes": 5870, + "received_pkts": 9, + "received_bytes": 1080, + "tcp_c2s_ip_fragments": 0, + "tcp_s2c_ip_fragments": 0, + "tcp_c2s_rtx_pkts": 0, + "tcp_c2s_rtx_bytes": 0, + "tcp_s2c_rtx_pkts": 0, + "tcp_s2c_rtx_bytes": 0, + "tcp_c2s_o3_pkts": 0, + "tcp_s2c_o3_pkts": 0, + "tcp_c2s_lost_bytes": 0, + "tcp_s2c_lost_bytes": 0, + "flags": 0, + "decoded_as": "HTTP", + "server_fqdn": "storeapi.app-vtion.com", + "decoded_path": "ETHERNET.IPv4.UDP.VXLAN.ETHERNET.IPv4.TCP", + "t_vsys_id": 1, + "vsys_id": 1, + "session_id": 3051251997500, + "tcp_handshake_latency_ms": 47, + "client_os_desc": "Android", + "server_os_desc": "Linux", + "device_tag": "{\"tags\":[{\"tag\":\"device_id\",\"value\":\"device_1\"}]}", + "sled_ip": "127.0.0.1", + "dup_traffic_flag": 0 +} \ No newline at end of file diff --git a/deps/utable/test/conf/ipfix_schema.json b/deps/utable/test/conf/ipfix_schema.json new file mode 100644 index 0000000..edcbe24 --- /dev/null +++ b/deps/utable/test/conf/ipfix_schema.json @@ -0,0 +1,1222 @@ +{ + "version":10, + "PEN_number": 54450, + "refresh_interval_s": 30, + "templates": [ + { + "template_id": 257, + "template_name": "BASE", + "elements":[ + "BASE_elements" + ] + }, + { + "template_id": 258, + "template_name": "SSL", + "elements":[ + "BASE_elements", + "SSL_elements" + ] + }, + { + "template_id": 259, + "template_name": "HTTP", + "elements":[ + "BASE_elements", + "HTTP_elements" + ] + }, + { + "template_id": 260, + "template_name": "MAIL", + "elements":[ + "BASE_elements", + "MAIL_elements" + ] + }, + { + "template_id": 261, + "template_name": "DNS", + "elements":[ + "BASE_elements", + "DNS_elements" + ] + }, + { + "template_id": 262, + "template_name": "DTLS", + "elements":[ + "BASE_elements", + "DTLS_elements" + ] + }, + { + "template_id": 263, + "template_name": "QUIC", + "elements":[ + "BASE_elements", + "QUIC_elements" + ] + }, + { + "template_id": 264, + "template_name": "FTP", + "elements":[ + "BASE_elements", + "FTP_elements" + ] + }, + { + "template_id": 265, + "template_name": "SIP", + "elements":[ + "BASE_elements", + "SIP_elements" + ] + }, + { + "template_id": 266, + "template_name": "RTP", + "elements":[ + "BASE_elements", + "RTP_elements" + ] + }, + { + "template_id": 267, + "template_name": "SSH", + "elements":[ + "BASE_elements", + "SSH_elements" + ] + }, + { + "template_id": 268, + "template_name": "RDP", + "elements":[ + "BASE_elements", + "RDP_elements" + ] + }, + { + "template_id": 269, + "template_name": "Stratum", + "elements":[ + "BASE_elements", + "Stratum_elements" + ] + } + ], + "BASE_elements": [ + { + "element_name": "decoded_as", + "element_type": "string", + "element_id": 1 + }, + { + "element_name": "session_id", + "element_type": "unsigned64", + "element_id": 2 + }, + { + "element_name": "start_timestamp_ms", + "element_type": "unsigned64", + "element_id": 3 + }, + { + "element_name": "end_timestamp_ms", + "element_type": "unsigned64", + "element_id": 4 + }, + { + "element_name": "duration_ms", + "element_type": "unsigned32", + "element_id": 5 + }, + { + "element_name": "tcp_handshake_latency_ms", + "element_type": "unsigned32", + "element_id": 6 + }, + { + "element_name": "device_id", + "element_type": "string", + "element_id": 7 + }, + { + "element_name": "out_link_id", + "element_type": "unsigned32", + "element_id": 8 + }, + { + "element_name": "in_link_id", + "element_type": "unsigned32", + "element_id": 9 + }, + { + "element_name": "device_tag", + "element_type": "string", + "element_id": 10 + }, + { + "element_name": "data_center", + "element_type": "string", + "element_id": 11 + }, + { + "element_name": "device_group", + "element_type": "string", + "element_id": 12 + }, + { + "element_name": "sled_ip", + "element_type": "string", + "element_id": 13 + }, + { + "element_name": "address_type", + "element_type": "unsigned32", + "element_id": 14 + }, + { + "element_name":"vsys_id", + "element_type":"unsigned32", + "element_id": 15 + }, + { + "element_name":"t_vsys_id", + "element_type":"unsigned32", + "element_id": 16 + }, + { + "element_name":"flags", + "element_type":"unsigned64", + "element_id": 17 + }, + { + "element_name":"flags_identify_info", + "element_type":"string", + "element_id": 18 + }, + { + "element_name":"security_rule_list", + "element_type":"string", + "element_id": 19 + }, + { + "element_name":"security_action", + "element_type":"string", + "element_id": 20 + }, + { + "element_name":"monitor_rule_list", + "element_type":"string", + "element_id": 21 + }, + { + "element_name":"shaping_rule_list", + "element_type":"string", + "element_id": 22 + }, + { + "element_name":"sc_rule_list", + "element_type":"string", + "element_id": 23 + }, + { + "element_name":"sc_rsp_raw", + "element_type":"string", + "element_id": 24 + }, + { + "element_name":"sc_rsp_decrypted", + "element_type":"string", + "element_id": 25 + }, + { + "element_name":"proxy_rule_list", + "element_type":"string", + "element_id": 26 + }, + { + "element_name":"proxy_action", + "element_type":"string", + "element_id": 27 + }, + { + "element_name":"proxy_pinning_status", + "element_type":"unsigned32", + "element_id": 28 + }, + { + "element_name":"proxy_intercept_status", + "element_type":"unsigned32", + "element_id": 29 + }, + { + "element_name":"proxy_passthrough_reason", + "element_type":"string", + "element_id": 30 + }, + { + "element_name":"proxy_client_side_latency_ms", + "element_type":"unsigned32", + "element_id": 31 + }, + { + "element_name":"proxy_server_side_latency_ms", + "element_type":"unsigned32", + "element_id": 32 + }, + { + "element_name":"proxy_client_side_version", + "element_type":"string", + "element_id": 33 + }, + { + "element_name":"proxy_server_side_version", + "element_type":"string", + "element_id": 34 + }, + { + "element_name":"proxy_cert_verify", + "element_type":"unsigned32", + "element_id": 35 + }, + { + "element_name":"proxy_intercept_error", + "element_type":"string", + "element_id": 36 + }, + { + "element_name":"security_mirrored_pkts", + "element_type":"unsigned32", + "element_id": 37 + }, + { + "element_name":"security_mirrored_bytes", + "element_type":"unsigned32", + "element_id": 38 + }, + { + "element_name":"client_ip", + "element_type":"string", + "element_id": 39 + }, + { + "element_name":"client_port", + "element_type":"unsigned32", + "element_id": 40 + }, + { + "element_name":"client_os_desc", + "element_type":"string", + "element_id": 41 + }, + { + "element_name":"client_geolocation", + "element_type":"string", + "element_id": 42 + }, + { + "element_name":"client_asn", + "element_type":"string", + "element_id": 43 + }, + { + "element_name":"subscriber_id", + "element_type":"string", + "element_id": 44 + }, + { + "element_name":"imei", + "element_type":"string", + "element_id": 45 + }, + { + "element_name":"imsi", + "element_type":"string", + "element_id": 46 + }, + { + "element_name":"phone_number", + "element_type":"string", + "element_id": 47 + }, + { + "element_name":"apn", + "element_type":"string", + "element_id": 48 + }, + { + "element_name":"server_ip", + "element_type":"string", + "element_id": 49 + }, + { + "element_name":"server_port", + "element_type":"unsigned32", + "element_id": 50 + }, + { + "element_name":"server_os_desc", + "element_type":"string", + "element_id": 51 + }, + { + "element_name":"server_geolocation", + "element_type":"string", + "element_id": 52 + }, + { + "element_name":"server_asn", + "element_type":"string", + "element_id": 53 + }, + { + "element_name":"server_fqdn", + "element_type":"string", + "element_id": 54 + }, + { + "element_name":"app_transition", + "element_type":"string", + "element_id": 55 + }, + { + "element_name":"app", + "element_type":"string", + "element_id": 56 + }, + { + "element_name":"app_debug_info", + "element_type":"string", + "element_id": 57 + }, + { + "element_name":"app_content", + "element_type":"string", + "element_id": 58 + }, + { + "element_name":"decoded_path", + "element_type":"string", + "element_id": 59 + }, + { + "element_name":"fqdn_category_list", + "element_type":"string", + "element_id": 60 + }, + { + "element_name":"sent_pkts", + "element_type":"unsigned64", + "element_id": 61 + }, + { + "element_name":"received_pkts", + "element_type":"unsigned64", + "element_id": 62 + }, + { + "element_name":"sent_bytes", + "element_type":"unsigned64", + "element_id": 63 + }, + { + "element_name":"received_bytes", + "element_type":"unsigned64", + "element_id": 64 + }, + { + "element_name":"tcp_c2s_ip_fragments", + "element_type":"unsigned64", + "element_id": 65 + }, + { + "element_name":"tcp_s2c_ip_fragments", + "element_type":"unsigned64", + "element_id": 66 + }, + { + "element_name":"tcp_c2s_lost_bytes", + "element_type":"unsigned64", + "element_id": 67 + }, + { + "element_name":"tcp_s2c_lost_bytes", + "element_type":"unsigned64", + "element_id": 68 + }, + { + "element_name":"tcp_c2s_o3_pkts", + "element_type":"unsigned64", + "element_id": 69 + }, + { + "element_name":"tcp_s2c_o3_pkts", + "element_type":"unsigned64", + "element_id": 70 + }, + { + "element_name":"tcp_c2s_rtx_pkts", + "element_type":"unsigned64", + "element_id": 71 + }, + { + "element_name":"tcp_s2c_rtx_pkts", + "element_type":"unsigned64", + "element_id": 72 + }, + { + "element_name":"tcp_c2s_rtx_bytes", + "element_type":"unsigned64", + "element_id": 73 + }, + { + "element_name":"tcp_s2c_rtx_bytes", + "element_type":"unsigned64", + "element_id": 74 + }, + { + "element_name":"tcp_rtt_ms", + "element_type":"unsigned32", + "element_id": 75 + }, + { + "element_name":"tcp_client_isn", + "element_type":"unsigned64", + "element_id": 76 + }, + { + "element_name":"tcp_server_isn", + "element_type":"unsigned64", + "element_id": 77 + }, + { + "element_name":"packet_capture_file", + "element_type":"string", + "element_id": 78 + }, + { + "element_name":"encapsulation_type", + "element_type":"unsigned32", + "element_id": 79 + }, + { + "element_name":"in_src_mac", + "element_type":"string", + "element_id": 80 + }, + { + "element_name":"out_src_mac", + "element_type":"string", + "element_id": 81 + }, + { + "element_name":"in_dest_mac", + "element_type":"string", + "element_id": 82 + }, + { + "element_name":"out_dest_mac", + "element_type":"string", + "element_id": 83 + }, + { + "element_name":"tunnels", + "element_type":"string", + "element_id": 84 + }, + { + "element_name":"dup_traffic_flag", + "element_type":"unsigned32", + "element_id": 85 + }, + { + "element_name":"tunnel_endpoint_a_desc", + "element_type":"string", + "element_id": 86 + }, + { + "element_name":"tunnel_endpoint_b_desc", + "element_type":"string", + "element_id": 87 + } + ], + "SSL_elements": [ + { + "element_name": "ssl_version", + "element_type": "string", + "element_id": 184 + }, + { + "element_name": "ssl_sni", + "element_type": "string", + "element_id": 185 + }, + { + "element_name": "ssl_san", + "element_type": "string", + "element_id": 186 + }, + { + "element_name": "ssl_cn", + "element_type": "string", + "element_id": 187 + }, + { + "element_name": "ssl_handshake_latency_ms", + "element_type": "unsigned32", + "element_id": 188 + }, + { + "element_name": "ssl_ja3_hash", + "element_type": "string", + "element_id": 189 + }, + { + "element_name": "ssl_ja3s_hash", + "element_type": "string", + "element_id": 190 + }, + { + "element_name": "ssl_cert_issuer", + "element_type": "string", + "element_id": 191 + }, + { + "element_name": "ssl_cert_subject", + "element_type": "string", + "element_id": 192 + }, + { + "element_name": "ssl_esni_flag", + "element_type": "unsigned32", + "element_id": 193 + }, + { + "element_name": "ssl_ech_flag", + "element_type": "unsigned32", + "element_id": 194 + } + ], + "HTTP_elements": [ + { + "element_name": "http_url", + "element_type": "string", + "element_id": 121 + }, + { + "element_name": "http_host", + "element_type": "string", + "element_id": 122 + }, + { + "element_name": "http_request_line", + "element_type": "string", + "element_id": 123 + }, + { + "element_name": "http_response_line", + "element_type": "string", + "element_id": 124 + }, + { + "element_name": "http_request_body", + "element_type": "string", + "element_id": 125 + }, + { + "element_name": "http_response_body", + "element_type": "string", + "element_id": 126 + }, + { + "element_name": "http_proxy_flag", + "element_type": "unsigned32", + "element_id": 127 + }, + { + "element_name": "http_sequence", + "element_type": "unsigned32", + "element_id": 128 + }, + { + "element_name": "http_cookie", + "element_type": "string", + "element_id": 129 + }, + { + "element_name": "http_referer", + "element_type": "string", + "element_id": 130 + }, + { + "element_name": "http_user_agent", + "element_type": "string", + "element_id": 131 + }, + { + "element_name": "http_request_content_length", + "element_type": "unsigned64", + "element_id": 132 + }, + { + "element_name": "http_request_content_type", + "element_type": "string", + "element_id": 133 + }, + { + "element_name": "http_response_content_length", + "element_type": "unsigned64", + "element_id": 134 + }, + { + "element_name": "http_response_content_type", + "element_type": "string", + "element_id": 135 + }, + { + "element_name": "http_set_cookie", + "element_type": "string", + "element_id": 136 + }, + { + "element_name": "http_version", + "element_type": "string", + "element_id": 137 + }, + { + "element_name": "http_status_code", + "element_type": "unsigned32", + "element_id": 138 + }, + { + "element_name": "http_response_latency_ms", + "element_type": "unsigned32", + "element_id": 139 + }, + { + "element_name": "http_session_duration_ms", + "element_type": "unsigned32", + "element_id": 140 + }, + { + "element_name": "http_action_file_size", + "element_type": "unsigned64", + "element_id": 141 + } + ], + "MAIL_elements": [ + { + "element_name": "mail_protocol_type", + "element_type": "string", + "element_id": 142 + }, + { + "element_name": "mail_account", + "element_type": "string", + "element_id": 143 + }, + { + "element_name": "mail_from_cmd", + "element_type": "string", + "element_id": 144 + }, + { + "element_name": "mail_to_cmd", + "element_type": "string", + "element_id": 145 + }, + { + "element_name": "mail_from", + "element_type": "string", + "element_id": 146 + }, + { + "element_name": "mail_to", + "element_type": "string", + "element_id": 147 + }, + { + "element_name": "mail_cc", + "element_type": "string", + "element_id": 148 + }, + { + "element_name": "mail_bcc", + "element_type": "string", + "element_id": 149 + }, + { + "element_name": "mail_subject", + "element_type": "string", + "element_id": 150 + }, + { + "element_name": "mail_subject_charset", + "element_type": "string", + "element_id": 151 + }, + { + "element_name": "mail_attachment_name", + "element_type": "string", + "element_id": 152 + }, + { + "element_name": "mail_attachment_name_charset", + "element_type": "string", + "element_id": 153 + }, + { + "element_name": "mail_eml_file", + "element_type": "string", + "element_id": 154 + } + + ], + "DNS_elements": [ + { + "element_name": "dns_message_id", + "element_type": "unsigned32", + "element_id": 88 + }, + { + "element_name": "dns_qr", + "element_type": "unsigned32", + "element_id": 89 + }, + { + "element_name": "dns_opcode", + "element_type": "unsigned32", + "element_id": 90 + }, + { + "element_name": "dns_aa", + "element_type": "unsigned32", + "element_id": 91 + }, + { + "element_name": "dns_tc", + "element_type": "unsigned32", + "element_id": 92 + }, + { + "element_name": "dns_rd", + "element_type": "unsigned32", + "element_id": 93 + }, + { + "element_name": "dns_ra", + "element_type": "unsigned32", + "element_id": 94 + }, + { + "element_name": "dns_rcode", + "element_type": "unsigned32", + "element_id": 95 + }, + { + "element_name": "dns_qdcount", + "element_type": "unsigned32", + "element_id": 96 + }, + { + "element_name": "dns_ancount", + "element_type": "unsigned32", + "element_id": 97 + }, + { + "element_name": "dns_nscount", + "element_type": "unsigned32", + "element_id": 98 + }, + { + "element_name": "dns_arcount", + "element_type": "unsigned32", + "element_id": 99 + }, + { + "element_name": "dns_qname", + "element_type": "string", + "element_id": 100 + }, + { + "element_name": "dns_qtype", + "element_type": "unsigned32", + "element_id": 101 + }, + { + "element_name": "dns_qclass", + "element_type": "unsigned32", + "element_id": 102 + }, + { + "element_name": "dns_cname", + "element_type": "string", + "element_id": 103 + }, + { + "element_name": "dns_sub", + "element_type": "unsigned32", + "element_id": 104 + }, + { + "element_name": "dns_rr", + "element_type": "string", + "element_id": 105 + }, + { + "element_name": "dns_response_latency_ms", + "element_type": "unsigned32", + "element_id": 106 + } + ], + "DTLS_elements": [ + { + "element_name": "dtls_cookie", + "element_type": "string", + "element_id": 107 + }, + { + "element_name": "dtls_version", + "element_type": "string", + "element_id": 108 + }, + { + "element_name": "dtls_sni", + "element_type": "string", + "element_id": 109 + }, + { + "element_name": "dtls_san", + "element_type": "string", + "element_id": 110 + }, + { + "element_name": "dtls_cn", + "element_type": "string", + "element_id": 111 + }, + { + "element_name": "dtls_handshake_latency_ms", + "element_type": "unsigned32", + "element_id": 112 + }, + { + "element_name": "dtls_ja3_fingerprint", + "element_type": "string", + "element_id": 113 + }, + { + "element_name": "dtls_ja3_hash", + "element_type": "string", + "element_id": 114 + }, + { + "element_name": "dtls_cert_issuer", + "element_type": "string", + "element_id": 115 + }, + { + "element_name": "dtls_cert_subject", + "element_type": "string", + "element_id": 116 + } + ], + "QUIC_elements": [ + { + "element_name": "quic_version", + "element_type": "string", + "element_id": 155 + }, + { + "element_name": "quic_sni", + "element_type": "string", + "element_id": 156 + }, + { + "element_name": "quic_user_agent", + "element_type": "string", + "element_id": 157 + } + ], + "FTP_elements": [ + { + "element_name": "ftp_account", + "element_type": "string", + "element_id": 117 + }, + { + "element_name": "ftp_url", + "element_type": "string", + "element_id": 118 + }, + { + "element_name": "ftp_password", + "element_type": "string", + "element_id": 119 + }, + { + "element_name": "ftp_link_type", + "element_type": "string", + "element_id": 120 + } + ], + "SIP_elements": [ + { + "element_name": "sip_call_id", + "element_type": "string", + "element_id": 195 + }, + { + "element_name": "sip_originator_description", + "element_type": "string", + "element_id": 196 + }, + { + "element_name": "sip_responder_description", + "element_type": "string", + "element_id": 197 + }, + { + "element_name": "sip_user_agent", + "element_type": "string", + "element_id": 198 + }, + { + "element_name": "sip_server", + "element_type": "string", + "element_id": 199 + }, + { + "element_name": "sip_originator_sdp_connect_ip", + "element_type": "string", + "element_id": 200 + }, + { + "element_name": "sip_originator_sdp_media_port", + "element_type": "string", + "element_id": 201 + }, + { + "element_name": "sip_originator_sdp_media_type", + "element_type": "string", + "element_id": 202 + }, + { + "element_name": "sip_originator_sdp_content", + "element_type": "string", + "element_id": 203 + }, + { + "element_name": "sip_responder_sdp_connect_ip", + "element_type": "string", + "element_id": 204 + }, + { + "element_name": "sip_responder_sdp_media_port", + "element_type": "string", + "element_id": 205 + }, + { + "element_name": "sip_responder_sdp_media_type", + "element_type": "string", + "element_id": 206 + }, + { + "element_name": "sip_responder_sdp_content", + "element_type": "string", + "element_id": 207 + }, + { + "element_name": "sip_duration_s", + "element_type": "string", + "element_id": 208 + }, + { + "element_name": "sip_bye", + "element_type": "string", + "element_id": 209 + } + ], + "RTP_elements": [ + { + "element_name": "rtp_payload_type_c2s", + "element_type": "unsigned32", + "element_id": 210 + }, + { + "element_name": "rtp_payload_type_s2c", + "element_type": "unsigned32", + "element_id": 211 + }, + { + "element_name": "rtp_pcap_path", + "element_type": "string", + "element_id": 212 + }, + { + "element_name": "rtp_originator_dir", + "element_type": "unsigned32", + "element_id": 213 + } + ], + "SSH_elements": [ + { + "element_name": "ssh_version", + "element_type": "unsigned64", + "element_id": 173 + }, + { + "element_name": "ssh_auth_success", + "element_type": "string", + "element_id": 174 + }, + { + "element_name": "ssh_client_version", + "element_type": "string", + "element_id": 175 + }, + { + "element_name": "ssh_server_version", + "element_type": "string", + "element_id": 176 + }, + { + "element_name": "ssh_cipher_alg", + "element_type": "string", + "element_id": 177 + }, + { + "element_name": "ssh_mac_alg", + "element_type": "string", + "element_id": 178 + }, + { + "element_name": "ssh_compression_alg", + "element_type": "string", + "element_id": 179 + }, + { + "element_name": "ssh_kex_alg", + "element_type": "string", + "element_id": 180 + }, + { + "element_name": "ssh_host_key_alg", + "element_type": "string", + "element_id": 181 + }, + { + "element_name": "ssh_host_key", + "element_type": "string", + "element_id": 182 + }, + { + "element_name": "ssh_hassh", + "element_type": "string", + "element_id": 183 + } + ], + "RDP_elements": [ + { + "element_name": "rdp_cookie", + "element_type": "string", + "element_id": 158 + }, + { + "element_name": "rdp_security_protocol", + "element_type": "string", + "element_id": 159 + }, + { + "element_name": "rdp_client_channels", + "element_type": "string", + "element_id": 160 + }, + { + "element_name": "rdp_keyboard_layout", + "element_type": "string", + "element_id": 161 + }, + { + "element_name": "rdp_client_version", + "element_type": "string", + "element_id": 162 + }, + { + "element_name": "rdp_client_name", + "element_type": "string", + "element_id": 163 + }, + { + "element_name": "rdp_client_product_id", + "element_type": "string", + "element_id": 164 + }, + { + "element_name": "rdp_desktop_width", + "element_type": "string", + "element_id": 165 + }, + { + "element_name": "rdp_desktop_height", + "element_type": "string", + "element_id": 166 + }, + { + "element_name": "rdp_requested_color_depth", + "element_type": "string", + "element_id": 167 + }, + { + "element_name": "rdp_certificate_type", + "element_type": "string", + "element_id": 168 + }, + { + "element_name": "rdp_certificate_count", + "element_type": "unsigned32", + "element_id": 169 + }, + { + "element_name": "rdp_certificate_permanent", + "element_type": "unsigned32", + "element_id": 170 + }, + { + "element_name": "rdp_encryption_level", + "element_type": "string", + "element_id": 171 + }, + { + "element_name": "rdp_encryption_method", + "element_type": "string", + "element_id": 172 + } + ], + "Stratum_elements": [ + { + "element_name": "stratum_cryptocurrency", + "element_type": "string", + "element_id": 214 + }, + { + "element_name": "stratum_mining_pools", + "element_type": "string", + "element_id": 215 + }, + { + "element_name": "stratum_mining_program", + "element_type": "string", + "element_id": 216 + }, + { + "element_name": "stratum_mining_subscribe", + "element_type": "string", + "element_id": 217 + } + ] +} \ No newline at end of file diff --git a/deps/utable/test/conf/ipfix_ssl_test.json b/deps/utable/test/conf/ipfix_ssl_test.json new file mode 100644 index 0000000..d6bff21 --- /dev/null +++ b/deps/utable/test/conf/ipfix_ssl_test.json @@ -0,0 +1,50 @@ +{ + "flags_identify_info": [1,2,3], + "security_rule_list": ["aaa","bbb", "ccc"], + "tcp_rtt_ms": 61, + "ssl_ja3s_hash": "cd5a8d2e276eabf0839bf1a25acc479e", + "ssl_version": "v3", + "ssl_cn": "*.google.com", + "ssl_cert_issuer": "CN=GTS CA 1C3;O=Google Trust Services LLC;C=US;;;;", + "ssl_cert_subject": "CN=*.google.com;;;;;;", + "ssl_san": "*.google.com;*.appengine.google.com;*.bdn.dev;*.cloud.google.com;*.crowdsource.google.com;*.datacompute.google.com;*.google.ca;*.google.cl;*.google.co.in;*.google.co.jp;*.google.co.uk;*.google.com.ar;*.google.com.au;*.google.com.br;*.google.com.co;*.google.com.mx;*.google.com.tr;*.google.com.vn;*.google.de;*.google.es;*.google.fr;*.google.hu;*.google.it;*.google.nl;*.google.pl;*.google.pt;*.googleadapis.com;*.googleapis.cn;*.googlevideo.com;*.gstatic.cn;*.gstatic-cn.com;*.gstaticcnapps.cn;googlecnapps.cn;*.googlecnapps.cn;googleapps-cn.com;*.googleapps-cn.com;gkecnapps.cn;*.gkecnapps.cn;googledownloads.cn;*.googledownloads.cn;recaptcha.net.cn;*.recaptcha.net.cn;widevine.cn;*.widevine.cn;ampproject.org.cn;*.ampproject.org.cn;ampproject.net.cn;*.ampproject.net.cn;google-analytics-cn.com;*.google-analytics-cn.com;googleadservices-cn.com;*.googleadservices-cn.com;googlevads-cn.com;*.googlevads-cn.com;googleapis-cn.com;*.googleapis-cn.com;googleoptimize-cn.com;*.googleoptimize-cn.com;doubleclick-cn.net;*.doubleclick-cn.net;*.fls.doubleclick-cn.net;*.g.doubleclick-cn.net;doubleclick.cn;*.doubleclick.cn;*.fls.doubleclick.cn;*.g.doubleclick.cn;dartsearch-cn.net;*.dartsearch-cn.net;googletraveladservices-cn.com;*.googletraveladservices-cn.com;googletagservices-cn.com;*.googletagservices-cn.com;googletagmanager-cn.com;*.googletagmanager-cn.com;googlesyndication-cn.com;*.googlesyndication-cn.com;*.safeframe.googlesyndication-cn.com;app-measurement-cn.com;*.app-measurement-cn.com;gvt1-cn.com;*.gvt1-cn.com;gvt2-cn.com;*.gvt2-cn.com;2mdn-cn.net;*.2mdn-cn.net;googleflights-cn.net;*.googleflights-cn.net;admob-cn.com;*.admob-cn.com;*.gstatic.com;*.metric.gstatic.com;*.gvt1.com;*.gcpcdn.gvt1.com;*.gvt2.com;*.gcp.gvt2.com;*.url.google.com;*.youtube-nocookie.com;*.ytimg.com;android.com;*.android.com;*.flash.android.com;g.cn;*.g.cn;g.co;*.g.co;goo.gl;www.goo.gl;google-analytics.com;*.google-analytics.com;google.com;googlecommerce.com;*.googlecommerce.com;ggpht.cn", + "in_src_mac": "d4:c1:c8:8f:ac:f0", + "in_dest_mac": "d4:c1:c8:98:c7:60", + "tcp_client_isn": 2664633009, + "tcp_server_isn": 2183931785, + "address_type": 4, + "client_ip": "196.190.248.0", + "server_ip": "142.250.185.34", + "client_port": 16986, + "server_port": 443, + "in_link_id": 0, + "out_link_id": 0, + "start_timestamp_ms": 1702610615910, + "end_timestamp_ms": 1702610615996, + "duration_ms": 86, + "sent_pkts": 0, + "sent_bytes": 0, + "received_pkts": 10, + "received_bytes": 7246, + "tcp_c2s_ip_fragments": 0, + "tcp_s2c_ip_fragments": 0, + "tcp_c2s_rtx_pkts": 0, + "tcp_c2s_rtx_bytes": 0, + "tcp_s2c_rtx_pkts": 0, + "tcp_s2c_rtx_bytes": 0, + "tcp_c2s_o3_pkts": 0, + "tcp_s2c_o3_pkts": 0, + "tcp_c2s_lost_bytes": 0, + "tcp_s2c_lost_bytes": 0, + "flags": 0, + "decoded_as": "SSL", + "decoded_path": "ETHERNET.IPv4.UDP.VXLAN.ETHERNET.IPv4.TCP", + "t_vsys_id": 1, + "vsys_id": 1, + "session_id": 11849422307955, + "tcp_handshake_latency_ms": 37, + "server_os_desc": "Android", + "device_tag": "{\"tags\":[{\"tag\":\"device_id\",\"value\":\"device_1\"}]}", + "sled_ip": "127.0.0.1", + "dup_traffic_flag": 0 +} \ No newline at end of file diff --git a/deps/utable/test/unit_test_ipfix_exporter.cpp b/deps/utable/test/unit_test_ipfix_exporter.cpp new file mode 100644 index 0000000..e7d22d9 --- /dev/null +++ b/deps/utable/test/unit_test_ipfix_exporter.cpp @@ -0,0 +1,802 @@ + +#include +#include +#include +#include +#include "utable/utable.h" +#include "cjson/cJSON.h" +#include "uthash/utarray.h" + +#pragma GCC diagnostic ignored "-Wunused-parameter" + +#define IPFIX_DEFUALT_VERSION 10 +#define GEEDGE_NETWORKS_PEN_NUMBER 54450 +#define IPFIX_DEFUALT_PEN_NUMBER GEEDGE_NETWORKS_PEN_NUMBER +#define IPFIX_NONE_PEN_NUMBER -1 +#define IPFIX_TEMPLATE_SET_ID 2 +#define IPFIX_BUFF_MAX_SIZE 2048 +#define IPFIX_ELEMENT_MAX_LEN 32 + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +enum ipfix_type +{ + IPFIX_UNSIGNED8 = 0, + IPFIX_UNSIGNED16, + IPFIX_UNSIGNED32, + IPFIX_UNSIGNED64, + IPFIX_VARIABLE_STRING, + IPFIX_UNKNOWN +}; + +struct ipfix_element +{ + enum ipfix_type type; + uint16_t element_id; + uint16_t value_length; // if type is variable_string, value_length is 0 + int PEN_number; + char name[IPFIX_ELEMENT_MAX_LEN]; +}; + +struct ipfix_template +{ + uint16_t template_id; + char *name; + UT_array *elements_array; // utarray for current template elements +}; + +struct ipfix_worker_context +{ + int source_id; + int sequence; + size_t template_blob_length; + char *template_blob; +}; + +struct ipfix_exporter_schema +{ + uint16_t n_worker; + uint16_t version; + int domain_id; + int PEN_number; + UT_array *templates_array; // utarray for templates + struct ipfix_worker_context *worker_context_array; // utarray for worker_context +}; + +struct ipfix_message_head +{ + uint16_t version; + uint16_t length; + int exporttime; + int ipfix_message_sequence; + int domain_id; +}; +#define IPFIX_MESSAGE_HEAD_LEN sizeof(struct ipfix_message_head) + +#define THREAD_MAX 4 +#define TEMPLATE_MAX 13 +struct gtest_ipfix_template_info +{ + const char *template_name; + int template_id; + int n_elements; +}; + +struct gtest_ipfix_template_info gtest_ipfix_template_info[TEMPLATE_MAX] = { + {"BASE", 257, 87}, + {"SSL", 258, 98}, + {"HTTP", 259, 108}, + {"MAIL", 260, 100}, + {"DNS", 261, 106}, + {"DTLS", 262, 97}, + {"QUIC", 263, 90}, + {"FTP", 264, 91}, + {"SIP", 265, 102}, + {"RTP", 266, 91}, + {"SSH", 267, 98}, + {"RDP", 268, 102}, + {"Stratum", 269, 91}}; + +#define IPFIX_ELEMENT_HEAD_LEN sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) // element_id + element_length + PEN_number +const char *ipfix_schema_json_path = "./conf/ipfix_schema.json"; +int g_domain_id = 1; +TEST(utable_ipfix_exporter_test, ipfix_schema_new_error_path) +{ + struct ipfix_exporter_schema *ipfix_schema = utable_ipfix_exporter_schema_new((const char *)"", g_domain_id, THREAD_MAX); + ASSERT_TRUE(ipfix_schema == NULL); +} + +int test_template_blob_len_calculate(struct ipfix_exporter_schema *ipfix_schema) +{ + int template_blob_length = 0; + template_blob_length += IPFIX_MESSAGE_HEAD_LEN; // message head + template_blob_length += sizeof(uint16_t) + sizeof(uint16_t); // flow set id + flow set length + for (unsigned int i = 0; i < TEMPLATE_MAX; i++) + { + struct ipfix_template *template_item = (struct ipfix_template *)utarray_eltptr(ipfix_schema->templates_array, i); + template_blob_length += sizeof(uint16_t) + sizeof(uint16_t) + utarray_len(template_item->elements_array) * (IPFIX_ELEMENT_HEAD_LEN); // sizeof(template_id) + sizeof(field_count) + n_elements * sizeof(element_id + element_length + PEN_number) + } + + return template_blob_length; +} + +TEST(utable_ipfix_exporter_test, ipfix_schema_new_check_schema) +{ + struct ipfix_exporter_schema *ipfix_schema = utable_ipfix_exporter_schema_new(ipfix_schema_json_path, g_domain_id, THREAD_MAX); + ASSERT_TRUE(ipfix_schema != NULL); + + EXPECT_EQ(ipfix_schema->domain_id, 1); + EXPECT_EQ(ipfix_schema->n_worker, THREAD_MAX); + EXPECT_EQ(utarray_len(ipfix_schema->templates_array), TEMPLATE_MAX); + EXPECT_EQ(ipfix_schema->PEN_number, GEEDGE_NETWORKS_PEN_NUMBER); + EXPECT_EQ(ipfix_schema->version, IPFIX_DEFUALT_VERSION); + + for (unsigned int i = 0; i < TEMPLATE_MAX; i++) + { + struct ipfix_template *template_item = (struct ipfix_template *)utarray_eltptr(ipfix_schema->templates_array, i); + ASSERT_TRUE(template_item != NULL); + EXPECT_EQ(template_item->template_id, gtest_ipfix_template_info[i].template_id); + EXPECT_EQ(gtest_ipfix_template_info[i].n_elements, utarray_len(template_item->elements_array)); + EXPECT_STREQ(template_item->name, gtest_ipfix_template_info[i].template_name); + } + + ASSERT_TRUE(ipfix_schema->worker_context_array != NULL); + for (int i = 0; i < THREAD_MAX; i++) + { + EXPECT_EQ(ipfix_schema->worker_context_array[i].source_id, (g_domain_id << 16) + i); + EXPECT_EQ(ipfix_schema->worker_context_array[i].sequence, 0); + ASSERT_TRUE(ipfix_schema->worker_context_array[i].template_blob != NULL); + EXPECT_EQ(ipfix_schema->worker_context_array[i].template_blob_length, test_template_blob_len_calculate(ipfix_schema)); + } + + utable_ipfix_exporter_schema_free(ipfix_schema); +} + +TEST(utable_ipfix_exporter_test, ipfix_template_flow_get0_all_elements_check_blob) +{ + struct ipfix_exporter_schema *ipfix_schema = utable_ipfix_exporter_schema_new(ipfix_schema_json_path, g_domain_id, THREAD_MAX); + ASSERT_TRUE(ipfix_schema != NULL); + + for (int i = 0; i < THREAD_MAX; i++) + { + size_t blob_len = 0; + const char *blob = utable_ipfix_template_flow_get0(ipfix_schema, i, &blob_len); + ASSERT_TRUE(blob != NULL); + EXPECT_EQ(blob_len, test_template_blob_len_calculate(ipfix_schema)); + + // check header + struct ipfix_message_head *header = (struct ipfix_message_head *)blob; + struct ipfix_message_head header_value = {}; + header_value.version = htons(header->version); + header_value.length = htons(header->length); + header_value.domain_id = htonl(header->domain_id); + header_value.ipfix_message_sequence = htonl(header->ipfix_message_sequence); + EXPECT_EQ(header_value.version, IPFIX_DEFUALT_VERSION); + EXPECT_EQ(header_value.length, test_template_blob_len_calculate(ipfix_schema)); + EXPECT_EQ(header_value.domain_id, (g_domain_id << 16) + i); + EXPECT_EQ(header_value.ipfix_message_sequence, 0); + + size_t offset = 0; + offset += IPFIX_MESSAGE_HEAD_LEN; + + uint16_t flow_set_id = ntohs(*(uint16_t *)(blob + offset)); + EXPECT_EQ(flow_set_id, IPFIX_TEMPLATE_SET_ID); // template set id + offset += 2; + + uint16_t flow_set_length = ntohs(*(uint16_t *)(blob + offset)); + EXPECT_EQ(flow_set_length, blob_len - IPFIX_MESSAGE_HEAD_LEN); // template set length + offset += 2; + + for (unsigned int j = 0; j < TEMPLATE_MAX; j++) + { + uint16_t cur_template_blob_len = sizeof(uint16_t) + sizeof(uint16_t) + gtest_ipfix_template_info[j].n_elements * IPFIX_ELEMENT_HEAD_LEN; + ASSERT_LE(offset + cur_template_blob_len, blob_len); + + uint16_t template_id = ntohs(*(uint16_t *)(blob + offset)); + EXPECT_EQ(template_id, gtest_ipfix_template_info[j].template_id); // template id + offset += 2; + + uint16_t field_count = ntohs(*(uint16_t *)(blob + offset)); + EXPECT_EQ(field_count, gtest_ipfix_template_info[j].n_elements); // field count + offset += 2; + + struct ipfix_template *template_item = (struct ipfix_template *)utarray_eltptr(ipfix_schema->templates_array, j); + for (unsigned int p = 0; p < (unsigned int)gtest_ipfix_template_info[j].n_elements; p++) + { + struct ipfix_element *element = (struct ipfix_element *)utarray_eltptr(template_item->elements_array, p); + uint16_t PEN_number_flag = ntohs(*(uint16_t *)(blob + offset)) & 0x8000; + EXPECT_EQ(PEN_number_flag, 0x8000); // PEN number flag + + uint16_t element_id = ntohs(*(uint16_t *)(blob + offset)) & 0x7fff; + EXPECT_EQ(element_id, element->element_id); // element id + offset += 2; + + if (element->type == IPFIX_VARIABLE_STRING) + { + uint16_t element_length = ntohs(*(uint16_t *)(blob + offset)); + EXPECT_EQ(element_length, 65535); // element length + } + else + { + uint16_t element_length = ntohs(*(uint16_t *)(blob + offset)); + EXPECT_EQ(element_length, element->value_length); // element length + } + offset += 2; + + uint32_t PEN_number = ntohl(*(uint32_t *)(blob + offset)); + EXPECT_EQ(PEN_number, element->PEN_number); // PEN number + offset += 4; + } + } + } + + utable_ipfix_exporter_schema_free(ipfix_schema); +} + +TEST(utable_ipfix_exporter_test, ipfix_template_flow_get0_all_elements_check_sequence) +{ + struct ipfix_exporter_schema *ipfix_schema = utable_ipfix_exporter_schema_new(ipfix_schema_json_path, g_domain_id, THREAD_MAX); + ASSERT_TRUE(ipfix_schema != NULL); + + for (int i = 0; i < THREAD_MAX; i++) + { + size_t blob_len = 0; + const char *blob = utable_ipfix_template_flow_get0(ipfix_schema, i, &blob_len); + ASSERT_TRUE(blob != NULL); + + // check header + struct ipfix_message_head *header = (struct ipfix_message_head *)blob; + struct ipfix_message_head header_value = {}; + header_value.version = htons(header->version); + header_value.length = htons(header->length); + header_value.domain_id = htonl(header->domain_id); + header_value.ipfix_message_sequence = htonl(header->ipfix_message_sequence); + EXPECT_EQ(header_value.version, IPFIX_DEFUALT_VERSION); + EXPECT_EQ(header_value.length, test_template_blob_len_calculate(ipfix_schema)); + EXPECT_EQ(header_value.domain_id, (g_domain_id << 16) + i); + EXPECT_EQ(header_value.ipfix_message_sequence, 0); + memset(&header_value, 0, sizeof(struct ipfix_message_head)); + header = NULL; + blob = NULL; + + // check sequence, utable_ipfix_template_flow_get0 will not increase sequence + blob = utable_ipfix_template_flow_get0(ipfix_schema, i, &blob_len); + ASSERT_TRUE(blob != NULL); + + // check header + header = (struct ipfix_message_head *)blob; + header_value.ipfix_message_sequence = htonl(header->ipfix_message_sequence); + EXPECT_EQ(header_value.ipfix_message_sequence, 0); + } + + utable_ipfix_exporter_schema_free(ipfix_schema); +} + +TEST(utable_ipfix_exporter_test, ipfix_template_get_not_found) +{ + struct ipfix_exporter_schema *ipfix_schema = utable_ipfix_exporter_schema_new(ipfix_schema_json_path, g_domain_id, THREAD_MAX); + ASSERT_TRUE(ipfix_schema != NULL); + + int template_id = utable_ipfix_template_get(ipfix_schema, "test"); + EXPECT_EQ(template_id, -1); + + utable_ipfix_exporter_schema_free(ipfix_schema); +} + +TEST(utable_ipfix_exporter_test, ipfix_template_get_check_template_id) +{ + struct ipfix_exporter_schema *ipfix_schema = utable_ipfix_exporter_schema_new(ipfix_schema_json_path, g_domain_id, THREAD_MAX); + ASSERT_TRUE(ipfix_schema != NULL); + + int template_id = 0; + for (unsigned int i = 0; i < TEMPLATE_MAX; i++) + { + template_id = utable_ipfix_template_get(ipfix_schema, gtest_ipfix_template_info[i].template_name); + EXPECT_EQ(template_id, i); + + struct ipfix_template *template_item = (struct ipfix_template *)utarray_eltptr(ipfix_schema->templates_array, i); + ASSERT_TRUE(template_item != NULL); + } + + utable_ipfix_exporter_schema_free(ipfix_schema); +} + +cJSON *test_ipifx_data_blob_to_cjson(struct ipfix_template *template_item, char *blob, size_t sz_blob) +{ + cJSON *root = cJSON_CreateObject(); + + size_t offset = IPFIX_MESSAGE_HEAD_LEN + sizeof(uint16_t) + sizeof(uint16_t); // skip message head and flow set id and flow set length + size_t n_elements = utarray_len(template_item->elements_array); + for (size_t i = 0; i < n_elements; i++) + { + struct ipfix_element *p_element = (struct ipfix_element *)utarray_eltptr(template_item->elements_array, i); + switch (p_element->type) + { + case IPFIX_UNSIGNED8: + { + cJSON_AddNumberToObject(root, p_element->name, *(uint8_t *)(blob + offset)); + offset += sizeof(uint8_t); + break; + } + case IPFIX_UNSIGNED16: + { + cJSON_AddNumberToObject(root, p_element->name, ntohs(*(uint16_t *)(blob + offset))); + offset += sizeof(uint16_t); + break; + } + case IPFIX_UNSIGNED32: + { + cJSON_AddNumberToObject(root, p_element->name, ntohl(*(uint32_t *)(blob + offset))); + offset += sizeof(uint32_t); + break; + } + case IPFIX_UNSIGNED64: + { + cJSON_AddNumberToObject(root, p_element->name, be64toh(*(uint64_t *)(blob + offset))); + offset += sizeof(uint64_t); + break; + } + case IPFIX_VARIABLE_STRING: + { + uint8_t sz_value = *(uint8_t *)(blob + offset); + offset += sizeof(uint8_t); + char *string_value = NULL; + if (sz_value == 0) + { + break; + } + else if (sz_value < 255) + { + string_value = (char *)malloc(sizeof(char) * (sz_value + 1)); + memcpy(string_value, blob + offset, sz_value); + string_value[sz_value] = '\0'; + offset += sz_value; + } + else // >= 255 + { + uint16_t sz_long_string_value = ntohs(*(uint16_t *)(blob + offset)); + offset += sizeof(uint16_t); + string_value = (char *)malloc(sizeof(char) * (sz_long_string_value + 1)); + memcpy(string_value, blob + offset, sz_long_string_value); + string_value[sz_long_string_value] = '\0'; + offset += sz_long_string_value; + } + cJSON_AddStringToObject(root, p_element->name, string_value); + free(string_value); + break; + } + default: + break; + } + } + + return root; +} + +// after include before, and the extra part is cJSON_Number, but valueint = 0; +void test_ipfix_compare_cjson(cJSON *before, cJSON *after) +{ + // find before's element in after + cJSON *item = before->child; + while (item != NULL) + { + if (item->type == cJSON_Number) + { + cJSON *item_after = cJSON_GetObjectItem(after, item->string); + ASSERT_TRUE(item_after != NULL); + EXPECT_EQ(item->valueint, item_after->valueint); + } + if (item->type == cJSON_String) + { + cJSON *item_after = cJSON_GetObjectItem(after, item->string); + ASSERT_TRUE(item_after != NULL); + EXPECT_STREQ(item->valuestring, item_after->valuestring); + } + item = item->next; + } + + // find after's element in before + item = after->child; + while (item != NULL) + { + if (item->type == cJSON_Number) // if after's element is cJSON_Number, before's element may be empty or cJSON_Number + { + cJSON *item_before = cJSON_GetObjectItem(before, item->string); + if (item_before == NULL) // if before's element is empty, after's element valueint must be 0 + { + EXPECT_EQ(item->valueint, 0); + } + else + { + EXPECT_EQ(item->valueint, item_before->valueint); // if before's element is cJSON_Number, after's element valueint must be equal to before's element valueint + } + } + if (item->type == cJSON_String) + { + cJSON *item_before = cJSON_GetObjectItem(before, item->string); + ASSERT_TRUE(item_before != NULL); + EXPECT_STREQ(item->valuestring, item_before->valuestring); + } + item = item->next; + } +} + +int test_empty_data_blob_len_calculate(struct ipfix_exporter_schema *ipfix_schema, int template_id) +{ + struct ipfix_template *template_item = (struct ipfix_template *)utarray_eltptr(ipfix_schema->templates_array, (unsigned int)template_id); + int data_blob_length = 0; + data_blob_length += IPFIX_MESSAGE_HEAD_LEN; // message head + data_blob_length += sizeof(uint16_t) + sizeof(uint16_t); // flow set id + flow set length + for (unsigned int i = 0; i < utarray_len(template_item->elements_array); i++) + { + struct ipfix_element *element = (struct ipfix_element *)utarray_eltptr(template_item->elements_array, i); + if (element->type == IPFIX_VARIABLE_STRING) + { + data_blob_length += 1; + } + else + { + data_blob_length += element->value_length; // element length + } + } + + return data_blob_length; +} + +TEST(utable_ipfix_exporter_test, ipfix_data_flow_empty_utable_check_blob) +{ + struct ipfix_exporter_schema *ipfix_schema = utable_ipfix_exporter_schema_new(ipfix_schema_json_path, g_domain_id, THREAD_MAX); + ASSERT_TRUE(ipfix_schema != NULL); + + for (unsigned int i = 0; i < TEMPLATE_MAX; i++) + { + cJSON *before = cJSON_CreateObject(); + struct utable *table = utable_new(); + int template_id = utable_ipfix_template_get(ipfix_schema, gtest_ipfix_template_info[i].template_name); + ASSERT_EQ(template_id, i); + + for (unsigned int j = 0; j < THREAD_MAX; j++) + { + size_t blob_len = 0; + char *blob = NULL; + utable_ipfix_data_flow_exporter(table, ipfix_schema, template_id, j, &blob, &blob_len); + ASSERT_TRUE(blob != NULL); + + struct ipfix_message_head *header = (struct ipfix_message_head *)blob; + struct ipfix_message_head header_value = {}; + header_value.version = htons(header->version); + header_value.length = htons(header->length); + header_value.domain_id = htonl(header->domain_id); + header_value.ipfix_message_sequence = htonl(header->ipfix_message_sequence); + + ASSERT_EQ(header_value.version, IPFIX_DEFUALT_VERSION); + ASSERT_EQ(header_value.length, blob_len); + ASSERT_EQ(header_value.length, test_empty_data_blob_len_calculate(ipfix_schema, i)); + ASSERT_EQ(header_value.domain_id, (g_domain_id << 16) + j); + ASSERT_EQ(header_value.ipfix_message_sequence, i); + + cJSON *after = test_ipifx_data_blob_to_cjson((struct ipfix_template *)utarray_eltptr(ipfix_schema->templates_array, i), blob, blob_len); + test_ipfix_compare_cjson(before, after); + cJSON_Delete(after); + free(blob); + blob = NULL; + } + + cJSON_Delete(before); + utable_free(table); + } + + utable_ipfix_exporter_schema_free(ipfix_schema); +} + +TEST(utable_ipfix_exporter_test, ipfix_data_flow_utable_value_type_integer) +{ + struct ipfix_exporter_schema *ipfix_schema = utable_ipfix_exporter_schema_new(ipfix_schema_json_path, g_domain_id, THREAD_MAX); + ASSERT_TRUE(ipfix_schema != NULL); + + cJSON *before = cJSON_CreateObject(); + struct utable *table = utable_new(); + utable_add_integer(table, "session_id", 123456789); + cJSON_AddNumberToObject(before, "session_id", 123456789); + + unsigned int template_id = utable_ipfix_template_get(ipfix_schema, "HTTP"); + char *blob = NULL; + size_t sz_blob = 0; + utable_ipfix_data_flow_exporter(table, ipfix_schema, template_id, 0, &blob, &sz_blob); + ASSERT_EQ(sz_blob, test_empty_data_blob_len_calculate(ipfix_schema, template_id)); // integer value length has been calculated in test_empty_data_blob_len_calculate + + cJSON *after = test_ipifx_data_blob_to_cjson((struct ipfix_template *)utarray_eltptr(ipfix_schema->templates_array, template_id), blob, sz_blob); + test_ipfix_compare_cjson(before, after); + + cJSON_Delete(before); + cJSON_Delete(after); + utable_free(table); + free(blob); + utable_ipfix_exporter_schema_free(ipfix_schema); +} + +TEST(utable_ipfix_exporter_test, ipfix_data_flow_utable_value_type_cstring) +{ + struct ipfix_exporter_schema *ipfix_schema = utable_ipfix_exporter_schema_new(ipfix_schema_json_path, g_domain_id, THREAD_MAX); + ASSERT_TRUE(ipfix_schema != NULL); + + cJSON *before = cJSON_CreateObject(); + struct utable *table = utable_new(); + utable_add_integer(table, "session_id", 123456789); + cJSON_AddNumberToObject(before, "session_id", 123456789); + + utable_add_cstring(table, "http_url", "http://www.baidu.com", strlen("http://www.baidu.com")); + cJSON_AddStringToObject(before, "http_url", "http://www.baidu.com"); + + unsigned int template_id = utable_ipfix_template_get(ipfix_schema, "HTTP"); + char *blob = NULL; + size_t sz_blob = 0; + utable_ipfix_data_flow_exporter(table, ipfix_schema, template_id, 0, &blob, &sz_blob); + ASSERT_EQ(sz_blob, test_empty_data_blob_len_calculate(ipfix_schema, template_id) + strlen("http://www.baidu.com")); // variable_string length need to be calculated + + cJSON *after = test_ipifx_data_blob_to_cjson((struct ipfix_template *)utarray_eltptr(ipfix_schema->templates_array, template_id), blob, sz_blob); + test_ipfix_compare_cjson(before, after); + + cJSON_Delete(before); + cJSON_Delete(after); + utable_free(table); + free(blob); + utable_ipfix_exporter_schema_free(ipfix_schema); +} + +char http_url_test[] = "storeapi.app-vtion.com/storeApi/systemApp/checkAppStatus.json?store=com.tcl.appmarket2&mac=3C:59:1E:DC:F8:F4&accessKeyId=1472540256617"; +TEST(utable_ipfix_exporter_test, ipfix_data_flow_variable_string_6000) // 6000 bytes > 2 * IPFIX_BUFF_MAX_SIZE, test variable_string length > 255 and realloc +{ + struct ipfix_exporter_schema *ipfix_schema = utable_ipfix_exporter_schema_new(ipfix_schema_json_path, g_domain_id, THREAD_MAX); + ASSERT_TRUE(ipfix_schema != NULL); + + char *http_url = NULL; + struct utable *table = utable_new(); + cJSON *before = cJSON_CreateObject(); + utable_add_cstring(table, "decoded_as", "HTTP", strlen("HTTP")); + cJSON_AddStringToObject(before, "decoded_as", "HTTP"); + + http_url = (char *)malloc(sizeof(char) * 7000); + + int offset = 0; + while (offset < 6000) + { + memcpy(http_url + offset, http_url_test, sizeof(http_url_test)); + offset += sizeof(http_url_test); + } + utable_add_cstring(table, "http_url", http_url, offset); + cJSON_AddStringToObject(before, "http_url", http_url); + + unsigned int template_id = utable_ipfix_template_get(ipfix_schema, "HTTP"); + char *blob = NULL; + size_t sz_blob = 0; + utable_ipfix_data_flow_exporter(table, ipfix_schema, template_id, 0, &blob, &sz_blob); + ASSERT_EQ(sz_blob, test_empty_data_blob_len_calculate(ipfix_schema, template_id) + offset + strlen("HTTP") + 2); // variable_string length need to be calculated, if length > 255, length + 2 + + cJSON *after = test_ipifx_data_blob_to_cjson((struct ipfix_template *)utarray_eltptr(ipfix_schema->templates_array, template_id), blob, sz_blob); + test_ipfix_compare_cjson(before, after); + + cJSON_Delete(before); + cJSON_Delete(after); + utable_free(table); + free(blob); + free(http_url); + utable_ipfix_exporter_schema_free(ipfix_schema); +} + +TEST(utable_ipfix_exporter_test, ipfix_data_flow_utable_value_type_integer_array) +{ + struct ipfix_exporter_schema *ipfix_schema = utable_ipfix_exporter_schema_new(ipfix_schema_json_path, g_domain_id, THREAD_MAX); + ASSERT_TRUE(ipfix_schema != NULL); + + cJSON *before = cJSON_CreateObject(); + struct utable *table = utable_new(); + utable_add_integer(table, "session_id", 123456789); + cJSON_AddNumberToObject(before, "session_id", 123456789); + + int64_t monitor_rule_list[8] = {1, 2, 3, 4, 5, 6, 7, 8}; + utable_add_integer_array(table, "monitor_rule_list", monitor_rule_list, 8); + cJSON_AddStringToObject(before, "monitor_rule_list", "[1,2,3,4,5,6,7,8]"); + + unsigned int template_id = utable_ipfix_template_get(ipfix_schema, "HTTP"); + char *blob = NULL; + size_t sz_blob = 0; + utable_ipfix_data_flow_exporter(table, ipfix_schema, template_id, 0, &blob, &sz_blob); + ASSERT_EQ(sz_blob, test_empty_data_blob_len_calculate(ipfix_schema, template_id) + strlen("[1,2,3,4,5,6,7,8]")); // variable_string length need to be calculated + + cJSON *after = test_ipifx_data_blob_to_cjson((struct ipfix_template *)utarray_eltptr(ipfix_schema->templates_array, template_id), blob, sz_blob); + test_ipfix_compare_cjson(before, after); + + cJSON_Delete(before); + cJSON_Delete(after); + utable_free(table); + free(blob); + utable_ipfix_exporter_schema_free(ipfix_schema); +} + +extern "C" int load_file_to_memory(const char *file_name, unsigned char **pp_out, size_t *out_sz); +cJSON *ipfix_init_utable_from_log_json(struct utable *table, const char *test_json_path, size_t *sz_blob) +{ + size_t json_size = 0; + unsigned char *json_str = NULL; + load_file_to_memory(test_json_path, &json_str, &json_size); + if (json_str == NULL || json_size == 0) + { + return NULL; + } + + cJSON *root = NULL; + root = cJSON_Parse((const char *)json_str); + cJSON *item = root->child; + while (item != NULL) + { + switch (item->type) + { + case cJSON_Number: + { + utable_add_integer(table, item->string, item->valueint); + break; + } + case cJSON_String: + { + int sz_value = strlen(item->valuestring); + utable_add_cstring(table, item->string, item->valuestring, sz_value); + if (sz_value < 255) + { + *sz_blob += strlen(item->valuestring); + } + else + { + *sz_blob += (strlen(item->valuestring) + 2); + } + break; + } + case cJSON_Array: + { + cJSON *array_item = cJSON_GetArrayItem(item, 0); + size_t array_sz = cJSON_GetArraySize(item); + if (array_sz > 0) + { + if (array_item->type == cJSON_Number) + { + int *array = (int *)malloc(sizeof(int) * array_sz); + int64_t *long_array = (int64_t *)malloc(sizeof(int64_t) * array_sz); + for (size_t i = 0; i < array_sz; i++) + { + array_item = cJSON_GetArrayItem(item, i); + if (array_item->type == cJSON_Number) + { + array[i] = array_item->valueint; + long_array[i] = array_item->valueint; + } + } + utable_add_integer_array(table, item->string, long_array, array_sz); + + cJSON *json_array = cJSON_CreateIntArray((const int *)array, array_sz); + cJSON_Delete(item->child); + item->type = cJSON_String; + item->child = NULL; + item->valuestring = cJSON_PrintUnformatted(json_array); + cJSON_Delete(json_array); + + free(array); + free(long_array); + } + if (array_item->type == cJSON_String) + { + char **array = (char **)malloc(sizeof(char *) * array_sz); + size_t *sz_value = (size_t *)malloc(sizeof(size_t) * array_sz); + for (size_t i = 0; i < array_sz; i++) + { + array_item = cJSON_GetArrayItem(item, i); + if (array_item->type == cJSON_String) + { + sz_value[i] = strlen(array_item->valuestring); + array[i] = strdup(array_item->valuestring); + } + } + utable_add_cstring_array(table, item->string, (const char **)array, sz_value, array_sz); + + cJSON *json_array = cJSON_CreateStringArray((const char **)array, array_sz); + item->valuestring = cJSON_PrintUnformatted(json_array); + item->type = cJSON_String; + cJSON_Delete(item->child); + item->child = NULL; + cJSON_Delete(json_array); + + for (size_t i = 0; i < array_sz; i++) + { + free(array[i]); + } + free(array); + } + int sz_value = strlen(item->valuestring); + if (sz_value < 255) + { + *sz_blob += sz_value; + } + else + { + *sz_blob += (sz_value + 2); + } + } + break; + } + default: + break; + } + item = item->next; + } + free(json_str); + return root; +} + +const char *ssl_test_json_path = "./conf/ipfix_ssl_test.json"; +TEST(utable_ipfix_exporter_test, ipfix_data_flow_ssl_log_test) +{ + struct ipfix_exporter_schema *ipfix_schema = utable_ipfix_exporter_schema_new(ipfix_schema_json_path, g_domain_id, THREAD_MAX); + ASSERT_TRUE(ipfix_schema != NULL); + + struct utable *table = utable_new(); + unsigned int template_id = utable_ipfix_template_get(ipfix_schema, "SSL"); + size_t predict_sz_blob = test_empty_data_blob_len_calculate(ipfix_schema, template_id); + + cJSON *before_json_root = ipfix_init_utable_from_log_json(table, ssl_test_json_path, &predict_sz_blob); + + char *blob = NULL; + size_t sz_blob = 0; + utable_ipfix_data_flow_exporter(table, ipfix_schema, template_id, 0, &blob, &sz_blob); + ASSERT_EQ(sz_blob, predict_sz_blob); + + cJSON *after_json_root = test_ipifx_data_blob_to_cjson((struct ipfix_template *)utarray_eltptr(ipfix_schema->templates_array, template_id), blob, sz_blob); + + char *before_json_str = cJSON_Print(before_json_root); + char *after_json_str = cJSON_Print(after_json_root); + printf("before_json: %s\n", before_json_str); + printf("after_json: %s\n", after_json_str); + free(before_json_str); + free(after_json_str); + + test_ipfix_compare_cjson(before_json_root, after_json_root); + + cJSON_Delete(before_json_root); + cJSON_Delete(after_json_root); + free(blob); + utable_free(table); + utable_ipfix_exporter_schema_free(ipfix_schema); +} + +const char *http_test_json_path = "./conf/ipfix_http_test.json"; +TEST(utable_ipfix_exporter_test, ipfix_data_flow_http_log_test) +{ + struct ipfix_exporter_schema *ipfix_schema = utable_ipfix_exporter_schema_new(ipfix_schema_json_path, g_domain_id, THREAD_MAX); + ASSERT_TRUE(ipfix_schema != NULL); + + struct utable *table = utable_new(); + unsigned int template_id = utable_ipfix_template_get(ipfix_schema, "HTTP"); + size_t predict_sz_blob = test_empty_data_blob_len_calculate(ipfix_schema, template_id); + + cJSON *before_json_root = ipfix_init_utable_from_log_json(table, http_test_json_path, &predict_sz_blob); + + char *blob = NULL; + size_t sz_blob = 0; + utable_ipfix_data_flow_exporter(table, ipfix_schema, template_id, 0, &blob, &sz_blob); + ASSERT_EQ(sz_blob, predict_sz_blob); + + cJSON *after_json_root = test_ipifx_data_blob_to_cjson((struct ipfix_template *)utarray_eltptr(ipfix_schema->templates_array, template_id), blob, sz_blob); + test_ipfix_compare_cjson(before_json_root, after_json_root); + + cJSON_Delete(before_json_root); + cJSON_Delete(after_json_root); + free(blob); + utable_free(table); + utable_ipfix_exporter_schema_free(ipfix_schema); +} + +int main(int argc, char *argv[]) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/deps/utable/test/unit_test_utable.cpp b/deps/utable/test/unit_test_utable.cpp new file mode 100644 index 0000000..f264372 --- /dev/null +++ b/deps/utable/test/unit_test_utable.cpp @@ -0,0 +1,773 @@ + +#include + +#include "base64/b64.h" +#include "utable/utable.h" +#include "cjson/cJSON.h" +#include "mpack/mpack.h" + + + +void test_utable_assert_arr(const struct utable *table, const char *key, int64_t expected_arr[], size_t n_expected_arr) +{ + int64_t *arr = NULL; + size_t n_arr = 0; + EXPECT_EQ(utable_get_value_type(table, key), utable_value_type_integer_array); + utable_get0_integer_value_array(table, key, &arr, &n_arr); + EXPECT_EQ(n_arr, n_expected_arr); + for (size_t i = 0; i < n_arr; i++) { + EXPECT_EQ(expected_arr[i], arr[i]); + } +} + +void test_utable_assert_str(const struct utable *table, const char *key, const char *expected_str) +{ + char *str = NULL; + size_t str_len = 0; + EXPECT_EQ(utable_get_value_type(table, key), utable_value_type_cstring); + utable_get0_cstring_value(table, key, &str, &str_len); + EXPECT_EQ(str_len, strlen(expected_str)); + EXPECT_EQ(strncmp(str, expected_str, str_len), 0); +} + +void test_utable_assert_str_array(const struct utable *table, const char *key, const char **expected_str_array, size_t n_expected_str) +{ + char **str_array = NULL; + size_t *str_len = NULL; + size_t n_str = 0; + EXPECT_EQ(utable_get_value_type(table, key), utable_value_type_cstring_array); + utable_get0_cstring_value_array(table, key, &str_array, &str_len, &n_str); + EXPECT_EQ(n_str, n_expected_str); + for (size_t i = 0; i < n_str; i++) { + EXPECT_EQ(str_len[i], strlen(expected_str_array[i])); + EXPECT_EQ(strncmp(str_array[i], expected_str_array[i], str_len[i]), 0); + } +} + +void test_utable_assert_blob(const struct utable *table, const char *key, const char *expected_blob, size_t expected_blob_len) +{ + char *blob = NULL; + size_t blob_len = 0; + EXPECT_EQ(utable_get_value_type(table, key), utable_value_type_blob); + utable_get0_blob_value(table, key, &blob, &blob_len); + EXPECT_EQ(blob_len, expected_blob_len); + EXPECT_EQ(memcmp(blob, expected_blob, blob_len), 0); +} + +TEST(utable_test, new_del_when_empty) +{ + struct utable *table = utable_new(); + ASSERT_TRUE(table != NULL); + utable_free(table); +} + +TEST(utable_test, add_1_int_and_query) +{ + struct utable *table = utable_new(); + int64_t int_array[] = { 1 }; + utable_add_integer_array(table, "key", int_array, 1); + + test_utable_assert_arr(table, "key", int_array, 1); + + struct utable_stat A={}; + struct utable_stat B={}; + B.n_item=1; + B.n_item_size=strlen("key")+sizeof(int64_t); + B.n_integer_array=1; + B.n_integer_array_size=sizeof(int64_t); + utable_stat(table, &A); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + utable_free(table); +} + +TEST(utable_test, add_2_int_and_query) +{ + struct utable *table = utable_new(); + int64_t int_array[] = { 1, 2 }; + utable_add_integer_array(table, "key", int_array, 2); + test_utable_assert_arr(table, "key", int_array, 2); + + struct utable_stat A={}; + struct utable_stat B={}; + B.n_item=1; + B.n_item_size=strlen("key")+sizeof(int64_t)*2; + B.n_integer_array=1; + B.n_integer_array_size=sizeof(int64_t)*2; + utable_stat(table, &A); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + utable_free(table); +} + +TEST(utable_test, add_string_and_query) +{ + struct utable *table = utable_new(); + const char *str = "hello world"; + utable_add_cstring(table, "key", str, strlen(str)); + test_utable_assert_str(table, "key", str); + + struct utable_stat A={}; + struct utable_stat B={}; + B.n_item=1; + B.n_item_size=strlen("key")+strlen(str); + B.n_cstring=1; + B.n_cstring_size=strlen(str); + utable_stat(table, &A); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + utable_free(table); +} + +TEST(utable_test, add_string_array_and_query) +{ + struct utable *table = utable_new(); + const char *str[] = {"hello world", "foo bar"}; + size_t n_str=sizeof(str)/sizeof(str[0]); + size_t str_sz[] = {strlen(str[0]), strlen(str[1])}; + utable_add_cstring_array(table, "key", str, str_sz, n_str); + test_utable_assert_str_array(table, "key", str, n_str); + + struct utable_stat A={}; + struct utable_stat B={}; + B.n_item=1; + B.n_item_size=strlen("key")+str_sz[0]+str_sz[1]; + B.n_cstring_array=1; + B.n_cstring_array_size=str_sz[0]+str_sz[1]; + utable_stat(table, &A); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + utable_free(table); +} + +TEST(utable_test, add_blob_and_query) +{ + struct utable *table = utable_new(); + const char *blob = "hello world"; + size_t blob_len = strlen(blob); + utable_add_blob(table, "key", blob, blob_len); + test_utable_assert_blob(table, "key", blob, blob_len); + + struct utable_stat A={}; + struct utable_stat B={}; + B.n_item=1; + B.n_item_size=strlen("key")+blob_len; + B.n_blob=1; + B.n_blob_size=blob_len; + utable_stat(table, &A); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + utable_free(table); +} + +void test_utable_check_if_value_in_json(cJSON *tag_obj, const char *key, const std::string &value) +{ + cJSON *tag_val = cJSON_GetObjectItem(tag_obj, key); + EXPECT_NE(tag_val, nullptr); + EXPECT_STREQ(tag_val->valuestring, value.c_str()); +} + +void test_utable_check_if_value_in_json(cJSON *tag_obj, const char *key, const char *value[], size_t n_value) +{ + cJSON *tag_val = cJSON_GetObjectItem(tag_obj, key); + EXPECT_NE(tag_val, nullptr); + EXPECT_EQ(tag_val->type, cJSON_Array); + int arr_size = cJSON_GetArraySize(tag_val); + EXPECT_EQ(arr_size, n_value); + for (int i = 0; i < arr_size; i++) { + cJSON *arr_item = cJSON_GetArrayItem(tag_val, i); + EXPECT_STREQ(arr_item->valuestring, value[i]); + } +} + +void test_utable_check_if_value_in_json(cJSON *tag_obj, const char *key, int64_t value[], size_t n_value) +{ + cJSON *tag_val = cJSON_GetObjectItem(tag_obj, key); + EXPECT_NE(tag_val, nullptr); + EXPECT_EQ(tag_val->type, cJSON_Array); + int arr_size = cJSON_GetArraySize(tag_val); + EXPECT_EQ(arr_size, n_value); + for (int i = 0; i < arr_size; i++) { + cJSON *arr_item = cJSON_GetArrayItem(tag_val, i); + EXPECT_EQ(arr_item->valueint, value[i]); + } +} + +void test_utable_check_if_value_in_json(cJSON *tag_obj, const char *key, const char *value, size_t blob_len) +{ + cJSON *tag_val = cJSON_GetObjectItem(tag_obj, key); + EXPECT_NE(tag_val, nullptr); + // the tag_val is encoded in base64 + size_t dec_size = 0; + unsigned char *dec = b64_decode_ex(tag_val->valuestring, strlen(tag_val->valuestring), &dec_size); + EXPECT_EQ(dec_size, blob_len); + EXPECT_EQ(memcmp(dec, value, blob_len), 0); + + free(dec); +} + +TEST(utable_test, export_json) +{ + struct utable *table = utable_new(); + int64_t int_array[] = { 1, 2 }; + const char *str = "hello world"; + const char *blob = "bin hello world"; + + const char *str_array[] = {"hello world", "foo bar"}; + size_t n_str=sizeof(str_array)/sizeof(str_array[0]); + size_t str_sz[] = {strlen(str_array[0]), strlen(str_array[1])}; + + utable_add_integer_array(table, "key1", int_array, 2); + utable_add_cstring(table, "key2", str, strlen(str)); + utable_add_blob(table, "key3", blob, strlen(blob)); + utable_add_cstring_array(table, "key4", str_array, str_sz, n_str); + + char *json = NULL; + size_t json_len = 0; + EXPECT_EQ(utable_json_export(table, &json, &json_len), 0); + EXPECT_NE(nullptr, json); + cJSON *root = cJSON_Parse(json); + test_utable_check_if_value_in_json(root, "key1", int_array, 2); + std::string expected_str(str); + test_utable_check_if_value_in_json(root, "key2", expected_str); + test_utable_check_if_value_in_json(root, "key3", blob, strlen(blob)); + + test_utable_check_if_value_in_json(root, "key4", str_array, n_str); + + struct utable_stat A={}; + struct utable_stat B={}; + B.n_item=4; + B.n_item_size=strlen("key1")+sizeof(int64_t)*2+strlen("key2")+strlen(str)+strlen("key3")+strlen(blob)+strlen("key4")+str_sz[0]+str_sz[1]; + B.n_blob=1; + B.n_blob_size=strlen(blob); + B.n_cstring=1; + B.n_cstring_size=strlen(str); + B.n_integer_array=1; + B.n_integer_array_size=sizeof(int64_t)*2; + B.n_integer=0; + B.n_integer_size=0; + B.n_cstring_array=1; + B.n_cstring_array_size=str_sz[0]+str_sz[1]; + utable_stat(table, &A); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + utable_free(table); + free(json); + cJSON_Delete(root); +} + + +TEST(utable_test, export_invalid_unicode_string_to_json) +{ + struct utable *table = utable_new(); + const char *str = "googleads.g.doublec\x86rck.net"; + utable_add_cstring(table, "key2", str, strlen(str)); + + char *json = NULL; + size_t json_len = 0; + EXPECT_EQ(utable_json_export(table, &json, &json_len), 0); + EXPECT_NE(nullptr, json); + cJSON *root = cJSON_Parse(json); + std::string expected_str(str); + test_utable_check_if_value_in_json(root, "key2", expected_str); + + struct utable_stat A={}; + struct utable_stat B={}; + B.n_item=1; + B.n_item_size=strlen("key2")+strlen(str); + B.n_cstring=1; + B.n_cstring_size=strlen(str); + utable_stat(table, &A); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + utable_free(table); + free(json); + cJSON_Delete(root); +} + +char *test_get_cstring_from_mpack_node(mpack_node_t node) +{ + size_t len = mpack_node_strlen(node); + char *str = (char *)malloc(len + 1); + memcpy(str, mpack_node_str(node), len); + str[len] = '\0'; + return str; +} + +void test_utable_check_if_value_in_mpack(mpack_node_t item, const char *key, const std::string &value) +{ + mpack_node_t key_node = mpack_node_map_cstr(item, "key"); + mpack_node_t type_node = mpack_node_map_cstr(item, "type"); + mpack_node_t value_node = mpack_node_map_cstr(item, "value"); + + char *key_tmp = test_get_cstring_from_mpack_node(key_node); + char *type_tmp = test_get_cstring_from_mpack_node(type_node); + char *value_tmp = test_get_cstring_from_mpack_node(value_node); + EXPECT_STREQ(key_tmp, key); + EXPECT_STREQ(type_tmp, "c_str"); + EXPECT_STREQ(value_tmp, value.c_str()); + + free(key_tmp); + free(type_tmp); + free(value_tmp); +} + +void test_utable_check_if_value_in_mpack(mpack_node_t item, const char *key, int64_t value[], size_t n_value) +{ + mpack_node_t key_node = mpack_node_map_cstr(item, "key"); + mpack_node_t type_node = mpack_node_map_cstr(item, "type"); + mpack_node_t value_node = mpack_node_map_cstr(item, "value"); + char *key_tmp = test_get_cstring_from_mpack_node(key_node); + char *type_tmp = test_get_cstring_from_mpack_node(type_node); + + EXPECT_STREQ(key_tmp, key); + EXPECT_STREQ(type_tmp, "i_arr"); + EXPECT_EQ(mpack_node_array_length(value_node), n_value); + for (size_t i = 0; i < n_value; i++) { + mpack_node_t tmp_v_node = mpack_node_array_at(value_node, i); + EXPECT_EQ(mpack_node_i64(tmp_v_node), value[i]); + } + free(key_tmp); + free(type_tmp); +} + +void test_utable_check_if_value_in_mpack(mpack_node_t item, const char *key, const char *value, size_t blob_len) +{ + mpack_node_t key_node = mpack_node_map_cstr(item, "key"); + mpack_node_t type_node = mpack_node_map_cstr(item, "type"); + mpack_node_t value_node = mpack_node_map_cstr(item, "value"); + char *key_tmp = test_get_cstring_from_mpack_node(key_node); + char *type_tmp = test_get_cstring_from_mpack_node(type_node); + EXPECT_STREQ(key_tmp, key); + EXPECT_STREQ(type_tmp, "blob"); + EXPECT_EQ(mpack_node_bin_size(value_node), blob_len); + EXPECT_EQ(memcmp(mpack_node_bin_data(value_node), value, blob_len), 0); + + free(key_tmp); + free(type_tmp); +} + +TEST(utable_test, DISABLED_export_mpack) +{ + struct utable *table = utable_new(); + int64_t int_array[] = { 1, 2 }; + const char *str = "hello world"; + const char *blob = "bin hello world"; + utable_add_integer_array(table, "key1", int_array, 2); + utable_add_cstring(table, "key2", str, strlen(str)); + utable_add_blob(table, "key3", blob, strlen(blob)); + + char *mpack = NULL; + size_t mpack_len = 0; + EXPECT_EQ(utable_msgpack_export(table, &mpack, &mpack_len), 0); + + + mpack_tree_t tree; + mpack_tree_init_data(&tree, mpack, mpack_len); + mpack_tree_parse(&tree); + mpack_node_t root = mpack_tree_root(&tree); + size_t n_item = mpack_node_array_length(root); + EXPECT_EQ(n_item, 3); + + test_utable_check_if_value_in_mpack(mpack_node_array_at(root, 0), "key1", int_array, 2); + std::string expected_str(str); + test_utable_check_if_value_in_mpack(mpack_node_array_at(root, 1), "key2", expected_str); + test_utable_check_if_value_in_mpack(mpack_node_array_at(root, 2), "key3", blob, strlen(blob)); + + utable_free(table); + free(mpack); + mpack_tree_destroy(&tree); +} + + +TEST(utable_test, add_blob_then_del) +{ + struct utable *table = utable_new(); + const char *blob = "hello world"; + size_t blob_len = strlen(blob); + utable_add_blob(table, "key", blob, blob_len); + test_utable_assert_blob(table, "key", blob, blob_len); + + struct utable_stat A={}; + struct utable_stat B={}; + B.n_item=1; + B.n_item_size=strlen("key")+strlen(blob); + B.n_blob=1; + B.n_blob_size=strlen(blob); + utable_stat(table, &A); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + char *value; + size_t value_len; + utable_delete_item(table, "key"); + EXPECT_EQ(utable_get0_blob_value(table, "key", &value, &value_len), -1); + EXPECT_EQ(utable_get0_cstring_value(table, "key", &value, &value_len), -1); + int64_t *value_array; + size_t n_value; + EXPECT_EQ(utable_get0_integer_value_array(table, "key", &value_array, &n_value), -1); + EXPECT_EQ(utable_get_value_type(table, "key"), utable_value_type_undefined); + + struct utable_stat C={}; + utable_stat(table, &A); + EXPECT_EQ(memcmp(&A, &C, sizeof(struct utable_stat)), 0); + + utable_free(table); +} + + +TEST(utable_test, add_string_array_then_del) +{ + struct utable *table = utable_new(); + const char *str[] = {"hello world", "foo bar"}; + size_t n_str=sizeof(str)/sizeof(str[0]); + size_t str_sz[] = {strlen(str[0]), strlen(str[1])}; + utable_add_cstring_array(table, "key", str, str_sz, n_str); + test_utable_assert_str_array(table, "key", str, n_str); + + struct utable_stat A={}; + struct utable_stat B={}; + B.n_item=1; + B.n_item_size=strlen("key")+str_sz[0]+str_sz[1]; + B.n_cstring_array=1; + B.n_cstring_array_size=str_sz[0]+str_sz[1]; + utable_stat(table, &A); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + char *value; + size_t value_len; + utable_delete_item(table, "key"); + EXPECT_EQ(utable_get0_blob_value(table, "key", &value, &value_len), -1); + EXPECT_EQ(utable_get0_cstring_value(table, "key", &value, &value_len), -1); + int64_t *value_array; + size_t n_value; + EXPECT_EQ(utable_get0_integer_value_array(table, "key", &value_array, &n_value), -1); + + char **str_array = NULL; + size_t *str_len = NULL; + n_str = 0; + EXPECT_EQ(utable_get0_cstring_value_array(table, "key", &str_array, &str_len, &n_str), -1); + + EXPECT_EQ(utable_get_value_type(table, "key"), utable_value_type_undefined); + + struct utable_stat C={}; + utable_stat(table, &A); + EXPECT_EQ(memcmp(&A, &C, sizeof(struct utable_stat)), 0); + + utable_free(table); +} + +TEST(utable_test, query_on_wrong_key) +{ + struct utable *table = utable_new(); + + char *value; + size_t value_len; + EXPECT_EQ(utable_get0_blob_value(table, "key", &value, &value_len), -1); + EXPECT_EQ(utable_get0_cstring_value(table, "key", &value, &value_len), -1); + int64_t *value_array; + size_t n_value; + EXPECT_EQ(utable_get0_integer_value_array(table, "key", &value_array, &n_value), -1); + EXPECT_EQ(utable_get_value_type(table, "key"), utable_value_type_undefined); + + utable_free(table); +} + +TEST(utable_test, add_on_dup_key) +{ + struct utable *table = utable_new(); + int64_t int_array[] = { 1, 2 }; + const char *str = "hello world"; + utable_add_integer_array(table, "key", int_array, 2); + utable_add_cstring(table, "key", str, strlen(str)); + + test_utable_assert_arr(table, "key", int_array, 2); + + struct utable_stat A={}; + struct utable_stat B={}; + B.n_item=1; + B.n_item_size=strlen("key")+sizeof(int64_t)*2; + B.n_integer_array=1; + B.n_integer_array_size=sizeof(int64_t)*2; + + utable_stat(table, &A); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + utable_free(table); +} + +TEST(utable_test, iter_and_query) +{ + struct utable *table = utable_new(); + + int64_t int_array[] = { 1, 2 }; + const char *str = "hello world"; + utable_add_integer_array(table, "key1", int_array, 2); + utable_add_cstring(table, "key2", str, strlen(str)); + utable_add_integer_array(table, "key3", int_array, 2); + + EXPECT_STREQ(utable_next_key(table), "key1"); + EXPECT_STREQ(utable_next_key(table), "key2"); + EXPECT_STREQ(utable_next_key(table), "key3"); + EXPECT_EQ(utable_next_key(table), nullptr); + utable_reset_iter(table); + EXPECT_STREQ(utable_next_key(table), "key1"); + EXPECT_STREQ(utable_next_key(table), "key2"); + EXPECT_STREQ(utable_next_key(table), "key3"); + EXPECT_EQ(utable_next_key(table), nullptr); + + struct utable_stat A={}; + struct utable_stat B={}; + B.n_item=3; + B.n_item_size=strlen("key1")+sizeof(int64_t)*2+strlen("key2")+strlen(str)+strlen("key3")+sizeof(int64_t)*2; + B.n_blob=0; + B.n_blob_size=0; + B.n_cstring=1; + B.n_cstring_size=strlen(str); + B.n_integer_array=2; + B.n_integer_array_size=sizeof(int64_t)*4; + B.n_integer=0; + B.n_integer_size=0; + utable_stat(table, &A); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + + utable_free(table); +} + +TEST(utable_test, dup_empty) +{ + struct utable *table = utable_new(); + + struct utable *dup_table = utable_duplicate(table); + EXPECT_EQ(utable_next_key(dup_table), nullptr); + + utable_free(table); + utable_free(dup_table); +} + +TEST(utable_test, dup_and_query) +{ + struct utable *table = utable_new(); + + const char *str = "hello world"; + utable_add_cstring(table, "key1", str, strlen(str)); + int64_t int_array[] = { 1, 2 }; + utable_add_integer_array(table, "key2", int_array, 2); + const char *blob = "bin hello world"; + utable_add_blob(table, "key3", blob, strlen(blob)); + + struct utable *dup_table = utable_duplicate(table); + test_utable_assert_arr(dup_table, "key2", int_array, 2); + test_utable_assert_str(dup_table, "key1", str); + test_utable_assert_blob(dup_table, "key3", blob, strlen(blob)); + + struct utable_stat A={}, B={}; + utable_stat(table, &A); + utable_stat(dup_table, &B); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + utable_free(table); + utable_free(dup_table); +} + +TEST(utable_test, replace_8k_cstring) +{ + struct utable *table = utable_new(); + + const char str1[] = "hello world"; + char str2[8192]; + memset(str2, 'B', sizeof(str2)); + str2[8191] = '\0'; // make sure it's null-terminated + + utable_add_cstring(table, "key1", str1, strlen(str1)); + + test_utable_assert_str(table, "key1", str1); + + utable_delete_item(table, "key1"); + utable_add_cstring(table, "key1", str2, strlen(str2)); + + test_utable_assert_str(table, "key1", str2); + + struct utable_stat A={}; + struct utable_stat B={}; + B.n_item=1; + B.n_item_size=strlen("key1")+strlen(str2); + B.n_cstring=1; + B.n_cstring_size=strlen(str2); + utable_stat(table, &A); + + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + utable_free(table); +} + +TEST(utable_test, replace_cstring_many_times) +{ + struct utable *table = utable_new(); + + char str[4000] ; + + memset(str, 'B', sizeof(str)); + str[3999] = '\0'; // make sure it's null-terminated + + utable_add_cstring(table, "key1", str, strlen(str)); + + test_utable_assert_str(table, "key1", str); + + for(int i=0; i<100000; i++) + { + utable_delete_item(table, "key1"); + utable_add_cstring(table, "key1", str, strlen(str)); + } + + test_utable_assert_str(table, "key1", str); + + struct utable_stat A={}; + struct utable_stat B={}; + B.n_item=1; + B.n_item_size=strlen("key1")+strlen(str); + B.n_cstring=1; + B.n_cstring_size=strlen(str); + utable_stat(table, &A); + + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + printf("replace_cstring_many_times done\n"); + utable_free(table); +} + +TEST(utable_test, merge_empty_to_empty) +{ + struct utable *table = utable_new(); + struct utable *table2 = utable_new(); + utable_merge(table, table2); + EXPECT_EQ(utable_next_key(table), nullptr); + + struct utable_stat A={}, B={}; + utable_stat(table, &A); + utable_stat(table2, &B); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + utable_free(table); + utable_free(table2); +} + +TEST(utable_test, merge_all_new) +{ + struct utable *table = utable_new(); + struct utable *table2 = utable_new(); + + const char *str = "hello world"; + utable_add_cstring(table, "key1", str, strlen(str)); + int64_t int_array[] = { 1, 2 }; + utable_add_integer_array(table, "key2", int_array, 2); + + const char *blob = "bin hello world"; + utable_add_blob(table2, "key on tbl2", blob, strlen(blob)); + + utable_merge(table2, table); + test_utable_assert_arr(table2, "key2", int_array, 2); + test_utable_assert_str(table2, "key1", str); + test_utable_assert_blob(table2, "key on tbl2", blob, strlen(blob)); + + utable_merge(table, table2); + struct utable_stat A={}, B={}; + utable_stat(table, &A); + utable_stat(table2, &B); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + utable_free(table); + utable_free(table2); +} + +TEST(utable_test, merge_all_skip) +{ + struct utable *table = utable_new(); + const char *str = "hello world"; + utable_add_cstring(table, "key1", str, strlen(str)); + int64_t int_array[] = { 1, 2 }; + utable_add_integer_array(table, "key2", int_array, 2); + struct utable *table2 = utable_duplicate(table); + + utable_merge(table2, table); + + EXPECT_STREQ(utable_next_key(table2), "key1"); + EXPECT_STREQ(utable_next_key(table2), "key2"); + EXPECT_EQ(utable_next_key(table2), nullptr); + + utable_free(table); + utable_free(table2); +} + +TEST(utable_test, merge_some_skip_some_new) +{ + struct utable *table = utable_new(); + struct utable *table2 = utable_new(); + + const char *str = "val on tbl1"; + utable_add_cstring(table, "key_share", str, strlen(str)); + const char *str2 = "val on tbl2"; + utable_add_cstring(table2, "key_share", str2, strlen(str2)); + const char *str3 = "val on tbl1"; + utable_add_cstring(table, "key1", str3, strlen(str3)); + + utable_merge(table2, table); + + test_utable_assert_str(table2, "key_share", str2); + test_utable_assert_str(table2, "key1", str3); + + utable_free(table); + utable_free(table2); +} + +TEST(utable_test, serialize_and_deserialize) +{ + struct utable *table = utable_new(); + int64_t int_array[] = { 1, 2 }; + const char *str = "hello world"; + const char *blob = "bin hello world"; + utable_add_integer_array(table, "key1", int_array, 2); + utable_add_cstring(table, "key2", str, strlen(str)); + utable_add_blob(table, "key3", blob, strlen(blob)); + + char *blob_out; + size_t blob_size; + utable_serialize(table, &blob_out, &blob_size); + + struct utable *table2 = utable_deserialize(blob_out, blob_size); + test_utable_assert_arr(table2, "key1", int_array, 2); + test_utable_assert_str(table2, "key2", str); + test_utable_assert_blob(table2, "key3", blob, strlen(blob)); + + struct utable_stat A={}, B={}; + utable_stat(table, &A); + utable_stat(table2, &B); + EXPECT_EQ(memcmp(&A, &B, sizeof(struct utable_stat)), 0); + + utable_free(table); + free(blob_out); + utable_free(table2); +} + +TEST(utable_test, serialize_empty_and_deserialize) +{ + struct utable *table = utable_new(); + + char *blob_out; + size_t blob_size; + utable_serialize(table, &blob_out, &blob_size); + + struct utable *table2 = utable_deserialize(blob_out, blob_size); + + ASSERT_TRUE(table2 != NULL); + ASSERT_EQ(utable_next_key(table2), nullptr); + + utable_free(table); + free(blob_out); + utable_free(table2); +} + +int main(int argc, char *argv[]) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/deps/utable/utable.c b/deps/utable/utable.c new file mode 100644 index 0000000..1ab36a4 --- /dev/null +++ b/deps/utable/utable.c @@ -0,0 +1,730 @@ +#include "utable.h" + +#include +#include +#include + +#include "mpack/mpack.h" + +#define YYJSON_HAS_STDINT_H 1 +#define YYJSON_DISABLE_READER 1 +#define YYJSON_HAS_STDBOOL_H 1 +#include "yyjson/yyjson.h" + +#include "uthash/uthash.h" +#include "base64/b64.h" + +#include "nmx_pool/mempool.h" + +#ifdef DEBUG_MODE + #define DEBUG_PRINT(...) printf(__VA_ARGS__) +#else + #define DEBUG_PRINT(...) +#endif + +#define ALLOC(number, type) ((type *)calloc(sizeof(type), number)) +#define FREE(p) {free(p);p=NULL;} + +#define MEMPOOL_TYPE mem_pool_t + +#define MEMPOOL_CREATE(pool_size) create_mem_pool(pool_size) +#define MEMPOOL_DESTROY(pool) destroy_mem_pool(pool) + +#define MEMPOOL_ALLOC(pool, number, type) ((type *)mem_alloc(pool, sizeof(type) * number)) +#define MEMPOOL_FREE(pool, p) mem_free(pool, p) + + + +struct utable_item { + char *key; + size_t key_sz; + enum utable_value_type value_type; + size_t value_sz; + union { + struct + { + char *cstring; + size_t cstring_sz; + }; + struct + { + char *blob; + size_t blob_sz; + }; + struct + { + int64_t *interger_array; + size_t n_integer; + }; + struct + { + char **cstring_array; + size_t *cstring_array_sz; + size_t n_cstring; + }; + int64_t integer; + }; + UT_hash_handle hh; +}; + +struct utable { + struct utable_item *items; + struct utable_item *iter; + MEMPOOL_TYPE *mempool; + struct utable_stat stat; +}; + + + + +struct utable *utable_new_with_size(size_t sz) +{ + struct utable *table = ALLOC(1, struct utable); + table->mempool = MEMPOOL_CREATE(sz); + return table; +} + +#define UTABLE_INITIAL_MEMPOOL_SIZE 2 * 1024 + +inline struct utable *utable_new(void) +{ + return utable_new_with_size(UTABLE_INITIAL_MEMPOOL_SIZE); +} + +void utable_free(struct utable *table) +{ + if(table->items) HASH_CLEAR(hh, table->items); + if(table->mempool) MEMPOOL_DESTROY(table->mempool); + FREE(table); +} + +inline void utable_stat(struct utable *table, struct utable_stat *stat) +{ + if(stat == NULL) + return; + memcpy(stat, &table->stat, sizeof(struct utable_stat)); +} + +static void utable_item_stat_add(struct utable_stat *stat, struct utable_item *item) +{ + if(stat==NULL || item == NULL) + return; + stat->n_item++; + stat->n_item_size += item->key_sz; + stat->n_item_size += item->value_sz; + switch (item->value_type) + { + case utable_value_type_cstring: + stat->n_cstring++; + stat->n_cstring_size += item->value_sz; + break; + case utable_value_type_blob: + stat->n_blob++; + stat->n_blob_size += item->value_sz; + break; + case utable_value_type_integer: + stat->n_integer++; + stat->n_integer_size += item->value_sz; + break; + case utable_value_type_integer_array: + stat->n_integer_array++; + stat->n_integer_array_size += item->value_sz; + break; + case utable_value_type_cstring_array: + stat->n_cstring_array++; + stat->n_cstring_array_size += item->value_sz; + break; + default: + break; + } +} + +static void utable_item_stat_sub(struct utable_stat *stat, struct utable_item *item) +{ + if(stat==NULL || item == NULL) + return; + stat->n_item--; + stat->n_item_size -= item->key_sz; + stat->n_item_size -= item->value_sz; + switch (item->value_type) + { + case utable_value_type_cstring: + stat->n_cstring--; + stat->n_cstring_size -= item->cstring_sz; + break; + case utable_value_type_blob: + stat->n_blob--; + stat->n_blob_size -= item->blob_sz; + break; + case utable_value_type_integer: + stat->n_integer--; + stat->n_integer_size -= sizeof(item->integer); + break; + case utable_value_type_integer_array: + stat->n_integer_array--; + stat->n_integer_array_size -= sizeof(int64_t) * item->n_integer; + break; + case utable_value_type_cstring_array: + stat->n_cstring_array--; + stat->n_cstring_array_size -= item->value_sz; + break; + default: + break; + } +} + +static char*mempool_strdup(MEMPOOL_TYPE *mempool, const char* s, size_t len) +{ + char* new_str = MEMPOOL_ALLOC(mempool, len + 1, char); + memcpy(new_str, s, len + 1); + return new_str; +} + +void utable_add_cstring(struct utable *table, const char *key, const char *value, size_t value_sz) +{ + // check if key already exists + struct utable_item *item; + size_t key_sz = strlen(key); + HASH_FIND(hh, table->items, key, key_sz, item); + if (item) { + DEBUG_PRINT("ERR: key %s already exists\n", key); + return; + } + item = MEMPOOL_ALLOC(table->mempool ,1,struct utable_item); + item->key = mempool_strdup(table->mempool,key, key_sz); + item->key_sz = key_sz; + item->value_type = utable_value_type_cstring; + item->cstring = MEMPOOL_ALLOC(table->mempool ,value_sz + 1, char); + memcpy(item->cstring, value, value_sz); + item->cstring[value_sz] = '\0'; + item->cstring_sz = value_sz; + item->value_sz = value_sz; + HASH_ADD_KEYPTR(hh, table->items, item->key, item->key_sz, item); + utable_item_stat_add(&table->stat, item); +} + +void utable_add_blob(struct utable *table, const char *key, const char *blob, size_t blob_sz) +{ + // check if key already exists + struct utable_item *item; + size_t key_sz = strlen(key); + HASH_FIND(hh, table->items, key, key_sz, item); + if (item) { + DEBUG_PRINT("ERR: key %s already exists\n", key); + return; + } + item = MEMPOOL_ALLOC(table->mempool ,1, struct utable_item); + item->key = mempool_strdup(table->mempool, key, key_sz); + item->key_sz = key_sz; + item->value_type = utable_value_type_blob; + item->blob = MEMPOOL_ALLOC(table->mempool ,blob_sz, char); + memcpy(item->blob, blob, blob_sz); + item->blob_sz = blob_sz; + item->value_sz = blob_sz; + HASH_ADD_KEYPTR(hh, table->items, item->key, item->key_sz, item); + + utable_item_stat_add(&table->stat, item); +} + +void utable_add_integer(struct utable *table, const char *key, int64_t value) +{ + // check if key already exists + struct utable_item *item; + size_t key_sz = strlen(key); + HASH_FIND(hh, table->items, key, key_sz, item); + if (item) { + DEBUG_PRINT("ERR: key %s already exists\n", key); + return; + } + item = MEMPOOL_ALLOC(table->mempool ,1, struct utable_item); + item->key = mempool_strdup(table->mempool, key, key_sz); + item->key_sz = key_sz; + item->value_type = utable_value_type_integer; + item->integer = value; + item->value_sz = sizeof(int64_t); + HASH_ADD_KEYPTR(hh, table->items, item->key, item->key_sz, item); + + utable_item_stat_add(&table->stat, item); +} + +void utable_add_integer_array(struct utable *table, const char *key, int64_t value_array[], size_t n_value) +{ + // check if key already exists + struct utable_item *item; + size_t key_sz = strlen(key); + HASH_FIND(hh, table->items, key, key_sz, item); + if (item) { + DEBUG_PRINT("ERR: key %s already exists\n", key); + return; + } + item = MEMPOOL_ALLOC(table->mempool ,1, struct utable_item); + item->key = mempool_strdup(table->mempool, key, key_sz); + item->key_sz = key_sz; + item->value_type = utable_value_type_integer_array; + item->interger_array = MEMPOOL_ALLOC(table->mempool ,n_value, int64_t); + memcpy(item->interger_array, value_array, sizeof(int64_t) * n_value); + item->n_integer = n_value; + item->value_sz = sizeof(int64_t) * n_value; + HASH_ADD_KEYPTR(hh, table->items, item->key, item->key_sz, item); + + utable_item_stat_add(&table->stat, item); +} + +void utable_add_cstring_array(struct utable *table, const char *key, const char* value_array[], size_t value_sz[], size_t n_value) +{ + // check if key already exists + struct utable_item *item; + size_t key_sz = strlen(key); + HASH_FIND(hh, table->items, key, key_sz, item); + if (item) { + DEBUG_PRINT("ERR: key %s already exists\n", key); + return; + } + item = MEMPOOL_ALLOC(table->mempool ,1, struct utable_item); + item->key = mempool_strdup(table->mempool, key, key_sz); + item->key_sz = key_sz; + item->value_type = utable_value_type_cstring_array; + item->cstring_array = MEMPOOL_ALLOC(table->mempool ,n_value, char *); + item->cstring_array_sz = MEMPOOL_ALLOC(table->mempool ,n_value, size_t); + item->value_sz = 0; + for(size_t i =0; i < n_value; i++) + { + item->cstring_array[i] = MEMPOOL_ALLOC(table->mempool , value_sz[i]+1, char); + memcpy(item->cstring_array[i], value_array[i], value_sz[i]); + item->cstring_array[i][value_sz[i]] = '\0'; + item->cstring_array_sz[i] = value_sz[i]; + item->value_sz += value_sz[i]; + } + item->n_cstring = n_value; + HASH_ADD_KEYPTR(hh, table->items, item->key, item->key_sz, item); + + utable_item_stat_add(&table->stat, item); +} + +void utable_delete_item(struct utable *table, const char *key) +{ + struct utable_item *item; + HASH_FIND_STR(table->items, key, item); + if (item) { + HASH_DEL(table->items, item); + MEMPOOL_FREE(table->mempool, item->key); + switch (item->value_type) { + case utable_value_type_cstring: + MEMPOOL_FREE(table->mempool,item->cstring); + break; + case utable_value_type_blob: + MEMPOOL_FREE(table->mempool,item->blob); + break; + case utable_value_type_integer_array: + MEMPOOL_FREE(table->mempool,item->interger_array); + break; + case utable_value_type_cstring_array: + for(size_t i=0; i < item->n_cstring; i++) + { + MEMPOOL_FREE(table->mempool,item->cstring_array[i]); + } + MEMPOOL_FREE(table->mempool,item->cstring_array_sz); + break; + default: + break; + } + utable_item_stat_sub(&table->stat, item); + MEMPOOL_FREE(table->mempool,item); + } +} + +void utable_reset_iter(struct utable *table) +{ + table->iter = NULL; +} + +const char *utable_next_key(struct utable *table) +{ + if (table->iter == NULL) { + table->iter = table->items; + } else { + table->iter = (struct utable_item *)table->iter->hh.next; + } + if (table->iter == NULL) { + return NULL; + } + return table->iter->key; +} + +enum utable_value_type utable_get_value_type(const struct utable *table, const char *key) +{ + struct utable_item *item; + HASH_FIND_STR(table->items, key, item); + if (item) { + return item->value_type; + } + return utable_value_type_undefined; +} + +int utable_get0_blob_value(const struct utable *table, const char *key, char **value, size_t *value_len) +{ + struct utable_item *item; + HASH_FIND_STR(table->items, key, item); + if (item) { + if (item->value_type == utable_value_type_blob) { + *value = item->blob; + *value_len = item->blob_sz; + return 0; + } + } + return -1; +} + +int utable_get0_cstring_value(const struct utable *table, const char *key, char **value, size_t *value_len) +{ + struct utable_item *item; + HASH_FIND_STR(table->items, key, item); + if (item) { + if (item->value_type == utable_value_type_cstring) { + *value = item->cstring; + *value_len = item->cstring_sz; + return 0; + } + } + return -1; +} + +int utable_get0_integer_value(const struct utable *table, const char *key, int64_t *value) +{ + struct utable_item *item; + HASH_FIND_STR(table->items, key, item); + if (item) { + if (item->value_type == utable_value_type_integer) { + *value = item->integer; + return 0; + } + } + return -1; +} + +int utable_get0_integer_value_array(const struct utable *table, const char *key, int64_t **value_array, size_t *n_value) +{ + struct utable_item *item; + HASH_FIND_STR(table->items, key, item); + if (item) { + if (item->value_type == utable_value_type_integer_array) { + *value_array = item->interger_array; + *n_value = item->n_integer; + return 0; + } + } + return -1; +} + +int utable_get0_cstring_value_array(const struct utable *table, const char *key, char ***value_array, size_t **value_len, size_t *n_value) +{ + struct utable_item *item; + HASH_FIND_STR(table->items, key, item); + if (item) { + if (item->value_type == utable_value_type_cstring_array) { + *value_array = item->cstring_array; + *value_len = item->cstring_array_sz; + *n_value = item->n_cstring; + return 0; + } + } + return -1; +} + +struct utable_item *utable_item_dup(MEMPOOL_TYPE *mempool, const struct utable_item *item) +{ + struct utable_item *new_item = MEMPOOL_ALLOC(mempool, 1, struct utable_item); + size_t key_sz = item->key_sz; + new_item->key = mempool_strdup(mempool, item->key, key_sz); + new_item->key_sz = key_sz; + new_item->value_type = item->value_type; + new_item->value_sz= item->value_sz; + switch (item->value_type) { + case utable_value_type_cstring: + new_item->cstring = MEMPOOL_ALLOC(mempool,item->cstring_sz + 1, char); + memcpy(new_item->cstring, item->cstring, item->cstring_sz); + new_item->cstring[item->cstring_sz] = '\0'; + new_item->cstring_sz = item->cstring_sz; + break; + case utable_value_type_blob: + new_item->blob = MEMPOOL_ALLOC(mempool,item->blob_sz, char); + memcpy(new_item->blob, item->blob, item->blob_sz); + new_item->blob_sz = item->blob_sz; + break; + case utable_value_type_integer: + new_item->integer = item->integer; + break; + case utable_value_type_integer_array: + new_item->interger_array = MEMPOOL_ALLOC(mempool,item->n_integer, int64_t); + memcpy(new_item->interger_array, item->interger_array, sizeof(int64_t) * item->n_integer); + new_item->n_integer = item->n_integer; + break; + case utable_value_type_cstring_array: + new_item->n_cstring= item->n_cstring; + new_item->cstring_array = MEMPOOL_ALLOC(mempool ,item->n_cstring, char *); + new_item->cstring_array_sz = MEMPOOL_ALLOC(mempool ,item->n_cstring, size_t); + for(size_t i =0; i < item->n_cstring; i++) + { + new_item->cstring_array[i] = MEMPOOL_ALLOC(mempool , item->cstring_array_sz[i]+1, char); + memcpy(new_item->cstring_array[i], item->cstring_array[i], item->cstring_array_sz[i]); + new_item->cstring_array[i][item->cstring_array_sz[i]] = '\0'; + new_item->cstring_array_sz[i] = item->cstring_array_sz[i]; + } + break; + default: + break; + } + return new_item; +} + +struct utable *utable_duplicate(const struct utable *table) +{ + struct utable *new_table = utable_new(); + struct utable_item *item, *tmp; + HASH_ITER(hh, table->items, item, tmp) { + struct utable_item *new_item = utable_item_dup(new_table->mempool, item); + HASH_ADD_KEYPTR(hh, new_table->items, new_item->key, new_item->key_sz, new_item); + } + new_table->stat = table->stat; + return new_table; +} + +int utable_merge(struct utable *dst, const struct utable *src) +{ + struct utable_item *item, *tmp; + HASH_ITER(hh, src->items, item, tmp) { + struct utable_item *dst_item; + HASH_FIND(hh, dst->items, item->key, item->key_sz, dst_item); + if (dst_item) { + continue; + } + struct utable_item *new_item = utable_item_dup(dst->mempool, item); + HASH_ADD_KEYPTR(hh, dst->items, new_item->key, new_item->key_sz, new_item); + utable_item_stat_add(&dst->stat, item); + } + return 0; +} + +int utable_json_export(const struct utable *table, char **blob, size_t *blob_len) +{ + yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); + yyjson_mut_val *root = yyjson_mut_obj(doc); + yyjson_mut_doc_set_root(doc, root); + char *encs[HASH_COUNT(table->items)]; + struct utable_item *item, *tmp; + int enc_cnt = 0; + HASH_ITER(hh, table->items, item, tmp) { + switch (item->value_type) { + case utable_value_type_cstring: { + yyjson_mut_obj_add_str(doc, root, item->key, item->cstring); // key and cstring are shallow copied + } + break; + case utable_value_type_blob: { + char *enc = b64_encode((unsigned char *)item->blob, item->blob_sz); + yyjson_mut_obj_add_str(doc, root, item->key, enc); + // do not free enc now, it is shallow copied + encs[enc_cnt++] = enc; + } + break; + case utable_value_type_integer: { + yyjson_mut_obj_add_int(doc, root, item->key, item->integer); + } + break; + case utable_value_type_integer_array: { + yyjson_mut_val *arr = yyjson_mut_arr_with_sint64(doc, item->interger_array, item->n_integer); + yyjson_mut_obj_add_val(doc, root, item->key, arr); + } + break; + case utable_value_type_cstring_array: { + yyjson_mut_val *arr = yyjson_mut_arr_with_strn(doc, (const char **)item->cstring_array, item->cstring_array_sz, item->n_cstring); + yyjson_mut_obj_add_val(doc, root, item->key, arr); + } + break; + default: + break; + } + } + + char *json_str = yyjson_mut_write(doc, YYJSON_WRITE_ALLOW_INVALID_UNICODE, blob_len); + yyjson_mut_doc_free(doc); + *blob = json_str; + for (int j = 0; j < enc_cnt; j++) { + free(encs[j]); + } + return 0; +} + +/* + [{ + "key": + "type":"i_arr", "c_str", or "blob" + "value":< + }] +*/ +int utable_msgpack_export(const struct utable *table, char **blob, size_t *blob_len) +{ + mpack_writer_t writer; + mpack_writer_init_growable(&writer, blob, blob_len); + mpack_start_array(&writer, HASH_COUNT(table->items)); + + struct utable_item *item, *tmp; + HASH_ITER(hh, table->items, item, tmp) { + mpack_start_map(&writer, 3); + mpack_write_cstr(&writer, "key"); + mpack_write_cstr(&writer, item->key); + + mpack_write_cstr(&writer, "type"); + switch (item->value_type) { + case utable_value_type_cstring: + mpack_write_cstr(&writer, "c_str"); + break; + case utable_value_type_blob: + mpack_write_cstr(&writer, "blob"); + break; + case utable_value_type_integer: + mpack_write_cstr(&writer, "i"); + break; + case utable_value_type_integer_array: + mpack_write_cstr(&writer, "i_arr"); + break; + default: + break; + } + + mpack_write_cstr(&writer, "value"); + switch (item->value_type) { + case utable_value_type_cstring: + mpack_write_bin(&writer, item->cstring, item->cstring_sz); + break; + case utable_value_type_blob: + mpack_write_bin(&writer, item->blob, item->blob_sz); + break; + case utable_value_type_integer: + mpack_write_i64(&writer, item->integer); + break; + case utable_value_type_integer_array: + mpack_start_array(&writer, item->n_integer); + for (size_t i = 0; i < item->n_integer; i++) { + mpack_write_i64(&writer, item->interger_array[i]); + } + mpack_finish_array(&writer); + break; + default: + break; + } + + mpack_finish_map(&writer); + } + + mpack_finish_array(&writer); + if (mpack_writer_destroy(&writer) != mpack_ok) { + DEBUG_PRINT("ERR mpack writer fieldtag_list_serialize destroy failed\n"); + return -1; + } + return 0; +} + +void utable_serialize(const struct utable *table, char **blob, size_t *blob_len) { + utable_msgpack_export(table, blob, blob_len); +} + +char *get_cstring_from_mpack_node(MEMPOOL_TYPE *mempool ,mpack_node_t node) +{ + size_t len = mpack_node_strlen(node); + char *str = MEMPOOL_ALLOC(mempool, len + 1, char); + memcpy(str, mpack_node_str(node), len); + str[len] = '\0'; + return str; +} + +static void utable_item_free(MEMPOOL_TYPE *mempool, struct utable_item *item) +{ + if(item == NULL) + return; + if(item->key)MEMPOOL_FREE(mempool, item->key); + switch (item->value_type) { + case utable_value_type_cstring: + MEMPOOL_FREE(mempool,item->cstring); + break; + case utable_value_type_blob: + MEMPOOL_FREE(mempool,item->blob); + break; + case utable_value_type_integer_array: + MEMPOOL_FREE(mempool,item->interger_array); + break; + case utable_value_type_cstring_array: + for(size_t i=0; i < item->n_cstring; i++) + { + MEMPOOL_FREE(mempool,item->cstring_array[i]); + } + MEMPOOL_FREE(mempool,item->cstring_array_sz); + break; + default: + break; + } +} + +struct utable *utable_deserialize(const char *blob, size_t blob_len) +{ + mpack_tree_t tree; + mpack_tree_init_data(&tree, blob, blob_len); + mpack_tree_parse(&tree); + mpack_node_t root = mpack_tree_root(&tree); + if (mpack_node_type(root) != mpack_type_array) { + DEBUG_PRINT("ERR mpack root type is not array\n"); + return NULL; + } + size_t n_item = mpack_node_array_length(root); + + struct utable *table = utable_new(); + for (size_t i = 0; i < n_item; i++) { + mpack_node_t item = mpack_node_array_at(root, i); + mpack_node_t key = mpack_node_map_cstr(item, "key"); + mpack_node_t type = mpack_node_map_cstr(item, "type"); + mpack_node_t value = mpack_node_map_cstr(item, "value"); + char *type_str = get_cstring_from_mpack_node(table->mempool, type); + + struct utable_item *new_item = MEMPOOL_ALLOC(table->mempool,1, struct utable_item); + new_item->key = get_cstring_from_mpack_node(table->mempool, key); + new_item->key_sz = strlen(new_item->key); + new_item->value_sz = 0; + + if (strcmp(type_str, "c_str") == 0) { + new_item->value_type = utable_value_type_cstring; + new_item->cstring = MEMPOOL_ALLOC(table->mempool,mpack_node_bin_size(value), char); + new_item->cstring_sz = mpack_node_bin_size(value); + new_item->value_sz = new_item->cstring_sz; + memcpy(new_item->cstring, mpack_node_bin_data(value), mpack_node_bin_size(value)); + } else if (strcmp(type_str, "blob") == 0) { + new_item->value_type = utable_value_type_blob; + new_item->blob = MEMPOOL_ALLOC(table->mempool,mpack_node_bin_size(value), char); + new_item->blob_sz = mpack_node_bin_size(value); + new_item->value_sz = new_item->blob_sz; + memcpy(new_item->blob, mpack_node_bin_data(value), mpack_node_bin_size(value)); + } else if (strcmp(type_str, "i") == 0) { + new_item->value_type = utable_value_type_integer; + new_item->integer = mpack_node_i64(value); + } else if (strcmp(type_str, "i_arr") == 0) { + new_item->value_type = utable_value_type_integer_array; + new_item->n_integer = mpack_node_array_length(value); + new_item->interger_array = MEMPOOL_ALLOC(table->mempool,new_item->n_integer, int64_t); + for (size_t j = 0; j < new_item->n_integer; j++) { + new_item->interger_array[j] = mpack_node_i64(mpack_node_array_at(value, j)); + } + new_item->value_sz = sizeof(int64_t) * new_item->n_integer; + } else { + DEBUG_PRINT("ERR unknown type %s\n", type_str); + mpack_tree_destroy(&tree); + MEMPOOL_FREE(table->mempool, type_str); + utable_item_free(table->mempool, new_item); + return NULL; + } + + HASH_ADD_KEYPTR(hh, table->items, new_item->key, new_item->key_sz, new_item); + utable_item_stat_add(&table->stat, new_item); + MEMPOOL_FREE(table->mempool, type_str); + } + + mpack_tree_destroy(&tree); + return table; +} diff --git a/deps/utable/utable.h b/deps/utable/utable.h new file mode 100644 index 0000000..13dfa5c --- /dev/null +++ b/deps/utable/utable.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct utable_stat +{ + size_t n_item; + size_t n_item_size; + size_t n_blob; + size_t n_blob_size; + size_t n_cstring; + size_t n_cstring_size; + size_t n_integer; + size_t n_integer_size; + size_t n_integer_array; + size_t n_integer_array_size; + size_t n_cstring_array; + size_t n_cstring_array_size; +}; + +struct utable; +struct utable *utable_new(void); +struct utable *utable_new_with_size(size_t sz); +void utable_free(struct utable *table); + + + + + +void utable_stat(struct utable *table, struct utable_stat *stat); + +void utable_add_cstring(struct utable *table, const char *key, const char *value, size_t value_sz); +void utable_add_blob(struct utable *table, const char *key, const char *blob, size_t blob_sz); +void utable_add_integer(struct utable *table, const char *key, int64_t value); +void utable_add_integer_array(struct utable *table, const char *key, int64_t value_array[], size_t n_value); +void utable_add_cstring_array(struct utable *table, const char *key, const char* value_array[], size_t value_sz[], size_t n_value); +void utable_delete_item(struct utable *table, const char *key); + +enum utable_value_type +{ + utable_value_type_undefined=0, + utable_value_type_cstring, + utable_value_type_blob, + utable_value_type_integer, + utable_value_type_integer_array, + utable_value_type_cstring_array +}; + +void utable_reset_iter(struct utable *table); +const char *utable_next_key(struct utable *table); +enum utable_value_type utable_get_value_type(const struct utable *table, const char *key); +int utable_get0_blob_value(const struct utable *table, const char *key, char **value, size_t *value_len); +int utable_get0_cstring_value(const struct utable *table, const char *key, char **value, size_t *value_len); +int utable_get0_integer_value(const struct utable *table, const char *key, int64_t *value); +int utable_get0_integer_value_array(const struct utable *table, const char *key, int64_t **value_array, size_t *n_value); +int utable_get0_cstring_value_array(const struct utable *table, const char *key, char ***value_array, size_t **value_len, size_t *n_value); + +struct utable *utable_duplicate(const struct utable *table); +int utable_merge(struct utable *dst, const struct utable *src); + +void utable_serialize(const struct utable *table, char **blob, size_t *blob_len); +struct utable *utable_deserialize(const char *blob, size_t blob_len); + +struct ipfix_exporter_schema; +struct ipfix_exporter_schema *utable_ipfix_exporter_schema_new(const char *ipfix_schema_json_path, uint16_t domain_id, uint16_t n_worker); // |domain_id|work_id|=source_id (16+16) domain_id 是进程唯一标识 +void utable_ipfix_exporter_schema_free(struct ipfix_exporter_schema *ipfix_schema); + +uint32_t utable_ipfix_template_flow_refresh_interval_s(struct ipfix_exporter_schema *ipfix_schema); +const char *utable_ipfix_template_flow_get0(struct ipfix_exporter_schema *ipfix_schema, uint16_t worker_id, size_t *blob_len); + +// return template id, -1 if not found +int utable_ipfix_template_get(struct ipfix_exporter_schema *ipfix_schema, const char *template_name); +int utable_ipfix_data_flow_exporter(const struct utable *table, struct ipfix_exporter_schema *ipfix_schema, int template_id, uint16_t worker_id, char **blob, size_t *blob_len); + + +int utable_json_export(const struct utable *table, char **blob, size_t *blob_len); +int utable_msgpack_export(const struct utable *table, char **blob, size_t *blob_len); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/deps/utable/utable_ipfix_exporter.c b/deps/utable/utable_ipfix_exporter.c new file mode 100644 index 0000000..5c4e837 --- /dev/null +++ b/deps/utable/utable_ipfix_exporter.c @@ -0,0 +1,795 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utable.h" +#include "cjson/cJSON.h" +#include "yyjson/yyjson.h" +#include "uthash/utarray.h" +#include "uthash/uthash.h" + +#define IPFIX_DEFUALT_VERSION 10 +#define GEEDGE_NETWORKS_PEN_NUMBER 54450 +#define IPFIX_DEFUALT_PEN_NUMBER GEEDGE_NETWORKS_PEN_NUMBER +#define IPFIX_NONE_PEN_NUMBER -1 +#define IPFIX_TEMPLATE_SET_ID 2 +#define IPFIX_BUFF_MAX_SIZE 2048 +#define IPFIX_ELEMENT_MAX_LEN 32 + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +enum ipfix_type +{ + IPFIX_UNSIGNED8 = 0, + IPFIX_UNSIGNED16, + IPFIX_UNSIGNED32, + IPFIX_UNSIGNED64, + IPFIX_VARIABLE_STRING, + IPFIX_UNKNOWN +}; + +struct ipfix_element +{ + enum ipfix_type type; + uint16_t element_id; + uint16_t value_length; // if type is variable_string, value_length is 0 + int PEN_number; + char name[IPFIX_ELEMENT_MAX_LEN]; +}; + +struct ipfix_template +{ + uint16_t template_id; + char *name; + UT_array *elements_array; // utarray for current template elements +}; + +struct ipfix_worker_context +{ + int source_id; + int sequence; + size_t template_blob_length; + char *template_blob; +}; + +struct ipfix_exporter_schema +{ + uint16_t n_worker; + uint16_t version; + int domain_id; + int PEN_number; + uint32_t templates_refresh_interval_s; + UT_array *templates_array; // utarray for templates + struct ipfix_worker_context *worker_context_array; // utarray for worker_context +}; + +struct ipfix_message_head +{ + uint16_t version; + uint16_t length; + int exporttime; + int ipfix_message_sequence; + int domain_id; +}; +#define IPFIX_MESSAGE_HEAD_LEN sizeof(struct ipfix_message_head) + +// from maat +int load_file_to_memory(const char *file_name, unsigned char **pp_out, size_t *out_sz) +{ + int ret = 0; + FILE *fp = NULL; + struct stat fstat_buf; + size_t read_size = 0; + + ret = stat(file_name, &fstat_buf); + if (ret != 0) + { + return -1; + } + + fp = fopen(file_name, "r"); + if (fp == NULL) + { + return -1; + } + + *out_sz = fstat_buf.st_size; + *pp_out = (unsigned char *)calloc(*out_sz + 1, sizeof(unsigned char)); + read_size = fread(*pp_out, 1, *out_sz, fp); + if (read_size != *out_sz) + { + free(*pp_out); + *pp_out = NULL; + } + + fclose(fp); + fp = NULL; + return 0; +} + +int ipfix_exporter_get_type(struct ipfix_element *p_element, char *element_type) +{ + if (strcmp(element_type, "string") == 0) + { + p_element->type = IPFIX_VARIABLE_STRING; + p_element->value_length = 0; + return 0; + } + else if (strcmp(element_type, "unsigned8") == 0) + { + p_element->type = IPFIX_UNSIGNED8; + p_element->value_length = sizeof(uint8_t); + return 0; + } + else if (strcmp(element_type, "unsigned16") == 0) + { + p_element->type = IPFIX_UNSIGNED16; + p_element->value_length = sizeof(uint16_t); + return 0; + } + else if (strcmp(element_type, "unsigned32") == 0) + { + p_element->type = IPFIX_UNSIGNED32; + p_element->value_length = sizeof(uint32_t); + return 0; + } + else if (strcmp(element_type, "unsigned64") == 0) + { + p_element->type = IPFIX_UNSIGNED64; + p_element->value_length = sizeof(uint64_t); + return 0; + } + else + { + return -1; + } +} + +int ipfix_template_payload_init(UT_array *templates_array, char **blob, size_t *blob_len) +{ + if (templates_array == NULL || utarray_len(templates_array) == 0 || blob == NULL || blob_len == NULL) + { + return -1; + } + + size_t n_templates = utarray_len(templates_array); + size_t template_blob_len = 0; + for (size_t i = 0; i < n_templates; i++) + { + struct ipfix_template *template_item = (struct ipfix_template *)utarray_eltptr(templates_array, i); + size_t n_elements = utarray_len(template_item->elements_array); + template_blob_len += (sizeof(uint16_t) + sizeof(uint16_t) + n_elements * (sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t))); // template_id + field_count + element_id + length + PEN_number + } + template_blob_len += ((sizeof(uint16_t) + sizeof(uint16_t)) + IPFIX_MESSAGE_HEAD_LEN); // set_id + set_length + ipfix_message_head + + char *payload = (char *)calloc(template_blob_len, sizeof(char)); + int offset = 0; + + offset += IPFIX_MESSAGE_HEAD_LEN; // skip ipfix_message_head + *(uint16_t *)(payload + offset) = htons(IPFIX_TEMPLATE_SET_ID); // set_id + offset += sizeof(uint16_t); // skip set_id + *(uint16_t *)(payload + offset) = htons(template_blob_len - IPFIX_MESSAGE_HEAD_LEN); // set_length + offset += sizeof(uint16_t); // skip set_length + + for (size_t i = 0; i < n_templates; i++) + { + struct ipfix_template *template_item = (struct ipfix_template *)utarray_eltptr(templates_array, i); + size_t n_elements = utarray_len(template_item->elements_array); + *(uint16_t *)(payload + offset) = htons(template_item->template_id); // template_id + offset += sizeof(uint16_t); // skip template_id + *(uint16_t *)(payload + offset) = htons(n_elements); // field_count + offset += sizeof(uint16_t); // skip field_count + + for (size_t j = 0; j < n_elements; j++) + { + struct ipfix_element *p_element = (struct ipfix_element *)utarray_eltptr(template_item->elements_array, j); + + // set element_id + if (p_element->PEN_number != IPFIX_NONE_PEN_NUMBER) + { + *(uint16_t *)(payload + offset) = htons(p_element->element_id | 0x8000); // Pen_provided is yes + } + else + { + *(uint16_t *)(payload + offset) = htons(p_element->element_id); // Pen_provided is no + } + offset += sizeof(uint16_t); // skip element_id + + // set element_length + if (p_element->type == IPFIX_VARIABLE_STRING) + { + *(uint16_t *)(payload + offset) = htons(65535); // if element_length is 65535, it means variable length + } + else + { + *(uint16_t *)(payload + offset) = htons(p_element->value_length); // length + } + offset += sizeof(uint16_t); // skip length + + // set PEN_number + if (p_element->PEN_number != IPFIX_NONE_PEN_NUMBER) + { + *(uint32_t *)(payload + offset) = htonl(p_element->PEN_number); // PEN_number + offset += sizeof(uint32_t); // skip PEN_number + } + } + } + + if (offset != (int)template_blob_len) + { + free(payload); + payload = NULL; + return -1; + } + + *blob = payload; + *blob_len = offset; + return 0; +} + +void ipfix_template_item_destroy(void *elt) +{ + struct ipfix_template *template_item = (struct ipfix_template *)elt; + if (template_item == NULL) + { + return; + } + + if (template_item->elements_array != NULL) + { + utarray_free(template_item->elements_array); + } + + if (template_item->name != NULL) + { + free(template_item->name); + template_item->name = NULL; + } + + return; +} + +void ipfix_utarray_init(UT_array **_array, size_t sz_item, void (*dtor)(void *)) +{ + if (_array == NULL || *_array != NULL) + { + return; + } + + UT_icd icd = {0}; + icd.sz = sz_item; + icd.dtor = dtor; + utarray_new(*_array, &icd); + return; +} + +UT_array *ipfix_elements_array_init(cJSON *root, const char *element_key, int PEN_number_global) +{ + if (root == NULL || element_key == NULL) + { + return NULL; + } + + cJSON *elements = cJSON_GetObjectItem(root, element_key); + if (elements == NULL) + { + return NULL; + } + + UT_array *elements_array = NULL; + ipfix_utarray_init(&elements_array, sizeof(struct ipfix_element), NULL); + size_t n_elements = cJSON_GetArraySize(elements); + for (size_t i = 0; i < n_elements; i++) + { + struct ipfix_element element_item = {0}; + cJSON *element = cJSON_GetArrayItem(elements, i); + cJSON *element_id = cJSON_GetObjectItem(element, "element_id"); + if (element_id == NULL || element_id->valueint == 0) // element_id不存在、类型不为Number、值为0 + { + goto error; + } + element_item.element_id = element_id->valueint; + + cJSON *element_type = cJSON_GetObjectItem(element, "element_type"); + if (element_type == NULL || element_type->valuestring == NULL) // element_type不存在、类型不为String、值为NULL + { + goto error; + } + + if (ipfix_exporter_get_type(&element_item, element_type->valuestring) == -1) // element_type不在支持范围内 + { + goto error; + } + + cJSON *PEN_number = cJSON_GetObjectItem(element, "PEN_number"); + if (PEN_number == NULL) // PEN_number不存在、类型不为Number + { + element_item.PEN_number = PEN_number_global; + } + else + { + element_item.PEN_number = PEN_number->valueint; + } + + cJSON *element_name = cJSON_GetObjectItem(element, "element_name"); + if (element_name == NULL || element_name->valuestring == NULL) // element_name不存在、类型不为String、值为NULL + { + goto error; + } + memcpy(element_item.name, element_name->valuestring, MIN(strlen(element_name->valuestring), IPFIX_ELEMENT_MAX_LEN - 1)); + utarray_push_back(elements_array, &element_item); + } + + return elements_array; +error: + utarray_free(elements_array); + elements_array = NULL; + return NULL; +} + +void utable_ipfix_exporter_schema_free(struct ipfix_exporter_schema *instance) +{ + if (instance == NULL) + { + return; + } + + if (instance->templates_array) + { + utarray_free(instance->templates_array); + instance->templates_array = NULL; + } + + if (instance->worker_context_array) + { + for (size_t i = 0; i < instance->n_worker; i++) + { + if (instance->worker_context_array[i].template_blob) + { + free(instance->worker_context_array[i].template_blob); + instance->worker_context_array[i].template_blob = NULL; + } + } + } + free(instance->worker_context_array); + instance->worker_context_array = NULL; + + free(instance); + instance = NULL; + return; +} + +struct ipfix_elements_hash +{ + char *name; + UT_array *elements_array; + UT_hash_handle hh; +}; + +void ipfix_elements_array_hash_destroy(struct ipfix_elements_hash *elements_array_hash) +{ + if (elements_array_hash == NULL) + { + return; + } + + struct ipfix_elements_hash *elements_array_item = NULL; + struct ipfix_elements_hash *tmp = NULL; + HASH_ITER(hh, elements_array_hash, elements_array_item, tmp) + { + HASH_DEL(elements_array_hash, elements_array_item); + if (elements_array_item->name != NULL) + { + free(elements_array_item->name); + elements_array_item->name = NULL; + } + if (elements_array_item->elements_array != NULL) + { + utarray_free(elements_array_item->elements_array); + elements_array_item->elements_array = NULL; + } + free(elements_array_item); + elements_array_item = NULL; + } + return; +} + +struct ipfix_exporter_schema *utable_ipfix_exporter_schema_new(const char *ipfix_schema_json_path, uint16_t domain_id, uint16_t n_worker) +{ + if (ipfix_schema_json_path == NULL) + { + return NULL; + } + + size_t json_size = 0; + unsigned char *json_str = NULL; + int ret = load_file_to_memory(ipfix_schema_json_path, &json_str, &json_size); + if (ret == -1) + { + return NULL; + } + + cJSON *root = NULL; + root = cJSON_Parse((const char *)json_str); + if (root == NULL) + { + free(json_str); + json_str = NULL; + return NULL; + } + + int version = 0; + version = cJSON_GetObjectItem(root, "version")->valueint; + if (version < IPFIX_DEFUALT_VERSION) + { + version = IPFIX_DEFUALT_VERSION; + } + + int PEN_number = 0; + PEN_number = cJSON_GetObjectItem(root, "PEN_number")->valueint; + if (PEN_number <= 0) + { + PEN_number = IPFIX_DEFUALT_PEN_NUMBER; + } + + uint32_t refresh_interval_s = 0; + refresh_interval_s = cJSON_GetObjectItem(root, "refresh_interval_s")->valueint; + if ((int)refresh_interval_s <= 0) + { + refresh_interval_s = 60; + } + + size_t n_template = 0; + cJSON *templates = NULL; + templates = cJSON_GetObjectItem(root, "templates"); + if (templates == NULL || cJSON_GetArraySize(templates) == 0) + { + free(json_str); + json_str = NULL; + return NULL; + } + n_template = cJSON_GetArraySize(templates); + + UT_array *templates_array = NULL; + ipfix_utarray_init(&templates_array, sizeof(struct ipfix_template), ipfix_template_item_destroy); + struct ipfix_elements_hash *elements_array_hash = NULL; + + for (size_t i = 0; i < n_template; i++) + { + struct ipfix_template template_item = {0}; + cJSON *template_obj = cJSON_GetArrayItem(templates, i); + cJSON *template_id = cJSON_GetObjectItem(template_obj, "template_id"); + if (template_id == NULL || template_id->valueint < 256 || template_id->valueint == 0xffff) + { + goto error; + } + template_item.template_id = template_id->valueint; + + cJSON *template_name = cJSON_GetObjectItem(template_obj, "template_name"); + if (template_name == NULL || template_name->valuestring == NULL) + { + goto error; + } + template_item.name = (char *)calloc(strlen(template_name->valuestring) + 1, sizeof(char)); + memcpy(template_item.name, template_name->valuestring, strlen(template_name->valuestring)); + + cJSON *elements_key_array = cJSON_GetObjectItem(template_obj, "elements"); + if (elements_key_array == NULL) + { + goto error; + } + size_t n_elements_key = cJSON_GetArraySize(elements_key_array); + ipfix_utarray_init(&template_item.elements_array, sizeof(struct ipfix_element), NULL); + for (size_t j = 0; j < n_elements_key; j++) + { + cJSON *element_key = cJSON_GetArrayItem(elements_key_array, j); + struct ipfix_elements_hash *elements_array_item = NULL; + HASH_FIND_STR(elements_array_hash, element_key->valuestring, elements_array_item); + if (elements_array_item == NULL) + { + elements_array_item = (struct ipfix_elements_hash *)calloc(1, sizeof(struct ipfix_elements_hash)); + elements_array_item->name = (char *)calloc(strlen(element_key->valuestring) + 1, sizeof(char)); + memcpy(elements_array_item->name, element_key->valuestring, strlen(element_key->valuestring)); + HASH_ADD_STR(elements_array_hash, name, elements_array_item); + elements_array_item->elements_array = ipfix_elements_array_init(root, element_key->valuestring, PEN_number); + } + + if (elements_array_item->elements_array == NULL) + { + goto error; + } + utarray_concat(template_item.elements_array, elements_array_item->elements_array); // merge elements_array + } + utarray_push_back(templates_array, &template_item); + } + ipfix_elements_array_hash_destroy(elements_array_hash); + + size_t sz_blob = 0; + char *template_blob = NULL; + ret = ipfix_template_payload_init(templates_array, &template_blob, &sz_blob); + if (ret == -1 || sz_blob == 0 || template_blob == NULL) + { + goto error; + } + + struct ipfix_worker_context *worker_context_array = (struct ipfix_worker_context *)calloc(n_worker, sizeof(struct ipfix_worker_context)); + for (size_t i = 0; i < n_worker; i++) + { + worker_context_array[i].source_id = (domain_id << 16) | i; + worker_context_array[i].sequence = 0; + worker_context_array[i].template_blob_length = sz_blob; + worker_context_array[i].template_blob = (char *)calloc(sz_blob, sizeof(char)); + memcpy(worker_context_array[i].template_blob, template_blob, sz_blob); + + struct ipfix_message_head *p_message_head = (struct ipfix_message_head *)(worker_context_array[i].template_blob); + p_message_head->version = htons(version); + p_message_head->length = htons(sz_blob); + p_message_head->domain_id = htonl(worker_context_array[i].source_id); + } + + struct ipfix_exporter_schema *_instance = (struct ipfix_exporter_schema *)calloc(1, sizeof(struct ipfix_exporter_schema)); + _instance->version = version; + _instance->PEN_number = PEN_number; + _instance->templates_refresh_interval_s = refresh_interval_s; + _instance->domain_id = domain_id; + _instance->n_worker = n_worker; + _instance->templates_array = templates_array; + _instance->worker_context_array = worker_context_array; + + free(template_blob); + cJSON_Delete(root); + free(json_str); + json_str = NULL; + return _instance; + +error: + utarray_free(templates_array); + cJSON_Delete(root); + free(json_str); + json_str = NULL; + return NULL; +} + +int utable_ipfix_template_get(struct ipfix_exporter_schema *instance, const char *template_name) +{ + if (instance == NULL || template_name == NULL) + { + return -1; + } + + size_t n_template = utarray_len(instance->templates_array); + for (size_t i = 0; i < n_template; i++) + { + struct ipfix_template *template_item = (struct ipfix_template *)utarray_eltptr(instance->templates_array, i); + if (strcmp(template_item->name, template_name) == 0) + { + return i; + } + } + + return -1; +} + +char *ipfix_data_interger_serialize(char *buff, size_t *offset, size_t *buff_sz, int64_t value, size_t sz_value) +{ + if (*offset + sz_value >= *buff_sz) + { + char *new_buff = (char *)realloc(buff, *offset + sz_value + IPFIX_BUFF_MAX_SIZE); + if (new_buff == NULL) + { + free(buff); + return NULL; + } + buff = new_buff; + *buff_sz = *offset + sz_value + IPFIX_BUFF_MAX_SIZE; + } + + switch (sz_value) + { + case 1: + *(uint8_t *)(buff + *offset) = (uint8_t)value; + *offset += 1; + break; + case 2: + *(uint16_t *)(buff + *offset) = htons((uint16_t)value); + *offset += 2; + break; + case 4: + *(uint32_t *)(buff + *offset) = htonl((uint32_t)value); + *offset += 4; + break; + case 8: + *(uint64_t *)(buff + *offset) = htobe64((uint64_t)value); + *offset += 8; + break; + } + + return buff; +} + +char *ipfix_data_variable_serialize(char *buff, size_t *offset, size_t *buff_sz, char *value, size_t sz_value) +{ + if (*offset + sz_value + 3 >= *buff_sz) // 3 means variable_string length > 255, need 3 bytes to describe length + { + char *new_buff = (char *)realloc(buff, *offset + sz_value + IPFIX_BUFF_MAX_SIZE); + if (new_buff == NULL) + { + free(buff); + return NULL; + } + buff = new_buff; + *buff_sz = *offset + sz_value + IPFIX_BUFF_MAX_SIZE; + } + + if (sz_value == 0 || value == NULL) // value == NULL need 1 byte to describe length + { + *(uint8_t *)(buff + *offset) = 0; + *offset += 1; + } + else if (sz_value < 255) + { + *(uint8_t *)(buff + *offset) = (uint8_t)sz_value; + *offset += 1; + memcpy(buff + *offset, value, sz_value); + *offset += sz_value; + } + else // >= 255 + { + *(uint8_t *)(buff + *offset) = 255; + *offset += 1; + *(uint16_t *)(buff + *offset) = htons(sz_value); + *offset += 2; + memcpy(buff + *offset, value, sz_value); + *offset += sz_value; + } + + return buff; +} + +int utable_ipfix_data_flow_exporter(const struct utable *table, struct ipfix_exporter_schema *instance, int template_id, uint16_t worker_id, char **blob, size_t *blob_len) +{ + if (table == NULL || instance == NULL || worker_id >= instance->n_worker || template_id < 0 || template_id >= (int)utarray_len(instance->templates_array)) + { + return -1; + } + + struct ipfix_template *template_item = (struct ipfix_template *)utarray_eltptr(instance->templates_array, (unsigned int)template_id); + if (template_item == NULL) + { + return -1; + } + + size_t offset = 0; + size_t n_elements = utarray_len(template_item->elements_array); + size_t buff_sz = IPFIX_BUFF_MAX_SIZE; + char *buff = (char *)calloc(IPFIX_BUFF_MAX_SIZE, sizeof(char)); + offset += IPFIX_MESSAGE_HEAD_LEN; // skip ipfix_message_head + + *(uint16_t *)(buff + offset) = htons(template_item->template_id); // data_flow_set_id + offset += sizeof(uint16_t); // skip data_flow_set_id + offset += sizeof(uint16_t); // skip data_flow_set_length + for (size_t i = 0; i < n_elements; i++) + { + struct ipfix_element *p_element = (struct ipfix_element *)utarray_eltptr(template_item->elements_array, i); + enum utable_value_type value_type = utable_get_value_type(table, p_element->name); + switch (value_type) + { + case utable_value_type_cstring: + case utable_value_type_blob: + { + char *value = NULL; + size_t sz_value = 0; + utable_get0_cstring_value(table, p_element->name, &value, &sz_value); + buff = ipfix_data_variable_serialize(buff, &offset, &buff_sz, value, sz_value); + break; + } + case utable_value_type_integer: + { + int64_t value = 0; + utable_get0_integer_value(table, p_element->name, &value); + buff = ipfix_data_interger_serialize(buff, &offset, &buff_sz, value, p_element->value_length); + break; + } + case utable_value_type_integer_array: + { + size_t n_array = 0; + int64_t *value_array = NULL; + utable_get0_integer_value_array(table, p_element->name, &value_array, &n_array); + yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); + yyjson_mut_val *arr = yyjson_mut_arr_with_sint64(doc, value_array, n_array); + yyjson_mut_doc_set_root(doc, arr); + char *value_array_str = yyjson_mut_write(doc, 0, NULL); + if(value_array_str) + { + buff = ipfix_data_variable_serialize(buff, &offset, &buff_sz, value_array_str, + strlen(value_array_str)); + yyjson_mut_doc_free(doc); + free(value_array_str); + } + break; + } + case utable_value_type_cstring_array: + { + size_t n_array = 0; + char **value_array = NULL; + size_t *value_sz = NULL; + + utable_get0_cstring_value_array(table, p_element->name, &value_array, &value_sz, &n_array); + yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); + yyjson_mut_val *arr = yyjson_mut_arr_with_strn(doc, (const char **)value_array, value_sz,n_array); + yyjson_mut_doc_set_root(doc, arr); + char *value_array_str = yyjson_mut_write(doc, 0, NULL); + if (value_array_str) + { + buff = ipfix_data_variable_serialize(buff, &offset, &buff_sz, value_array_str, + strlen(value_array_str)); + yyjson_mut_doc_free(doc); + free(value_array_str); + } + break; + } + default: // utable_value_type_undefined, ipfix append 0 + { + switch (p_element->type) + { + case IPFIX_UNSIGNED8: + case IPFIX_UNSIGNED16: + case IPFIX_UNSIGNED32: + case IPFIX_UNSIGNED64: + buff = ipfix_data_interger_serialize(buff, &offset, &buff_sz, 0, p_element->value_length); + break; + case IPFIX_VARIABLE_STRING: + buff = ipfix_data_variable_serialize(buff, &offset, &buff_sz, NULL, 0); + break; + default: + { + free(buff); + buff = NULL; + return -1; + } + } + break; + } + } + } + *(uint16_t *)(buff + IPFIX_MESSAGE_HEAD_LEN + sizeof(uint16_t)) = htons(offset - IPFIX_MESSAGE_HEAD_LEN); // data_flow_set_length + + struct ipfix_message_head *p_message_head = (struct ipfix_message_head *)(buff); + p_message_head->version = htons(instance->version); + p_message_head->length = htons(offset); + p_message_head->exporttime = htonl(time(NULL)); // time_t is long int, but exporttime is int + p_message_head->ipfix_message_sequence = htonl(instance->worker_context_array[worker_id].sequence++); + p_message_head->domain_id = htonl(instance->worker_context_array[worker_id].source_id); + *blob = buff; + *blob_len = offset; + return 0; +} + +const char *utable_ipfix_template_flow_get0(struct ipfix_exporter_schema *instance, uint16_t worker_id, size_t *blob_len) +{ + if (instance == NULL || instance->worker_context_array == NULL || worker_id >= instance->n_worker) + { + return NULL; + } + + struct ipfix_message_head *p_message_head = (struct ipfix_message_head *)(instance->worker_context_array[worker_id].template_blob); + + p_message_head->exporttime = htonl(time(NULL)); // time_t is long int, but exporttime is int + p_message_head->ipfix_message_sequence = htonl(instance->worker_context_array[worker_id].sequence); // template sequence is not increase + *blob_len = instance->worker_context_array[worker_id].template_blob_length; + return instance->worker_context_array[worker_id].template_blob; +} + +uint32_t utable_ipfix_template_flow_refresh_interval_s(struct ipfix_exporter_schema *ipfix_schema) +{ + return ipfix_schema->templates_refresh_interval_s; +} \ No newline at end of file diff --git a/deps/utable/version.map b/deps/utable/version.map new file mode 100644 index 0000000..2010900 --- /dev/null +++ b/deps/utable/version.map @@ -0,0 +1,6 @@ +{ + global: + utable_*; + local: + *; /* Hide all other symbols */ +}; \ No newline at end of file diff --git a/deps/yyjson/CMakeLists.txt b/deps/yyjson/CMakeLists.txt new file mode 100644 index 0000000..cd3b5d9 --- /dev/null +++ b/deps/yyjson/CMakeLists.txt @@ -0,0 +1,8 @@ +if (CMAKE_CXX_CPPCHECK) + list(APPEND CMAKE_CXX_CPPCHECK + "--suppress=*:${CMAKE_CURRENT_SOURCE_DIR}/*" + ) + set(CMAKE_C_CPPCHECK ${CMAKE_CXX_CPPCHECK}) +endif() + +add_library(yyjson yyjson.c) \ No newline at end of file diff --git a/deps/yyjson/LICENSE b/deps/yyjson/LICENSE new file mode 100644 index 0000000..a09bff3 --- /dev/null +++ b/deps/yyjson/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 YaoYuan + +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. diff --git a/deps/yyjson/yyjson.c b/deps/yyjson/yyjson.c new file mode 100644 index 0000000..3e74166 --- /dev/null +++ b/deps/yyjson/yyjson.c @@ -0,0 +1,9447 @@ +/*============================================================================== + Copyright (c) 2020 YaoYuan + + 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. + *============================================================================*/ + +#include "yyjson.h" +#include + + + +/*============================================================================== + * Warning Suppress + *============================================================================*/ + +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +# pragma clang diagnostic ignored "-Wunused-parameter" +# pragma clang diagnostic ignored "-Wunused-label" +# pragma clang diagnostic ignored "-Wunused-macros" +# pragma clang diagnostic ignored "-Wunused-variable" +#elif defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wunused-parameter" +# pragma GCC diagnostic ignored "-Wunused-label" +# pragma GCC diagnostic ignored "-Wunused-macros" +# pragma GCC diagnostic ignored "-Wunused-variable" +#elif defined(_MSC_VER) +# pragma warning(disable:4100) /* unreferenced formal parameter */ +# pragma warning(disable:4101) /* unreferenced variable */ +# pragma warning(disable:4102) /* unreferenced label */ +# pragma warning(disable:4127) /* conditional expression is constant */ +# pragma warning(disable:4706) /* assignment within conditional expression */ +#endif + + + +/*============================================================================== + * Version + *============================================================================*/ + +uint32_t yyjson_version(void) { + return YYJSON_VERSION_HEX; +} + + + +/*============================================================================== + * Flags + *============================================================================*/ + +/* msvc intrinsic */ +#if YYJSON_MSC_VER >= 1400 +# include +# if defined(_M_AMD64) || defined(_M_ARM64) +# define MSC_HAS_BIT_SCAN_64 1 +# pragma intrinsic(_BitScanForward64) +# pragma intrinsic(_BitScanReverse64) +# else +# define MSC_HAS_BIT_SCAN_64 0 +# endif +# if defined(_M_AMD64) || defined(_M_ARM64) || \ + defined(_M_IX86) || defined(_M_ARM) +# define MSC_HAS_BIT_SCAN 1 +# pragma intrinsic(_BitScanForward) +# pragma intrinsic(_BitScanReverse) +# else +# define MSC_HAS_BIT_SCAN 0 +# endif +# if defined(_M_AMD64) +# define MSC_HAS_UMUL128 1 +# pragma intrinsic(_umul128) +# else +# define MSC_HAS_UMUL128 0 +# endif +#else +# define MSC_HAS_BIT_SCAN_64 0 +# define MSC_HAS_BIT_SCAN 0 +# define MSC_HAS_UMUL128 0 +#endif + +/* gcc builtin */ +#if yyjson_has_builtin(__builtin_clzll) || yyjson_gcc_available(3, 4, 0) +# define GCC_HAS_CLZLL 1 +#else +# define GCC_HAS_CLZLL 0 +#endif + +#if yyjson_has_builtin(__builtin_ctzll) || yyjson_gcc_available(3, 4, 0) +# define GCC_HAS_CTZLL 1 +#else +# define GCC_HAS_CTZLL 0 +#endif + +/* int128 type */ +#if defined(__SIZEOF_INT128__) && (__SIZEOF_INT128__ == 16) && \ + (defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER)) +# define YYJSON_HAS_INT128 1 +#else +# define YYJSON_HAS_INT128 0 +#endif + +/* IEEE 754 floating-point binary representation */ +#if defined(__STDC_IEC_559__) || defined(__STDC_IEC_60559_BFP__) +# define YYJSON_HAS_IEEE_754 1 +#elif (FLT_RADIX == 2) && (DBL_MANT_DIG == 53) && (DBL_DIG == 15) && \ + (DBL_MIN_EXP == -1021) && (DBL_MAX_EXP == 1024) && \ + (DBL_MIN_10_EXP == -307) && (DBL_MAX_10_EXP == 308) +# define YYJSON_HAS_IEEE_754 1 +#else +# define YYJSON_HAS_IEEE_754 0 +#endif + +/* + Correct rounding in double number computations. + + On the x86 architecture, some compilers may use x87 FPU instructions for + floating-point arithmetic. The x87 FPU loads all floating point number as + 80-bit double-extended precision internally, then rounds the result to original + precision, which may produce inaccurate results. For a more detailed + explanation, see the paper: https://arxiv.org/abs/cs/0701192 + + Here are some examples of double precision calculation error: + + 2877.0 / 1e6 == 0.002877, but x87 returns 0.0028770000000000002 + 43683.0 * 1e21 == 4.3683e25, but x87 returns 4.3683000000000004e25 + + Here are some examples of compiler flags to generate x87 instructions on x86: + + clang -m32 -mno-sse + gcc/icc -m32 -mfpmath=387 + msvc /arch:SSE or /arch:IA32 + + If we are sure that there's no similar error described above, we can define the + YYJSON_DOUBLE_MATH_CORRECT as 1 to enable the fast path calculation. This is + not an accurate detection, it's just try to avoid the error at compile-time. + An accurate detection can be done at run-time: + + bool is_double_math_correct(void) { + volatile double r = 43683.0; + r *= 1e21; + return r == 4.3683e25; + } + + See also: utils.h in https://github.com/google/double-conversion/ + */ +#if !defined(FLT_EVAL_METHOD) && defined(__FLT_EVAL_METHOD__) +# define FLT_EVAL_METHOD __FLT_EVAL_METHOD__ +#endif + +#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0 && FLT_EVAL_METHOD != 1 +# define YYJSON_DOUBLE_MATH_CORRECT 0 +#elif defined(i386) || defined(__i386) || defined(__i386__) || \ + defined(_X86_) || defined(__X86__) || defined(_M_IX86) || \ + defined(__I86__) || defined(__IA32__) || defined(__THW_INTEL) +# if (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 2) || \ + (defined(__SSE2_MATH__) && __SSE2_MATH__) +# define YYJSON_DOUBLE_MATH_CORRECT 1 +# else +# define YYJSON_DOUBLE_MATH_CORRECT 0 +# endif +#elif defined(__mc68000__) || defined(__pnacl__) || defined(__native_client__) +# define YYJSON_DOUBLE_MATH_CORRECT 0 +#else +# define YYJSON_DOUBLE_MATH_CORRECT 1 +#endif + +/* endian */ +#if yyjson_has_include() +# include /* POSIX */ +#endif +#if yyjson_has_include() +# include /* Linux */ +#elif yyjson_has_include() +# include /* BSD, Android */ +#elif yyjson_has_include() +# include /* BSD, Darwin */ +#endif + +#define YYJSON_BIG_ENDIAN 4321 +#define YYJSON_LITTLE_ENDIAN 1234 + +#if defined(BYTE_ORDER) && BYTE_ORDER +# if defined(BIG_ENDIAN) && (BYTE_ORDER == BIG_ENDIAN) +# define YYJSON_ENDIAN YYJSON_BIG_ENDIAN +# elif defined(LITTLE_ENDIAN) && (BYTE_ORDER == LITTLE_ENDIAN) +# define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN +# endif + +#elif defined(__BYTE_ORDER) && __BYTE_ORDER +# if defined(__BIG_ENDIAN) && (__BYTE_ORDER == __BIG_ENDIAN) +# define YYJSON_ENDIAN YYJSON_BIG_ENDIAN +# elif defined(__LITTLE_ENDIAN) && (__BYTE_ORDER == __LITTLE_ENDIAN) +# define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN +# endif + +#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ +# if defined(__ORDER_BIG_ENDIAN__) && \ + (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +# define YYJSON_ENDIAN YYJSON_BIG_ENDIAN +# elif defined(__ORDER_LITTLE_ENDIAN__) && \ + (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +# define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN +# endif + +#elif (defined(__LITTLE_ENDIAN__) && __LITTLE_ENDIAN__ == 1) || \ + defined(__i386) || defined(__i386__) || \ + defined(_X86_) || defined(__X86__) || \ + defined(_M_IX86) || defined(__THW_INTEL__) || \ + defined(__x86_64) || defined(__x86_64__) || \ + defined(__amd64) || defined(__amd64__) || \ + defined(_M_AMD64) || defined(_M_X64) || \ + defined(_M_ARM) || defined(_M_ARM64) || \ + defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || \ + defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \ + defined(__EMSCRIPTEN__) || defined(__wasm__) || \ + defined(__loongarch__) +# define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN + +#elif (defined(__BIG_ENDIAN__) && __BIG_ENDIAN__ == 1) || \ + defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ + defined(_MIPSEB) || defined(__MIPSEB) || defined(__MIPSEB__) || \ + defined(__or1k__) || defined(__OR1K__) +# define YYJSON_ENDIAN YYJSON_BIG_ENDIAN + +#else +# define YYJSON_ENDIAN 0 /* unknown endian, detect at run-time */ +#endif + +/* + This macro controls how yyjson handles unaligned memory accesses. + + By default, yyjson uses `memcpy()` for memory copying. This takes advantage of + the compiler's automatic optimizations to generate unaligned memory access + instructions when the target architecture supports it. + + However, for some older compilers or architectures where `memcpy()` isn't + optimized well and may generate unnecessary function calls, consider defining + this macro as 1. In such cases, yyjson switches to manual byte-by-byte access, + potentially improving performance. An example of the generated assembly code on + the ARM platform can be found here: https://godbolt.org/z/334jjhxPT + + As this flag has already been enabled for some common architectures in the + following code, users typically don't need to manually specify it. If users are + unsure about it, please review the generated assembly code or perform actual + benchmark to make an informed decision. + */ +#ifndef YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS +# if defined(__ia64) || defined(_IA64) || defined(__IA64__) || \ + defined(__ia64__) || defined(_M_IA64) || defined(__itanium__) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* Itanium */ +# elif (defined(__arm__) || defined(__arm64__) || defined(__aarch64__)) && \ + (defined(__GNUC__) || defined(__clang__)) && \ + (!defined(__ARM_FEATURE_UNALIGNED) || !__ARM_FEATURE_UNALIGNED) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* ARM */ +# elif defined(__sparc) || defined(__sparc__) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* SPARC */ +# elif defined(__mips) || defined(__mips__) || defined(__MIPS__) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* MIPS */ +# elif defined(__m68k__) || defined(M68000) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* M68K */ +# else +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 0 +# endif +#endif + +/* + Estimated initial ratio of the JSON data (data_size / value_count). + For example: + + data: {"id":12345678,"name":"Harry"} + data_size: 30 + value_count: 5 + ratio: 6 + + yyjson uses dynamic memory with a growth factor of 1.5 when reading and writing + JSON, the ratios below are used to determine the initial memory size. + + A too large ratio will waste memory, and a too small ratio will cause multiple + memory growths and degrade performance. Currently, these ratios are generated + with some commonly used JSON datasets. + */ +#define YYJSON_READER_ESTIMATED_PRETTY_RATIO 16 +#define YYJSON_READER_ESTIMATED_MINIFY_RATIO 6 +#define YYJSON_WRITER_ESTIMATED_PRETTY_RATIO 32 +#define YYJSON_WRITER_ESTIMATED_MINIFY_RATIO 18 + +/* The initial and maximum size of the memory pool's chunk in yyjson_mut_doc. */ +#define YYJSON_MUT_DOC_STR_POOL_INIT_SIZE 0x100 +#define YYJSON_MUT_DOC_STR_POOL_MAX_SIZE 0x10000000 +#define YYJSON_MUT_DOC_VAL_POOL_INIT_SIZE (0x10 * sizeof(yyjson_mut_val)) +#define YYJSON_MUT_DOC_VAL_POOL_MAX_SIZE (0x1000000 * sizeof(yyjson_mut_val)) + +/* The minimum size of the dynamic allocator's chunk. */ +#define YYJSON_ALC_DYN_MIN_SIZE 0x1000 + +/* Default value for compile-time options. */ +#ifndef YYJSON_DISABLE_READER +#define YYJSON_DISABLE_READER 0 +#endif +#ifndef YYJSON_DISABLE_WRITER +#define YYJSON_DISABLE_WRITER 0 +#endif +#ifndef YYJSON_DISABLE_UTILS +#define YYJSON_DISABLE_UTILS 0 +#endif +#ifndef YYJSON_DISABLE_FAST_FP_CONV +#define YYJSON_DISABLE_FAST_FP_CONV 0 +#endif +#ifndef YYJSON_DISABLE_NON_STANDARD +#define YYJSON_DISABLE_NON_STANDARD 0 +#endif +#ifndef YYJSON_DISABLE_UTF8_VALIDATION +#define YYJSON_DISABLE_UTF8_VALIDATION 0 +#endif + + + +/*============================================================================== + * Macros + *============================================================================*/ + +/* Macros used for loop unrolling and other purpose. */ +#define repeat2(x) { x x } +#define repeat3(x) { x x x } +#define repeat4(x) { x x x x } +#define repeat8(x) { x x x x x x x x } +#define repeat16(x) { x x x x x x x x x x x x x x x x } + +#define repeat2_incr(x) { x(0) x(1) } +#define repeat4_incr(x) { x(0) x(1) x(2) x(3) } +#define repeat8_incr(x) { x(0) x(1) x(2) x(3) x(4) x(5) x(6) x(7) } +#define repeat16_incr(x) { x(0) x(1) x(2) x(3) x(4) x(5) x(6) x(7) \ + x(8) x(9) x(10) x(11) x(12) x(13) x(14) x(15) } + +#define repeat_in_1_18(x) { x(1) x(2) x(3) x(4) x(5) x(6) x(7) x(8) \ + x(9) x(10) x(11) x(12) x(13) x(14) x(15) x(16) \ + x(17) x(18) } + +/* Macros used to provide branch prediction information for compiler. */ +#undef likely +#define likely(x) yyjson_likely(x) +#undef unlikely +#define unlikely(x) yyjson_unlikely(x) + +/* Macros used to provide inline information for compiler. */ +#undef static_inline +#define static_inline static yyjson_inline +#undef static_noinline +#define static_noinline static yyjson_noinline + +/* Macros for min and max. */ +#undef yyjson_min +#define yyjson_min(x, y) ((x) < (y) ? (x) : (y)) +#undef yyjson_max +#define yyjson_max(x, y) ((x) > (y) ? (x) : (y)) + +/* Used to write u64 literal for C89 which doesn't support "ULL" suffix. */ +#undef U64 +#define U64(hi, lo) ((((u64)hi##UL) << 32U) + lo##UL) + +/* Used to cast away (remove) const qualifier. */ +#define constcast(type) (type)(void *)(size_t)(const void *) + +/* flag test */ +#define has_read_flag(_flag) unlikely(read_flag_eq(flg, YYJSON_READ_##_flag)) +#define has_write_flag(_flag) unlikely(write_flag_eq(flg, YYJSON_WRITE_##_flag)) + +static_inline bool read_flag_eq(yyjson_read_flag flg, yyjson_read_flag chk) { +#if YYJSON_DISABLE_NON_STANDARD + if (chk == YYJSON_READ_ALLOW_INF_AND_NAN || + chk == YYJSON_READ_ALLOW_COMMENTS || + chk == YYJSON_READ_ALLOW_TRAILING_COMMAS || + chk == YYJSON_READ_ALLOW_INVALID_UNICODE) + return false; /* this should be evaluated at compile-time */ +#endif + return (flg & chk) != 0; +} + +static_inline bool write_flag_eq(yyjson_write_flag flg, yyjson_write_flag chk) { +#if YYJSON_DISABLE_NON_STANDARD + if (chk == YYJSON_WRITE_ALLOW_INF_AND_NAN || + chk == YYJSON_WRITE_ALLOW_INVALID_UNICODE) + return false; /* this should be evaluated at compile-time */ +#endif + return (flg & chk) != 0; +} + + + +/*============================================================================== + * Integer Constants + *============================================================================*/ + +/* U64 constant values */ +#undef U64_MAX +#define U64_MAX U64(0xFFFFFFFF, 0xFFFFFFFF) +#undef I64_MAX +#define I64_MAX U64(0x7FFFFFFF, 0xFFFFFFFF) +#undef USIZE_MAX +#define USIZE_MAX ((usize)(~(usize)0)) + +/* Maximum number of digits for reading u32/u64/usize safety (not overflow). */ +#undef U32_SAFE_DIG +#define U32_SAFE_DIG 9 /* u32 max is 4294967295, 10 digits */ +#undef U64_SAFE_DIG +#define U64_SAFE_DIG 19 /* u64 max is 18446744073709551615, 20 digits */ +#undef USIZE_SAFE_DIG +#define USIZE_SAFE_DIG (sizeof(usize) == 8 ? U64_SAFE_DIG : U32_SAFE_DIG) + + + +/*============================================================================== + * IEEE-754 Double Number Constants + *============================================================================*/ + +/* Inf raw value (positive) */ +#define F64_RAW_INF U64(0x7FF00000, 0x00000000) + +/* NaN raw value (quiet NaN, no payload, no sign) */ +#if defined(__hppa__) || (defined(__mips__) && !defined(__mips_nan2008)) +#define F64_RAW_NAN U64(0x7FF7FFFF, 0xFFFFFFFF) +#else +#define F64_RAW_NAN U64(0x7FF80000, 0x00000000) +#endif + +/* double number bits */ +#define F64_BITS 64 + +/* double number exponent part bits */ +#define F64_EXP_BITS 11 + +/* double number significand part bits */ +#define F64_SIG_BITS 52 + +/* double number significand part bits (with 1 hidden bit) */ +#define F64_SIG_FULL_BITS 53 + +/* double number significand bit mask */ +#define F64_SIG_MASK U64(0x000FFFFF, 0xFFFFFFFF) + +/* double number exponent bit mask */ +#define F64_EXP_MASK U64(0x7FF00000, 0x00000000) + +/* double number exponent bias */ +#define F64_EXP_BIAS 1023 + +/* double number significant digits count in decimal */ +#define F64_DEC_DIG 17 + +/* max significant digits count in decimal when reading double number */ +#define F64_MAX_DEC_DIG 768 + +/* maximum decimal power of double number (1.7976931348623157e308) */ +#define F64_MAX_DEC_EXP 308 + +/* minimum decimal power of double number (4.9406564584124654e-324) */ +#define F64_MIN_DEC_EXP (-324) + +/* maximum binary power of double number */ +#define F64_MAX_BIN_EXP 1024 + +/* minimum binary power of double number */ +#define F64_MIN_BIN_EXP (-1021) + + + +/*============================================================================== + * Types + *============================================================================*/ + +/** Type define for primitive types. */ +typedef float f32; +typedef double f64; +typedef int8_t i8; +typedef uint8_t u8; +typedef int16_t i16; +typedef uint16_t u16; +typedef int32_t i32; +typedef uint32_t u32; +typedef int64_t i64; +typedef uint64_t u64; +typedef size_t usize; + +/** 128-bit integer, used by floating-point number reader and writer. */ +#if YYJSON_HAS_INT128 +__extension__ typedef __int128 i128; +__extension__ typedef unsigned __int128 u128; +#endif + +/** 16/32/64-bit vector */ +typedef struct v16 { char c[2]; } v16; +typedef struct v32 { char c[4]; } v32; +typedef struct v64 { char c[8]; } v64; + +/** 16/32/64-bit vector union */ +typedef union v16_uni { v16 v; u16 u; } v16_uni; +typedef union v32_uni { v32 v; u32 u; } v32_uni; +typedef union v64_uni { v64 v; u64 u; } v64_uni; + + + +/*============================================================================== + * Load/Store Utils + *============================================================================*/ + +#if YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + +#define byte_move_idx(x) ((char *)dst)[x] = ((const char *)src)[x]; + +static_inline void byte_copy_2(void *dst, const void *src) { + repeat2_incr(byte_move_idx) +} + +static_inline void byte_copy_4(void *dst, const void *src) { + repeat4_incr(byte_move_idx) +} + +static_inline void byte_copy_8(void *dst, const void *src) { + repeat8_incr(byte_move_idx) +} + +static_inline void byte_copy_16(void *dst, const void *src) { + repeat16_incr(byte_move_idx) +} + +static_inline void byte_move_2(void *dst, const void *src) { + repeat2_incr(byte_move_idx) +} + +static_inline void byte_move_4(void *dst, const void *src) { + repeat4_incr(byte_move_idx) +} + +static_inline void byte_move_8(void *dst, const void *src) { + repeat8_incr(byte_move_idx) +} + +static_inline void byte_move_16(void *dst, const void *src) { + repeat16_incr(byte_move_idx) +} + +static_inline bool byte_match_2(void *buf, const char *pat) { + return + ((char *)buf)[0] == ((const char *)pat)[0] && + ((char *)buf)[1] == ((const char *)pat)[1]; +} + +static_inline bool byte_match_4(void *buf, const char *pat) { + return + ((char *)buf)[0] == ((const char *)pat)[0] && + ((char *)buf)[1] == ((const char *)pat)[1] && + ((char *)buf)[2] == ((const char *)pat)[2] && + ((char *)buf)[3] == ((const char *)pat)[3]; +} + +static_inline u16 byte_load_2(const void *src) { + v16_uni uni; + uni.v.c[0] = ((const char *)src)[0]; + uni.v.c[1] = ((const char *)src)[1]; + return uni.u; +} + +static_inline u32 byte_load_3(const void *src) { + v32_uni uni; + uni.v.c[0] = ((const char *)src)[0]; + uni.v.c[1] = ((const char *)src)[1]; + uni.v.c[2] = ((const char *)src)[2]; + uni.v.c[3] = 0; + return uni.u; +} + +static_inline u32 byte_load_4(const void *src) { + v32_uni uni; + uni.v.c[0] = ((const char *)src)[0]; + uni.v.c[1] = ((const char *)src)[1]; + uni.v.c[2] = ((const char *)src)[2]; + uni.v.c[3] = ((const char *)src)[3]; + return uni.u; +} + +#undef byte_move_expr + +#else + +static_inline void byte_copy_2(void *dst, const void *src) { + memcpy(dst, src, 2); +} + +static_inline void byte_copy_4(void *dst, const void *src) { + memcpy(dst, src, 4); +} + +static_inline void byte_copy_8(void *dst, const void *src) { + memcpy(dst, src, 8); +} + +static_inline void byte_copy_16(void *dst, const void *src) { + memcpy(dst, src, 16); +} + +static_inline void byte_move_2(void *dst, const void *src) { + u16 tmp; + memcpy(&tmp, src, 2); + memcpy(dst, &tmp, 2); +} + +static_inline void byte_move_4(void *dst, const void *src) { + u32 tmp; + memcpy(&tmp, src, 4); + memcpy(dst, &tmp, 4); +} + +static_inline void byte_move_8(void *dst, const void *src) { + u64 tmp; + memcpy(&tmp, src, 8); + memcpy(dst, &tmp, 8); +} + +static_inline void byte_move_16(void *dst, const void *src) { + char *pdst = (char *)dst; + const char *psrc = (const char *)src; + u64 tmp1, tmp2; + memcpy(&tmp1, psrc, 8); + memcpy(&tmp2, psrc + 8, 8); + memcpy(pdst, &tmp1, 8); + memcpy(pdst + 8, &tmp2, 8); +} + +static_inline bool byte_match_2(void *buf, const char *pat) { + v16_uni u1, u2; + memcpy(&u1, buf, 2); + memcpy(&u2, pat, 2); + return u1.u == u2.u; +} + +static_inline bool byte_match_4(void *buf, const char *pat) { + v32_uni u1, u2; + memcpy(&u1, buf, 4); + memcpy(&u2, pat, 4); + return u1.u == u2.u; +} + +static_inline u16 byte_load_2(const void *src) { + v16_uni uni; + memcpy(&uni, src, 2); + return uni.u; +} + +static_inline u32 byte_load_3(const void *src) { + v32_uni uni; + memcpy(&uni, src, 2); + uni.v.c[2] = ((const char *)src)[2]; + uni.v.c[3] = 0; + return uni.u; +} + +static_inline u32 byte_load_4(const void *src) { + v32_uni uni; + memcpy(&uni, src, 4); + return uni.u; +} + +#endif + + + +/*============================================================================== + * Number Utils + * These functions are used to detect and convert NaN and Inf numbers. + *============================================================================*/ + +/** Convert raw binary to double. */ +static_inline f64 f64_from_raw(u64 u) { + /* use memcpy to avoid violating the strict aliasing rule */ + f64 f; + memcpy(&f, &u, 8); + return f; +} + +/** Convert double to raw binary. */ +static_inline u64 f64_to_raw(f64 f) { + /* use memcpy to avoid violating the strict aliasing rule */ + u64 u; + memcpy(&u, &f, 8); + return u; +} + +/** Get raw 'infinity' with sign. */ +static_inline u64 f64_raw_get_inf(bool sign) { +#if YYJSON_HAS_IEEE_754 + return F64_RAW_INF | ((u64)sign << 63); +#elif defined(INFINITY) + return f64_to_raw(sign ? -INFINITY : INFINITY); +#else + return f64_to_raw(sign ? -HUGE_VAL : HUGE_VAL); +#endif +} + +/** Get raw 'nan' with sign. */ +static_inline u64 f64_raw_get_nan(bool sign) { +#if YYJSON_HAS_IEEE_754 + return F64_RAW_NAN | ((u64)sign << 63); +#elif defined(NAN) + return f64_to_raw(sign ? (f64)-NAN : (f64)NAN); +#else + return f64_to_raw((sign ? -0.0 : 0.0) / 0.0); +#endif +} + +/** + Convert normalized u64 (highest bit is 1) to f64. + + Some compiler (such as Microsoft Visual C++ 6.0) do not support converting + number from u64 to f64. This function will first convert u64 to i64 and then + to f64, with `to nearest` rounding mode. + */ +static_inline f64 normalized_u64_to_f64(u64 val) { +#if YYJSON_U64_TO_F64_NO_IMPL + i64 sig = (i64)((val >> 1) | (val & 1)); + return ((f64)sig) * (f64)2.0; +#else + return (f64)val; +#endif +} + + + +/*============================================================================== + * Size Utils + * These functions are used for memory allocation. + *============================================================================*/ + +/** Returns whether the size is overflow after increment. */ +static_inline bool size_add_is_overflow(usize size, usize add) { + return size > (size + add); +} + +/** Returns whether the size is power of 2 (size should not be 0). */ +static_inline bool size_is_pow2(usize size) { + return (size & (size - 1)) == 0; +} + +/** Align size upwards (may overflow). */ +static_inline usize size_align_up(usize size, usize align) { + if (size_is_pow2(align)) { + return (size + (align - 1)) & ~(align - 1); + } else { + return size + align - (size + align - 1) % align - 1; + } +} + +/** Align size downwards. */ +static_inline usize size_align_down(usize size, usize align) { + if (size_is_pow2(align)) { + return size & ~(align - 1); + } else { + return size - (size % align); + } +} + +/** Align address upwards (may overflow). */ +static_inline void *mem_align_up(void *mem, usize align) { + usize size; + memcpy(&size, &mem, sizeof(usize)); + size = size_align_up(size, align); + memcpy(&mem, &size, sizeof(usize)); + return mem; +} + + + +/*============================================================================== + * Bits Utils + * These functions are used by the floating-point number reader and writer. + *============================================================================*/ + +/** Returns the number of leading 0-bits in value (input should not be 0). */ +static_inline u32 u64_lz_bits(u64 v) { +#if GCC_HAS_CLZLL + return (u32)__builtin_clzll(v); +#elif MSC_HAS_BIT_SCAN_64 + unsigned long r; + _BitScanReverse64(&r, v); + return (u32)63 - (u32)r; +#elif MSC_HAS_BIT_SCAN + unsigned long hi, lo; + bool hi_set = _BitScanReverse(&hi, (u32)(v >> 32)) != 0; + _BitScanReverse(&lo, (u32)v); + hi |= 32; + return (u32)63 - (u32)(hi_set ? hi : lo); +#else + /* + branchless, use de Bruijn sequences + see: https://www.chessprogramming.org/BitScan + */ + const u8 table[64] = { + 63, 16, 62, 7, 15, 36, 61, 3, 6, 14, 22, 26, 35, 47, 60, 2, + 9, 5, 28, 11, 13, 21, 42, 19, 25, 31, 34, 40, 46, 52, 59, 1, + 17, 8, 37, 4, 23, 27, 48, 10, 29, 12, 43, 20, 32, 41, 53, 18, + 38, 24, 49, 30, 44, 33, 54, 39, 50, 45, 55, 51, 56, 57, 58, 0 + }; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v |= v >> 32; + return table[(v * U64(0x03F79D71, 0xB4CB0A89)) >> 58]; +#endif +} + +/** Returns the number of trailing 0-bits in value (input should not be 0). */ +static_inline u32 u64_tz_bits(u64 v) { +#if GCC_HAS_CTZLL + return (u32)__builtin_ctzll(v); +#elif MSC_HAS_BIT_SCAN_64 + unsigned long r; + _BitScanForward64(&r, v); + return (u32)r; +#elif MSC_HAS_BIT_SCAN + unsigned long lo, hi; + bool lo_set = _BitScanForward(&lo, (u32)(v)) != 0; + _BitScanForward(&hi, (u32)(v >> 32)); + hi += 32; + return lo_set ? lo : hi; +#else + /* + branchless, use de Bruijn sequences + see: https://www.chessprogramming.org/BitScan + */ + const u8 table[64] = { + 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, + 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, + 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, + 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12 + }; + return table[((v & (~v + 1)) * U64(0x022FDD63, 0xCC95386D)) >> 58]; +#endif +} + + + +/*============================================================================== + * 128-bit Integer Utils + * These functions are used by the floating-point number reader and writer. + *============================================================================*/ + +/** Multiplies two 64-bit unsigned integers (a * b), + returns the 128-bit result as 'hi' and 'lo'. */ +static_inline void u128_mul(u64 a, u64 b, u64 *hi, u64 *lo) { +#if YYJSON_HAS_INT128 + u128 m = (u128)a * b; + *hi = (u64)(m >> 64); + *lo = (u64)(m); +#elif MSC_HAS_UMUL128 + *lo = _umul128(a, b, hi); +#else + u32 a0 = (u32)(a), a1 = (u32)(a >> 32); + u32 b0 = (u32)(b), b1 = (u32)(b >> 32); + u64 p00 = (u64)a0 * b0, p01 = (u64)a0 * b1; + u64 p10 = (u64)a1 * b0, p11 = (u64)a1 * b1; + u64 m0 = p01 + (p00 >> 32); + u32 m00 = (u32)(m0), m01 = (u32)(m0 >> 32); + u64 m1 = p10 + m00; + u32 m10 = (u32)(m1), m11 = (u32)(m1 >> 32); + *hi = p11 + m01 + m11; + *lo = ((u64)m10 << 32) | (u32)p00; +#endif +} + +/** Multiplies two 64-bit unsigned integers and add a value (a * b + c), + returns the 128-bit result as 'hi' and 'lo'. */ +static_inline void u128_mul_add(u64 a, u64 b, u64 c, u64 *hi, u64 *lo) { +#if YYJSON_HAS_INT128 + u128 m = (u128)a * b + c; + *hi = (u64)(m >> 64); + *lo = (u64)(m); +#else + u64 h, l, t; + u128_mul(a, b, &h, &l); + t = l + c; + h += (u64)(((t < l) | (t < c))); + *hi = h; + *lo = t; +#endif +} + + + +/*============================================================================== + * File Utils + * These functions are used to read and write JSON files. + *============================================================================*/ + +#define YYJSON_FOPEN_EXT +#if !defined(_MSC_VER) && defined(__GLIBC__) && defined(__GLIBC_PREREQ) +# if __GLIBC_PREREQ(2, 7) +# undef YYJSON_FOPEN_EXT +# define YYJSON_FOPEN_EXT "e" /* glibc extension to enable O_CLOEXEC */ +# endif +#endif + +static_inline FILE *fopen_safe(const char *path, const char *mode) { +#if YYJSON_MSC_VER >= 1400 + FILE *file = NULL; + if (fopen_s(&file, path, mode) != 0) return NULL; + return file; +#else + return fopen(path, mode); +#endif +} + +static_inline FILE *fopen_readonly(const char *path) { + return fopen_safe(path, "rb" YYJSON_FOPEN_EXT); +} + +static_inline FILE *fopen_writeonly(const char *path) { + return fopen_safe(path, "wb" YYJSON_FOPEN_EXT); +} + +static_inline usize fread_safe(void *buf, usize size, FILE *file) { +#if YYJSON_MSC_VER >= 1400 + return fread_s(buf, size, 1, size, file); +#else + return fread(buf, 1, size, file); +#endif +} + + + +/*============================================================================== + * Default Memory Allocator + * This is a simple libc memory allocator wrapper. + *============================================================================*/ + +static void *default_malloc(void *ctx, usize size) { + return malloc(size); +} + +static void *default_realloc(void *ctx, void *ptr, usize old_size, usize size) { + return realloc(ptr, size); +} + +static void default_free(void *ctx, void *ptr) { + free(ptr); +} + +static const yyjson_alc YYJSON_DEFAULT_ALC = { + default_malloc, + default_realloc, + default_free, + NULL +}; + + + +/*============================================================================== + * Null Memory Allocator + * + * This allocator is just a placeholder to ensure that the internal + * malloc/realloc/free function pointers are not null. + *============================================================================*/ + +static void *null_malloc(void *ctx, usize size) { + return NULL; +} + +static void *null_realloc(void *ctx, void *ptr, usize old_size, usize size) { + return NULL; +} + +static void null_free(void *ctx, void *ptr) { + return; +} + +static const yyjson_alc YYJSON_NULL_ALC = { + null_malloc, + null_realloc, + null_free, + NULL +}; + + + +/*============================================================================== + * Pool Memory Allocator + * + * This allocator is initialized with a fixed-size buffer. + * The buffer is split into multiple memory chunks for memory allocation. + *============================================================================*/ + +/** memory chunk header */ +typedef struct pool_chunk { + usize size; /* chunk memory size, include chunk header */ + struct pool_chunk *next; /* linked list, nullable */ + /* char mem[]; flexible array member */ +} pool_chunk; + +/** allocator ctx header */ +typedef struct pool_ctx { + usize size; /* total memory size, include ctx header */ + pool_chunk *free_list; /* linked list, nullable */ + /* pool_chunk chunks[]; flexible array member */ +} pool_ctx; + +/** align up the input size to chunk size */ +static_inline void pool_size_align(usize *size) { + *size = size_align_up(*size, sizeof(pool_chunk)) + sizeof(pool_chunk); +} + +static void *pool_malloc(void *ctx_ptr, usize size) { + /* assert(size != 0) */ + pool_ctx *ctx = (pool_ctx *)ctx_ptr; + pool_chunk *next, *prev = NULL, *cur = ctx->free_list; + + if (unlikely(size >= ctx->size)) return NULL; + pool_size_align(&size); + + while (cur) { + if (cur->size < size) { + /* not enough space, try next chunk */ + prev = cur; + cur = cur->next; + continue; + } + if (cur->size >= size + sizeof(pool_chunk) * 2) { + /* too much space, split this chunk */ + next = (pool_chunk *)(void *)((u8 *)cur + size); + next->size = cur->size - size; + next->next = cur->next; + cur->size = size; + } else { + /* just enough space, use whole chunk */ + next = cur->next; + } + if (prev) prev->next = next; + else ctx->free_list = next; + return (void *)(cur + 1); + } + return NULL; +} + +static void pool_free(void *ctx_ptr, void *ptr) { + /* assert(ptr != NULL) */ + pool_ctx *ctx = (pool_ctx *)ctx_ptr; + pool_chunk *cur = ((pool_chunk *)ptr) - 1; + pool_chunk *prev = NULL, *next = ctx->free_list; + + while (next && next < cur) { + prev = next; + next = next->next; + } + if (prev) prev->next = cur; + else ctx->free_list = cur; + cur->next = next; + + if (next && ((u8 *)cur + cur->size) == (u8 *)next) { + /* merge cur to higher chunk */ + cur->size += next->size; + cur->next = next->next; + } + if (prev && ((u8 *)prev + prev->size) == (u8 *)cur) { + /* merge cur to lower chunk */ + prev->size += cur->size; + prev->next = cur->next; + } +} + +static void *pool_realloc(void *ctx_ptr, void *ptr, + usize old_size, usize size) { + /* assert(ptr != NULL && size != 0 && old_size < size) */ + pool_ctx *ctx = (pool_ctx *)ctx_ptr; + pool_chunk *cur = ((pool_chunk *)ptr) - 1, *prev, *next, *tmp; + + /* check size */ + if (unlikely(size >= ctx->size)) return NULL; + pool_size_align(&old_size); + pool_size_align(&size); + if (unlikely(old_size == size)) return ptr; + + /* find next and prev chunk */ + prev = NULL; + next = ctx->free_list; + while (next && next < cur) { + prev = next; + next = next->next; + } + + if ((u8 *)cur + cur->size == (u8 *)next && cur->size + next->size >= size) { + /* merge to higher chunk if they are contiguous */ + usize free_size = cur->size + next->size - size; + if (free_size > sizeof(pool_chunk) * 2) { + tmp = (pool_chunk *)(void *)((u8 *)cur + size); + if (prev) prev->next = tmp; + else ctx->free_list = tmp; + tmp->next = next->next; + tmp->size = free_size; + cur->size = size; + } else { + if (prev) prev->next = next->next; + else ctx->free_list = next->next; + cur->size += next->size; + } + return ptr; + } else { + /* fallback to malloc and memcpy */ + void *new_ptr = pool_malloc(ctx_ptr, size - sizeof(pool_chunk)); + if (new_ptr) { + memcpy(new_ptr, ptr, cur->size - sizeof(pool_chunk)); + pool_free(ctx_ptr, ptr); + } + return new_ptr; + } +} + +bool yyjson_alc_pool_init(yyjson_alc *alc, void *buf, usize size) { + pool_chunk *chunk; + pool_ctx *ctx; + + if (unlikely(!alc)) return false; + *alc = YYJSON_NULL_ALC; + if (size < sizeof(pool_ctx) * 4) return false; + ctx = (pool_ctx *)mem_align_up(buf, sizeof(pool_ctx)); + if (unlikely(!ctx)) return false; + size -= (usize)((u8 *)ctx - (u8 *)buf); + size = size_align_down(size, sizeof(pool_ctx)); + + chunk = (pool_chunk *)(ctx + 1); + chunk->size = size - sizeof(pool_ctx); + chunk->next = NULL; + ctx->size = size; + ctx->free_list = chunk; + + alc->malloc = pool_malloc; + alc->realloc = pool_realloc; + alc->free = pool_free; + alc->ctx = (void *)ctx; + return true; +} + + + +/*============================================================================== + * Dynamic Memory Allocator + * + * This allocator allocates memory on demand and does not immediately release + * unused memory. Instead, it places the unused memory into a freelist for + * potential reuse in the future. It is only when the entire allocator is + * destroyed that all previously allocated memory is released at once. + *============================================================================*/ + +/** memory chunk header */ +typedef struct dyn_chunk { + usize size; /* chunk size, include header */ + struct dyn_chunk *next; + /* char mem[]; flexible array member */ +} dyn_chunk; + +/** allocator ctx header */ +typedef struct { + dyn_chunk free_list; /* dummy header, sorted from small to large */ + dyn_chunk used_list; /* dummy header */ +} dyn_ctx; + +/** align up the input size to chunk size */ +static_inline bool dyn_size_align(usize *size) { + usize alc_size = *size + sizeof(dyn_chunk); + alc_size = size_align_up(alc_size, YYJSON_ALC_DYN_MIN_SIZE); + if (unlikely(alc_size < *size)) return false; /* overflow */ + *size = alc_size; + return true; +} + +/** remove a chunk from list (the chunk must already be in the list) */ +static_inline void dyn_chunk_list_remove(dyn_chunk *list, dyn_chunk *chunk) { + dyn_chunk *prev = list, *cur; + for (cur = prev->next; cur; cur = cur->next) { + if (cur == chunk) { + prev->next = cur->next; + cur->next = NULL; + return; + } + prev = cur; + } +} + +/** add a chunk to list header (the chunk must not be in the list) */ +static_inline void dyn_chunk_list_add(dyn_chunk *list, dyn_chunk *chunk) { + chunk->next = list->next; + list->next = chunk; +} + +static void *dyn_malloc(void *ctx_ptr, usize size) { + /* assert(size != 0) */ + const yyjson_alc def = YYJSON_DEFAULT_ALC; + dyn_ctx *ctx = (dyn_ctx *)ctx_ptr; + dyn_chunk *chunk, *prev, *next; + if (unlikely(!dyn_size_align(&size))) return NULL; + + /* freelist is empty, create new chunk */ + if (!ctx->free_list.next) { + chunk = (dyn_chunk *)def.malloc(def.ctx, size); + if (unlikely(!chunk)) return NULL; + chunk->size = size; + chunk->next = NULL; + dyn_chunk_list_add(&ctx->used_list, chunk); + return (void *)(chunk + 1); + } + + /* find a large enough chunk, or resize the largest chunk */ + prev = &ctx->free_list; + while (true) { + chunk = prev->next; + if (chunk->size >= size) { /* enough size, reuse this chunk */ + prev->next = chunk->next; + dyn_chunk_list_add(&ctx->used_list, chunk); + return (void *)(chunk + 1); + } + if (!chunk->next) { /* resize the largest chunk */ + chunk = (dyn_chunk *)def.realloc(def.ctx, chunk, chunk->size, size); + if (unlikely(!chunk)) return NULL; + prev->next = NULL; + chunk->size = size; + dyn_chunk_list_add(&ctx->used_list, chunk); + return (void *)(chunk + 1); + } + prev = chunk; + } +} + +static void *dyn_realloc(void *ctx_ptr, void *ptr, + usize old_size, usize size) { + /* assert(ptr != NULL && size != 0 && old_size < size) */ + const yyjson_alc def = YYJSON_DEFAULT_ALC; + dyn_ctx *ctx = (dyn_ctx *)ctx_ptr; + dyn_chunk *prev, *next, *new_chunk; + dyn_chunk *chunk = (dyn_chunk *)ptr - 1; + if (unlikely(!dyn_size_align(&size))) return NULL; + if (chunk->size >= size) return ptr; + + dyn_chunk_list_remove(&ctx->used_list, chunk); + new_chunk = (dyn_chunk *)def.realloc(def.ctx, chunk, chunk->size, size); + if (likely(new_chunk)) { + new_chunk->size = size; + chunk = new_chunk; + } + dyn_chunk_list_add(&ctx->used_list, chunk); + return new_chunk ? (void *)(new_chunk + 1) : NULL; +} + +static void dyn_free(void *ctx_ptr, void *ptr) { + /* assert(ptr != NULL) */ + dyn_ctx *ctx = (dyn_ctx *)ctx_ptr; + dyn_chunk *chunk = (dyn_chunk *)ptr - 1, *prev; + + dyn_chunk_list_remove(&ctx->used_list, chunk); + for (prev = &ctx->free_list; prev; prev = prev->next) { + if (!prev->next || prev->next->size >= chunk->size) { + chunk->next = prev->next; + prev->next = chunk; + break; + } + } +} + +yyjson_alc *yyjson_alc_dyn_new(void) { + const yyjson_alc def = YYJSON_DEFAULT_ALC; + usize hdr_len = sizeof(yyjson_alc) + sizeof(dyn_ctx); + yyjson_alc *alc = (yyjson_alc *)def.malloc(def.ctx, hdr_len); + dyn_ctx *ctx = (dyn_ctx *)(void *)(alc + 1); + if (unlikely(!alc)) return NULL; + alc->malloc = dyn_malloc; + alc->realloc = dyn_realloc; + alc->free = dyn_free; + alc->ctx = alc + 1; + memset(ctx, 0, sizeof(*ctx)); + return alc; +} + +void yyjson_alc_dyn_free(yyjson_alc *alc) { + const yyjson_alc def = YYJSON_DEFAULT_ALC; + dyn_ctx *ctx = (dyn_ctx *)(void *)(alc + 1); + dyn_chunk *chunk, *next; + if (unlikely(!alc)) return; + for (chunk = ctx->free_list.next; chunk; chunk = next) { + next = chunk->next; + def.free(def.ctx, chunk); + } + for (chunk = ctx->used_list.next; chunk; chunk = next) { + next = chunk->next; + def.free(def.ctx, chunk); + } + def.free(def.ctx, alc); +} + + + +/*============================================================================== + * JSON document and value + *============================================================================*/ + +static_inline void unsafe_yyjson_str_pool_release(yyjson_str_pool *pool, + yyjson_alc *alc) { + yyjson_str_chunk *chunk = pool->chunks, *next; + while (chunk) { + next = chunk->next; + alc->free(alc->ctx, chunk); + chunk = next; + } +} + +static_inline void unsafe_yyjson_val_pool_release(yyjson_val_pool *pool, + yyjson_alc *alc) { + yyjson_val_chunk *chunk = pool->chunks, *next; + while (chunk) { + next = chunk->next; + alc->free(alc->ctx, chunk); + chunk = next; + } +} + +bool unsafe_yyjson_str_pool_grow(yyjson_str_pool *pool, + const yyjson_alc *alc, usize len) { + yyjson_str_chunk *chunk; + usize size, max_len; + + /* create a new chunk */ + max_len = USIZE_MAX - sizeof(yyjson_str_chunk); + if (unlikely(len > max_len)) return false; + size = len + sizeof(yyjson_str_chunk); + size = yyjson_max(pool->chunk_size, size); + chunk = (yyjson_str_chunk *)alc->malloc(alc->ctx, size); + if (unlikely(!chunk)) return false; + + /* insert the new chunk as the head of the linked list */ + chunk->next = pool->chunks; + chunk->chunk_size = size; + pool->chunks = chunk; + pool->cur = (char *)chunk + sizeof(yyjson_str_chunk); + pool->end = (char *)chunk + size; + + /* the next chunk is twice the size of the current one */ + size = yyjson_min(pool->chunk_size * 2, pool->chunk_size_max); + if (size < pool->chunk_size) size = pool->chunk_size_max; /* overflow */ + pool->chunk_size = size; + return true; +} + +bool unsafe_yyjson_val_pool_grow(yyjson_val_pool *pool, + const yyjson_alc *alc, usize count) { + yyjson_val_chunk *chunk; + usize size, max_count; + + /* create a new chunk */ + max_count = USIZE_MAX / sizeof(yyjson_mut_val) - 1; + if (unlikely(count > max_count)) return false; + size = (count + 1) * sizeof(yyjson_mut_val); + size = yyjson_max(pool->chunk_size, size); + chunk = (yyjson_val_chunk *)alc->malloc(alc->ctx, size); + if (unlikely(!chunk)) return false; + + /* insert the new chunk as the head of the linked list */ + chunk->next = pool->chunks; + chunk->chunk_size = size; + pool->chunks = chunk; + pool->cur = (yyjson_mut_val *)(void *)((u8 *)chunk) + 1; + pool->end = (yyjson_mut_val *)(void *)((u8 *)chunk + size); + + /* the next chunk is twice the size of the current one */ + size = yyjson_min(pool->chunk_size * 2, pool->chunk_size_max); + if (size < pool->chunk_size) size = pool->chunk_size_max; /* overflow */ + pool->chunk_size = size; + return true; +} + +bool yyjson_mut_doc_set_str_pool_size(yyjson_mut_doc *doc, size_t len) { + usize max_size = USIZE_MAX - sizeof(yyjson_str_chunk); + if (!doc || !len || len > max_size) return false; + doc->str_pool.chunk_size = len + sizeof(yyjson_str_chunk); + return true; +} + +bool yyjson_mut_doc_set_val_pool_size(yyjson_mut_doc *doc, size_t count) { + usize max_count = USIZE_MAX / sizeof(yyjson_mut_val) - 1; + if (!doc || !count || count > max_count) return false; + doc->val_pool.chunk_size = (count + 1) * sizeof(yyjson_mut_val); + return true; +} + +void yyjson_mut_doc_free(yyjson_mut_doc *doc) { + if (doc) { + yyjson_alc alc = doc->alc; + unsafe_yyjson_str_pool_release(&doc->str_pool, &alc); + unsafe_yyjson_val_pool_release(&doc->val_pool, &alc); + alc.free(alc.ctx, doc); + } +} + +yyjson_mut_doc *yyjson_mut_doc_new(const yyjson_alc *alc) { + yyjson_mut_doc *doc; + if (!alc) alc = &YYJSON_DEFAULT_ALC; + doc = (yyjson_mut_doc *)alc->malloc(alc->ctx, sizeof(yyjson_mut_doc)); + if (!doc) return NULL; + memset(doc, 0, sizeof(yyjson_mut_doc)); + + doc->alc = *alc; + doc->str_pool.chunk_size = YYJSON_MUT_DOC_STR_POOL_INIT_SIZE; + doc->str_pool.chunk_size_max = YYJSON_MUT_DOC_STR_POOL_MAX_SIZE; + doc->val_pool.chunk_size = YYJSON_MUT_DOC_VAL_POOL_INIT_SIZE; + doc->val_pool.chunk_size_max = YYJSON_MUT_DOC_VAL_POOL_MAX_SIZE; + return doc; +} + +yyjson_mut_doc *yyjson_doc_mut_copy(yyjson_doc *doc, const yyjson_alc *alc) { + yyjson_mut_doc *m_doc; + yyjson_mut_val *m_val; + + if (!doc || !doc->root) return NULL; + m_doc = yyjson_mut_doc_new(alc); + if (!m_doc) return NULL; + m_val = yyjson_val_mut_copy(m_doc, doc->root); + if (!m_val) { + yyjson_mut_doc_free(m_doc); + return NULL; + } + yyjson_mut_doc_set_root(m_doc, m_val); + return m_doc; +} + +yyjson_mut_doc *yyjson_mut_doc_mut_copy(yyjson_mut_doc *doc, + const yyjson_alc *alc) { + yyjson_mut_doc *m_doc; + yyjson_mut_val *m_val; + + if (!doc) return NULL; + if (!doc->root) return yyjson_mut_doc_new(alc); + + m_doc = yyjson_mut_doc_new(alc); + if (!m_doc) return NULL; + m_val = yyjson_mut_val_mut_copy(m_doc, doc->root); + if (!m_val) { + yyjson_mut_doc_free(m_doc); + return NULL; + } + yyjson_mut_doc_set_root(m_doc, m_val); + return m_doc; +} + +yyjson_mut_val *yyjson_val_mut_copy(yyjson_mut_doc *m_doc, + yyjson_val *i_vals) { + /* + The immutable object or array stores all sub-values in a contiguous memory, + We copy them to another contiguous memory as mutable values, + then reconnect the mutable values with the original relationship. + */ + usize i_vals_len; + yyjson_mut_val *m_vals, *m_val; + yyjson_val *i_val, *i_end; + + if (!m_doc || !i_vals) return NULL; + i_end = unsafe_yyjson_get_next(i_vals); + i_vals_len = (usize)(unsafe_yyjson_get_next(i_vals) - i_vals); + m_vals = unsafe_yyjson_mut_val(m_doc, i_vals_len); + if (!m_vals) return NULL; + i_val = i_vals; + m_val = m_vals; + + for (; i_val < i_end; i_val++, m_val++) { + yyjson_type type = unsafe_yyjson_get_type(i_val); + m_val->tag = i_val->tag; + m_val->uni.u64 = i_val->uni.u64; + if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) { + const char *str = i_val->uni.str; + usize str_len = unsafe_yyjson_get_len(i_val); + m_val->uni.str = unsafe_yyjson_mut_strncpy(m_doc, str, str_len); + if (!m_val->uni.str) return NULL; + } else if (type == YYJSON_TYPE_ARR) { + usize len = unsafe_yyjson_get_len(i_val); + if (len > 0) { + yyjson_val *ii_val = i_val + 1, *ii_next; + yyjson_mut_val *mm_val = m_val + 1, *mm_ctn = m_val, *mm_next; + while (len-- > 1) { + ii_next = unsafe_yyjson_get_next(ii_val); + mm_next = mm_val + (ii_next - ii_val); + mm_val->next = mm_next; + ii_val = ii_next; + mm_val = mm_next; + } + mm_val->next = mm_ctn + 1; + mm_ctn->uni.ptr = mm_val; + } + } else if (type == YYJSON_TYPE_OBJ) { + usize len = unsafe_yyjson_get_len(i_val); + if (len > 0) { + yyjson_val *ii_key = i_val + 1, *ii_nextkey; + yyjson_mut_val *mm_key = m_val + 1, *mm_ctn = m_val; + yyjson_mut_val *mm_nextkey; + while (len-- > 1) { + ii_nextkey = unsafe_yyjson_get_next(ii_key + 1); + mm_nextkey = mm_key + (ii_nextkey - ii_key); + mm_key->next = mm_key + 1; + mm_key->next->next = mm_nextkey; + ii_key = ii_nextkey; + mm_key = mm_nextkey; + } + mm_key->next = mm_key + 1; + mm_key->next->next = mm_ctn + 1; + mm_ctn->uni.ptr = mm_key; + } + } + } + + return m_vals; +} + +static yyjson_mut_val *unsafe_yyjson_mut_val_mut_copy(yyjson_mut_doc *m_doc, + yyjson_mut_val *m_vals) { + /* + The mutable object or array stores all sub-values in a circular linked + list, so we can traverse them in the same loop. The traversal starts from + the last item, continues with the first item in a list, and ends with the + second to last item, which needs to be linked to the last item to close the + circle. + */ + yyjson_mut_val *m_val = unsafe_yyjson_mut_val(m_doc, 1); + if (unlikely(!m_val)) return NULL; + m_val->tag = m_vals->tag; + + switch (unsafe_yyjson_get_type(m_vals)) { + case YYJSON_TYPE_OBJ: + case YYJSON_TYPE_ARR: + if (unsafe_yyjson_get_len(m_vals) > 0) { + yyjson_mut_val *last = (yyjson_mut_val *)m_vals->uni.ptr; + yyjson_mut_val *next = last->next, *prev; + prev = unsafe_yyjson_mut_val_mut_copy(m_doc, last); + if (!prev) return NULL; + m_val->uni.ptr = (void *)prev; + while (next != last) { + prev->next = unsafe_yyjson_mut_val_mut_copy(m_doc, next); + if (!prev->next) return NULL; + prev = prev->next; + next = next->next; + } + prev->next = (yyjson_mut_val *)m_val->uni.ptr; + } + break; + + case YYJSON_TYPE_RAW: + case YYJSON_TYPE_STR: { + const char *str = m_vals->uni.str; + usize str_len = unsafe_yyjson_get_len(m_vals); + m_val->uni.str = unsafe_yyjson_mut_strncpy(m_doc, str, str_len); + if (!m_val->uni.str) return NULL; + break; + } + + default: + m_val->uni = m_vals->uni; + break; + } + + return m_val; +} + +yyjson_mut_val *yyjson_mut_val_mut_copy(yyjson_mut_doc *doc, + yyjson_mut_val *val) { + if (doc && val) return unsafe_yyjson_mut_val_mut_copy(doc, val); + return NULL; +} + +/* Count the number of values and the total length of the strings. */ +static void yyjson_mut_stat(yyjson_mut_val *val, + usize *val_sum, usize *str_sum) { + yyjson_type type = unsafe_yyjson_get_type(val); + *val_sum += 1; + if (type == YYJSON_TYPE_ARR || type == YYJSON_TYPE_OBJ) { + yyjson_mut_val *child = (yyjson_mut_val *)val->uni.ptr; + usize len = unsafe_yyjson_get_len(val), i; + len <<= (u8)(type == YYJSON_TYPE_OBJ); + *val_sum += len; + for (i = 0; i < len; i++) { + yyjson_type stype = unsafe_yyjson_get_type(child); + if (stype == YYJSON_TYPE_STR || stype == YYJSON_TYPE_RAW) { + *str_sum += unsafe_yyjson_get_len(child) + 1; + } else if (stype == YYJSON_TYPE_ARR || stype == YYJSON_TYPE_OBJ) { + yyjson_mut_stat(child, val_sum, str_sum); + *val_sum -= 1; + } + child = child->next; + } + } else if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) { + *str_sum += unsafe_yyjson_get_len(val) + 1; + } +} + +/* Copy mutable values to immutable value pool. */ +static usize yyjson_imut_copy(yyjson_val **val_ptr, char **buf_ptr, + yyjson_mut_val *mval) { + yyjson_val *val = *val_ptr; + yyjson_type type = unsafe_yyjson_get_type(mval); + if (type == YYJSON_TYPE_ARR || type == YYJSON_TYPE_OBJ) { + yyjson_mut_val *child = (yyjson_mut_val *)mval->uni.ptr; + usize len = unsafe_yyjson_get_len(mval), i; + usize val_sum = 1; + if (type == YYJSON_TYPE_OBJ) { + if (len) child = child->next->next; + len <<= 1; + } else { + if (len) child = child->next; + } + *val_ptr = val + 1; + for (i = 0; i < len; i++) { + val_sum += yyjson_imut_copy(val_ptr, buf_ptr, child); + child = child->next; + } + val->tag = mval->tag; + val->uni.ofs = val_sum * sizeof(yyjson_val); + return val_sum; + } else if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) { + char *buf = *buf_ptr; + usize len = unsafe_yyjson_get_len(mval); + memcpy((void *)buf, (const void *)mval->uni.str, len); + buf[len] = '\0'; + val->tag = mval->tag; + val->uni.str = buf; + *val_ptr = val + 1; + *buf_ptr = buf + len + 1; + return 1; + } else { + val->tag = mval->tag; + val->uni = mval->uni; + *val_ptr = val + 1; + return 1; + } +} + +yyjson_doc *yyjson_mut_doc_imut_copy(yyjson_mut_doc *mdoc, + const yyjson_alc *alc) { + if (!mdoc) return NULL; + return yyjson_mut_val_imut_copy(mdoc->root, alc); +} + +yyjson_doc *yyjson_mut_val_imut_copy(yyjson_mut_val *mval, + const yyjson_alc *alc) { + usize val_num = 0, str_sum = 0, hdr_size, buf_size; + yyjson_doc *doc = NULL; + yyjson_val *val_hdr = NULL; + + /* This value should be NULL here. Setting a non-null value suppresses + warning from the clang analyzer. */ + char *str_hdr = (char *)(void *)&str_sum; + if (!mval) return NULL; + if (!alc) alc = &YYJSON_DEFAULT_ALC; + + /* traverse the input value to get pool size */ + yyjson_mut_stat(mval, &val_num, &str_sum); + + /* create doc and val pool */ + hdr_size = size_align_up(sizeof(yyjson_doc), sizeof(yyjson_val)); + buf_size = hdr_size + val_num * sizeof(yyjson_val); + doc = (yyjson_doc *)alc->malloc(alc->ctx, buf_size); + if (!doc) return NULL; + memset(doc, 0, sizeof(yyjson_doc)); + val_hdr = (yyjson_val *)(void *)((char *)(void *)doc + hdr_size); + doc->root = val_hdr; + doc->alc = *alc; + + /* create str pool */ + if (str_sum > 0) { + str_hdr = (char *)alc->malloc(alc->ctx, str_sum); + doc->str_pool = str_hdr; + if (!str_hdr) { + alc->free(alc->ctx, (void *)doc); + return NULL; + } + } + + /* copy vals and strs */ + doc->val_read = yyjson_imut_copy(&val_hdr, &str_hdr, mval); + doc->dat_read = str_sum + 1; + return doc; +} + +static_inline bool unsafe_yyjson_num_equals(void *lhs, void *rhs) { + yyjson_val_uni *luni = &((yyjson_val *)lhs)->uni; + yyjson_val_uni *runi = &((yyjson_val *)rhs)->uni; + yyjson_subtype lt = unsafe_yyjson_get_subtype(lhs); + yyjson_subtype rt = unsafe_yyjson_get_subtype(rhs); + if (lt == rt) return luni->u64 == runi->u64; + if (lt == YYJSON_SUBTYPE_SINT && rt == YYJSON_SUBTYPE_UINT) { + return luni->i64 >= 0 && luni->u64 == runi->u64; + } + if (lt == YYJSON_SUBTYPE_UINT && rt == YYJSON_SUBTYPE_SINT) { + return runi->i64 >= 0 && luni->u64 == runi->u64; + } + return false; +} + +static_inline bool unsafe_yyjson_str_equals(void *lhs, void *rhs) { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + return !memcmp(unsafe_yyjson_get_str(lhs), + unsafe_yyjson_get_str(rhs), len); +} + +bool unsafe_yyjson_equals(yyjson_val *lhs, yyjson_val *rhs) { + yyjson_type type = unsafe_yyjson_get_type(lhs); + if (type != unsafe_yyjson_get_type(rhs)) return false; + + switch (type) { + case YYJSON_TYPE_OBJ: { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + if (len > 0) { + yyjson_obj_iter iter; + yyjson_obj_iter_init(rhs, &iter); + lhs = unsafe_yyjson_get_first(lhs); + while (len-- > 0) { + rhs = yyjson_obj_iter_getn(&iter, lhs->uni.str, + unsafe_yyjson_get_len(lhs)); + if (!rhs) return false; + if (!unsafe_yyjson_equals(lhs + 1, rhs)) return false; + lhs = unsafe_yyjson_get_next(lhs + 1); + } + } + /* yyjson allows duplicate keys, so the check may be inaccurate */ + return true; + } + + case YYJSON_TYPE_ARR: { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + if (len > 0) { + lhs = unsafe_yyjson_get_first(lhs); + rhs = unsafe_yyjson_get_first(rhs); + while (len-- > 0) { + if (!unsafe_yyjson_equals(lhs, rhs)) return false; + lhs = unsafe_yyjson_get_next(lhs); + rhs = unsafe_yyjson_get_next(rhs); + } + } + return true; + } + + case YYJSON_TYPE_NUM: + return unsafe_yyjson_num_equals(lhs, rhs); + + case YYJSON_TYPE_RAW: + case YYJSON_TYPE_STR: + return unsafe_yyjson_str_equals(lhs, rhs); + + case YYJSON_TYPE_NULL: + case YYJSON_TYPE_BOOL: + return lhs->tag == rhs->tag; + + default: + return false; + } +} + +bool unsafe_yyjson_mut_equals(yyjson_mut_val *lhs, yyjson_mut_val *rhs) { + yyjson_type type = unsafe_yyjson_get_type(lhs); + if (type != unsafe_yyjson_get_type(rhs)) return false; + + switch (type) { + case YYJSON_TYPE_OBJ: { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + if (len > 0) { + yyjson_mut_obj_iter iter; + yyjson_mut_obj_iter_init(rhs, &iter); + lhs = (yyjson_mut_val *)lhs->uni.ptr; + while (len-- > 0) { + rhs = yyjson_mut_obj_iter_getn(&iter, lhs->uni.str, + unsafe_yyjson_get_len(lhs)); + if (!rhs) return false; + if (!unsafe_yyjson_mut_equals(lhs->next, rhs)) return false; + lhs = lhs->next->next; + } + } + /* yyjson allows duplicate keys, so the check may be inaccurate */ + return true; + } + + case YYJSON_TYPE_ARR: { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + if (len > 0) { + lhs = (yyjson_mut_val *)lhs->uni.ptr; + rhs = (yyjson_mut_val *)rhs->uni.ptr; + while (len-- > 0) { + if (!unsafe_yyjson_mut_equals(lhs, rhs)) return false; + lhs = lhs->next; + rhs = rhs->next; + } + } + return true; + } + + case YYJSON_TYPE_NUM: + return unsafe_yyjson_num_equals(lhs, rhs); + + case YYJSON_TYPE_RAW: + case YYJSON_TYPE_STR: + return unsafe_yyjson_str_equals(lhs, rhs); + + case YYJSON_TYPE_NULL: + case YYJSON_TYPE_BOOL: + return lhs->tag == rhs->tag; + + default: + return false; + } +} + + + +#if !YYJSON_DISABLE_UTILS + +/*============================================================================== + * JSON Pointer API (RFC 6901) + *============================================================================*/ + +/** + Get a token from JSON pointer string. + @param ptr [in,out] + in: string that points to current token prefix `/` + out: string that points to next token prefix `/`, or string end + @param end [in] end of the entire JSON Pointer string + @param len [out] unescaped token length + @param esc [out] number of escaped characters in this token + @return head of the token, or NULL if syntax error + */ +static_inline const char *ptr_next_token(const char **ptr, const char *end, + usize *len, usize *esc) { + const char *hdr = *ptr + 1; + const char *cur = hdr; + /* skip unescaped characters */ + while (cur < end && *cur != '/' && *cur != '~') cur++; + if (likely(cur == end || *cur != '~')) { + /* no escaped characters, return */ + *ptr = cur; + *len = (usize)(cur - hdr); + *esc = 0; + return hdr; + } else { + /* handle escaped characters */ + usize esc_num = 0; + while (cur < end && *cur != '/') { + if (*cur++ == '~') { + if (cur == end || (*cur != '0' && *cur != '1')) { + *ptr = cur - 1; + return NULL; + } + esc_num++; + } + } + *ptr = cur; + *len = (usize)(cur - hdr) - esc_num; + *esc = esc_num; + return hdr; + } +} + +/** + Convert token string to index. + @param cur [in] token head + @param len [in] token length + @param idx [out] the index number, or USIZE_MAX if token is '-' + @return true if token is a valid array index + */ +static_inline bool ptr_token_to_idx(const char *cur, usize len, usize *idx) { + const char *end = cur + len; + usize num = 0, add; + if (unlikely(len == 0 || len > USIZE_SAFE_DIG)) return false; + if (*cur == '0') { + if (unlikely(len > 1)) return false; + *idx = 0; + return true; + } + if (*cur == '-') { + if (unlikely(len > 1)) return false; + *idx = USIZE_MAX; + return true; + } + for (; cur < end && (add = (usize)((u8)*cur - (u8)'0')) <= 9; cur++) { + num = num * 10 + add; + } + if (unlikely(num == 0 || cur < end)) return false; + *idx = num; + return true; +} + +/** + Compare JSON key with token. + @param key a string key (yyjson_val or yyjson_mut_val) + @param token a JSON pointer token + @param len unescaped token length + @param esc number of escaped characters in this token + @return true if `str` is equals to `token` + */ +static_inline bool ptr_token_eq(void *key, + const char *token, usize len, usize esc) { + yyjson_val *val = (yyjson_val *)key; + if (unsafe_yyjson_get_len(val) != len) return false; + if (likely(!esc)) { + return memcmp(val->uni.str, token, len) == 0; + } else { + const char *str = val->uni.str; + for (; len-- > 0; token++, str++) { + if (*token == '~') { + if (*str != (*++token == '0' ? '~' : '/')) return false; + } else { + if (*str != *token) return false; + } + } + return true; + } +} + +/** + Get a value from array by token. + @param arr an array, should not be NULL or non-array type + @param token a JSON pointer token + @param len unescaped token length + @param esc number of escaped characters in this token + @return value at index, or NULL if token is not index or index is out of range + */ +static_inline yyjson_val *ptr_arr_get(yyjson_val *arr, const char *token, + usize len, usize esc) { + yyjson_val *val = unsafe_yyjson_get_first(arr); + usize num = unsafe_yyjson_get_len(arr), idx = 0; + if (unlikely(num == 0)) return NULL; + if (unlikely(!ptr_token_to_idx(token, len, &idx))) return NULL; + if (unlikely(idx >= num)) return NULL; + if (unsafe_yyjson_arr_is_flat(arr)) { + return val + idx; + } else { + while (idx-- > 0) val = unsafe_yyjson_get_next(val); + return val; + } +} + +/** + Get a value from object by token. + @param obj [in] an object, should not be NULL or non-object type + @param token [in] a JSON pointer token + @param len [in] unescaped token length + @param esc [in] number of escaped characters in this token + @return value associated with the token, or NULL if no value + */ +static_inline yyjson_val *ptr_obj_get(yyjson_val *obj, const char *token, + usize len, usize esc) { + yyjson_val *key = unsafe_yyjson_get_first(obj); + usize num = unsafe_yyjson_get_len(obj); + if (unlikely(num == 0)) return NULL; + for (; num > 0; num--, key = unsafe_yyjson_get_next(key + 1)) { + if (ptr_token_eq(key, token, len, esc)) return key + 1; + } + return NULL; +} + +/** + Get a value from array by token. + @param arr [in] an array, should not be NULL or non-array type + @param token [in] a JSON pointer token + @param len [in] unescaped token length + @param esc [in] number of escaped characters in this token + @param pre [out] previous (sibling) value of the returned value + @param last [out] whether index is last + @return value at index, or NULL if token is not index or index is out of range + */ +static_inline yyjson_mut_val *ptr_mut_arr_get(yyjson_mut_val *arr, + const char *token, + usize len, usize esc, + yyjson_mut_val **pre, + bool *last) { + yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr; /* last (tail) */ + usize num = unsafe_yyjson_get_len(arr), idx; + if (last) *last = false; + if (pre) *pre = NULL; + if (unlikely(num == 0)) { + if (last && len == 1 && (*token == '0' || *token == '-')) *last = true; + return NULL; + } + if (unlikely(!ptr_token_to_idx(token, len, &idx))) return NULL; + if (last) *last = (idx == num || idx == USIZE_MAX); + if (unlikely(idx >= num)) return NULL; + while (idx-- > 0) val = val->next; + *pre = val; + return val->next; +} + +/** + Get a value from object by token. + @param obj [in] an object, should not be NULL or non-object type + @param token [in] a JSON pointer token + @param len [in] unescaped token length + @param esc [in] number of escaped characters in this token + @param pre [out] previous (sibling) key of the returned value's key + @return value associated with the token, or NULL if no value + */ +static_inline yyjson_mut_val *ptr_mut_obj_get(yyjson_mut_val *obj, + const char *token, + usize len, usize esc, + yyjson_mut_val **pre) { + yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr, *key; + usize num = unsafe_yyjson_get_len(obj); + if (pre) *pre = NULL; + if (unlikely(num == 0)) return NULL; + for (; num > 0; num--, pre_key = key) { + key = pre_key->next->next; + if (ptr_token_eq(key, token, len, esc)) { + *pre = pre_key; + return key->next; + } + } + return NULL; +} + +/** + Create a string value with JSON pointer token. + @param token [in] a JSON pointer token + @param len [in] unescaped token length + @param esc [in] number of escaped characters in this token + @param doc [in] used for memory allocation when creating value + @return new string value, or NULL if memory allocation failed + */ +static_inline yyjson_mut_val *ptr_new_key(const char *token, + usize len, usize esc, + yyjson_mut_doc *doc) { + const char *src = token; + if (likely(!esc)) { + return yyjson_mut_strncpy(doc, src, len); + } else { + const char *end = src + len + esc; + char *dst = unsafe_yyjson_mut_str_alc(doc, len + esc); + char *str = dst; + if (unlikely(!dst)) return NULL; + for (; src < end; src++, dst++) { + if (*src != '~') *dst = *src; + else *dst = (*++src == '0' ? '~' : '/'); + } + *dst = '\0'; + return yyjson_mut_strn(doc, str, len); + } +} + +/* macros for yyjson_ptr */ +#define return_err(_ret, _code, _pos, _msg) do { \ + if (err) { \ + err->code = YYJSON_PTR_ERR_##_code; \ + err->msg = _msg; \ + err->pos = (usize)(_pos); \ + } \ + return _ret; \ +} while (false) + +#define return_err_resolve(_ret, _pos) \ + return_err(_ret, RESOLVE, _pos, "JSON pointer cannot be resolved") +#define return_err_syntax(_ret, _pos) \ + return_err(_ret, SYNTAX, _pos, "invalid escaped character") +#define return_err_alloc(_ret) \ + return_err(_ret, MEMORY_ALLOCATION, 0, "failed to create value") + +yyjson_val *unsafe_yyjson_ptr_getx(yyjson_val *val, + const char *ptr, size_t ptr_len, + yyjson_ptr_err *err) { + + const char *hdr = ptr, *end = ptr + ptr_len, *token; + usize len, esc; + yyjson_type type; + + while (true) { + token = ptr_next_token(&ptr, end, &len, &esc); + if (unlikely(!token)) return_err_syntax(NULL, ptr - hdr); + type = unsafe_yyjson_get_type(val); + if (type == YYJSON_TYPE_OBJ) { + val = ptr_obj_get(val, token, len, esc); + } else if (type == YYJSON_TYPE_ARR) { + val = ptr_arr_get(val, token, len, esc); + } else { + val = NULL; + } + if (!val) return_err_resolve(NULL, token - hdr); + if (ptr == end) return val; + } +} + +yyjson_mut_val *unsafe_yyjson_mut_ptr_getx(yyjson_mut_val *val, + const char *ptr, + size_t ptr_len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + + const char *hdr = ptr, *end = ptr + ptr_len, *token; + usize len, esc; + yyjson_mut_val *ctn, *pre = NULL; + yyjson_type type; + bool idx_is_last = false; + + while (true) { + token = ptr_next_token(&ptr, end, &len, &esc); + if (unlikely(!token)) return_err_syntax(NULL, ptr - hdr); + ctn = val; + type = unsafe_yyjson_get_type(val); + if (type == YYJSON_TYPE_OBJ) { + val = ptr_mut_obj_get(val, token, len, esc, &pre); + } else if (type == YYJSON_TYPE_ARR) { + val = ptr_mut_arr_get(val, token, len, esc, &pre, &idx_is_last); + } else { + val = NULL; + } + if (ctx && (ptr == end)) { + if (type == YYJSON_TYPE_OBJ || + (type == YYJSON_TYPE_ARR && (val || idx_is_last))) { + ctx->ctn = ctn; + ctx->pre = pre; + } + } + if (!val) return_err_resolve(NULL, token - hdr); + if (ptr == end) return val; + } +} + +bool unsafe_yyjson_mut_ptr_putx(yyjson_mut_val *val, + const char *ptr, size_t ptr_len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, bool insert_new, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + + const char *hdr = ptr, *end = ptr + ptr_len, *token; + usize token_len, esc, ctn_len; + yyjson_mut_val *ctn, *key, *pre = NULL; + yyjson_mut_val *sep_ctn = NULL, *sep_key = NULL, *sep_val = NULL; + yyjson_type ctn_type; + bool idx_is_last = false; + + /* skip exist parent nodes */ + while (true) { + token = ptr_next_token(&ptr, end, &token_len, &esc); + if (unlikely(!token)) return_err_syntax(false, ptr - hdr); + ctn = val; + ctn_type = unsafe_yyjson_get_type(ctn); + if (ctn_type == YYJSON_TYPE_OBJ) { + val = ptr_mut_obj_get(ctn, token, token_len, esc, &pre); + } else if (ctn_type == YYJSON_TYPE_ARR) { + val = ptr_mut_arr_get(ctn, token, token_len, esc, &pre, + &idx_is_last); + } else return_err_resolve(false, token - hdr); + if (!val) break; + if (ptr == end) break; /* is last token */ + } + + /* create parent nodes if not exist */ + if (unlikely(ptr != end)) { /* not last token */ + if (!create_parent) return_err_resolve(false, token - hdr); + + /* add value at last index if container is array */ + if (ctn_type == YYJSON_TYPE_ARR) { + if (!idx_is_last || !insert_new) { + return_err_resolve(false, token - hdr); + } + val = yyjson_mut_obj(doc); + if (!val) return_err_alloc(false); + + /* delay attaching until all operations are completed */ + sep_ctn = ctn; + sep_key = NULL; + sep_val = val; + + /* move to next token */ + ctn = val; + val = NULL; + ctn_type = YYJSON_TYPE_OBJ; + token = ptr_next_token(&ptr, end, &token_len, &esc); + if (unlikely(!token)) return_err_resolve(false, token - hdr); + } + + /* container is object, create parent nodes */ + while (ptr != end) { /* not last token */ + key = ptr_new_key(token, token_len, esc, doc); + if (!key) return_err_alloc(false); + val = yyjson_mut_obj(doc); + if (!val) return_err_alloc(false); + + /* delay attaching until all operations are completed */ + if (!sep_ctn) { + sep_ctn = ctn; + sep_key = key; + sep_val = val; + } else { + yyjson_mut_obj_add(ctn, key, val); + } + + /* move to next token */ + ctn = val; + val = NULL; + token = ptr_next_token(&ptr, end, &token_len, &esc); + if (unlikely(!token)) return_err_syntax(false, ptr - hdr); + } + } + + /* JSON pointer is resolved, insert or replace target value */ + ctn_len = unsafe_yyjson_get_len(ctn); + if (ctn_type == YYJSON_TYPE_OBJ) { + if (ctx) ctx->ctn = ctn; + if (!val || insert_new) { + /* insert new key-value pair */ + key = ptr_new_key(token, token_len, esc, doc); + if (unlikely(!key)) return_err_alloc(false); + if (ctx) ctx->pre = ctn_len ? (yyjson_mut_val *)ctn->uni.ptr : key; + unsafe_yyjson_mut_obj_add(ctn, key, new_val, ctn_len); + } else { + /* replace exist value */ + key = pre->next->next; + if (ctx) ctx->pre = pre; + if (ctx) ctx->old = val; + yyjson_mut_obj_put(ctn, key, new_val); + } + } else { + /* array */ + if (ctx && (val || idx_is_last)) ctx->ctn = ctn; + if (insert_new) { + /* append new value */ + if (val) { + pre->next = new_val; + new_val->next = val; + if (ctx) ctx->pre = pre; + unsafe_yyjson_set_len(ctn, ctn_len + 1); + } else if (idx_is_last) { + if (ctx) ctx->pre = ctn_len ? + (yyjson_mut_val *)ctn->uni.ptr : new_val; + yyjson_mut_arr_append(ctn, new_val); + } else { + return_err_resolve(false, token - hdr); + } + } else { + /* replace exist value */ + if (!val) return_err_resolve(false, token - hdr); + if (ctn_len > 1) { + new_val->next = val->next; + pre->next = new_val; + if (ctn->uni.ptr == val) ctn->uni.ptr = new_val; + } else { + new_val->next = new_val; + ctn->uni.ptr = new_val; + pre = new_val; + } + if (ctx) ctx->pre = pre; + if (ctx) ctx->old = val; + } + } + + /* all operations are completed, attach the new components to the target */ + if (unlikely(sep_ctn)) { + if (sep_key) yyjson_mut_obj_add(sep_ctn, sep_key, sep_val); + else yyjson_mut_arr_append(sep_ctn, sep_val); + } + return true; +} + +yyjson_mut_val *unsafe_yyjson_mut_ptr_replacex( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + yyjson_mut_val *cur_val; + yyjson_ptr_ctx cur_ctx; + memset(&cur_ctx, 0, sizeof(cur_ctx)); + if (!ctx) ctx = &cur_ctx; + cur_val = unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err); + if (!cur_val) return NULL; + + if (yyjson_mut_is_obj(ctx->ctn)) { + yyjson_mut_val *key = ctx->pre->next->next; + yyjson_mut_obj_put(ctx->ctn, key, new_val); + } else { + yyjson_ptr_ctx_replace(ctx, new_val); + } + ctx->old = cur_val; + return cur_val; +} + +yyjson_mut_val *unsafe_yyjson_mut_ptr_removex(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_mut_val *cur_val; + yyjson_ptr_ctx cur_ctx; + memset(&cur_ctx, 0, sizeof(cur_ctx)); + if (!ctx) ctx = &cur_ctx; + cur_val = unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err); + if (cur_val) { + if (yyjson_mut_is_obj(ctx->ctn)) { + yyjson_mut_val *key = ctx->pre->next->next; + yyjson_mut_obj_put(ctx->ctn, key, NULL); + } else { + yyjson_ptr_ctx_remove(ctx); + } + ctx->pre = NULL; + ctx->old = cur_val; + } + return cur_val; +} + +/* macros for yyjson_ptr */ +#undef return_err +#undef return_err_resolve +#undef return_err_syntax +#undef return_err_alloc + + + +/*============================================================================== + * JSON Patch API (RFC 6902) + *============================================================================*/ + +/* JSON Patch operation */ +typedef enum patch_op { + PATCH_OP_ADD, /* path, value */ + PATCH_OP_REMOVE, /* path */ + PATCH_OP_REPLACE, /* path, value */ + PATCH_OP_MOVE, /* from, path */ + PATCH_OP_COPY, /* from, path */ + PATCH_OP_TEST, /* path, value */ + PATCH_OP_NONE /* invalid */ +} patch_op; + +static patch_op patch_op_get(yyjson_val *op) { + const char *str = op->uni.str; + switch (unsafe_yyjson_get_len(op)) { + case 3: + if (!memcmp(str, "add", 3)) return PATCH_OP_ADD; + return PATCH_OP_NONE; + case 4: + if (!memcmp(str, "move", 4)) return PATCH_OP_MOVE; + if (!memcmp(str, "copy", 4)) return PATCH_OP_COPY; + if (!memcmp(str, "test", 4)) return PATCH_OP_TEST; + return PATCH_OP_NONE; + case 6: + if (!memcmp(str, "remove", 6)) return PATCH_OP_REMOVE; + return PATCH_OP_NONE; + case 7: + if (!memcmp(str, "replace", 7)) return PATCH_OP_REPLACE; + return PATCH_OP_NONE; + default: + return PATCH_OP_NONE; + } +} + +/* macros for yyjson_patch */ +#define return_err(_code, _msg) do { \ + if (err->ptr.code == YYJSON_PTR_ERR_MEMORY_ALLOCATION) { \ + err->code = YYJSON_PATCH_ERROR_MEMORY_ALLOCATION; \ + err->msg = _msg; \ + memset(&err->ptr, 0, sizeof(yyjson_ptr_err)); \ + } else { \ + err->code = YYJSON_PATCH_ERROR_##_code; \ + err->msg = _msg; \ + err->idx = iter.idx ? iter.idx - 1 : 0; \ + } \ + return NULL; \ +} while (false) + +#define return_err_copy() \ + return_err(MEMORY_ALLOCATION, "failed to copy value") +#define return_err_key(_key) \ + return_err(MISSING_KEY, "missing key " _key) +#define return_err_val(_key) \ + return_err(INVALID_MEMBER, "invalid member " _key) + +#define ptr_get(_ptr) yyjson_mut_ptr_getx( \ + root, _ptr->uni.str, _ptr##_len, NULL, &err->ptr) +#define ptr_add(_ptr, _val) yyjson_mut_ptr_addx( \ + root, _ptr->uni.str, _ptr##_len, _val, doc, false, NULL, &err->ptr) +#define ptr_remove(_ptr) yyjson_mut_ptr_removex( \ + root, _ptr->uni.str, _ptr##_len, NULL, &err->ptr) +#define ptr_replace(_ptr, _val)yyjson_mut_ptr_replacex( \ + root, _ptr->uni.str, _ptr##_len, _val, NULL, &err->ptr) + +yyjson_mut_val *yyjson_patch(yyjson_mut_doc *doc, + yyjson_val *orig, + yyjson_val *patch, + yyjson_patch_err *err) { + + yyjson_mut_val *root; + yyjson_val *obj; + yyjson_arr_iter iter; + yyjson_patch_err err_tmp; + if (!err) err = &err_tmp; + memset(err, 0, sizeof(*err)); + memset(&iter, 0, sizeof(iter)); + + if (unlikely(!doc || !orig || !patch)) { + return_err(INVALID_PARAMETER, "input parameter is NULL"); + } + if (unlikely(!yyjson_is_arr(patch))) { + return_err(INVALID_PARAMETER, "input patch is not array"); + } + root = yyjson_val_mut_copy(doc, orig); + if (unlikely(!root)) return_err_copy(); + + /* iterate through the patch array */ + yyjson_arr_iter_init(patch, &iter); + while ((obj = yyjson_arr_iter_next(&iter))) { + patch_op op_enum; + yyjson_val *op, *path, *from = NULL, *value; + yyjson_mut_val *val = NULL, *test; + usize path_len, from_len = 0; + if (unlikely(!unsafe_yyjson_is_obj(obj))) { + return_err(INVALID_OPERATION, "JSON patch operation is not object"); + } + + /* get required member: op */ + op = yyjson_obj_get(obj, "op"); + if (unlikely(!op)) return_err_key("`op`"); + if (unlikely(!yyjson_is_str(op))) return_err_val("`op`"); + op_enum = patch_op_get(op); + + /* get required member: path */ + path = yyjson_obj_get(obj, "path"); + if (unlikely(!path)) return_err_key("`path`"); + if (unlikely(!yyjson_is_str(path))) return_err_val("`path`"); + path_len = unsafe_yyjson_get_len(path); + + /* get required member: value, from */ + switch ((int)op_enum) { + case PATCH_OP_ADD: case PATCH_OP_REPLACE: case PATCH_OP_TEST: + value = yyjson_obj_get(obj, "value"); + if (unlikely(!value)) return_err_key("`value`"); + val = yyjson_val_mut_copy(doc, value); + if (unlikely(!val)) return_err_copy(); + break; + case PATCH_OP_MOVE: case PATCH_OP_COPY: + from = yyjson_obj_get(obj, "from"); + if (unlikely(!from)) return_err_key("`from`"); + if (unlikely(!yyjson_is_str(from))) return_err_val("`from`"); + from_len = unsafe_yyjson_get_len(from); + break; + default: + break; + } + + /* perform an operation */ + switch ((int)op_enum) { + case PATCH_OP_ADD: /* add(path, val) */ + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_REMOVE: /* remove(path) */ + if (unlikely(!ptr_remove(path))) { + return_err(POINTER, "failed to remove `path`"); + } + break; + case PATCH_OP_REPLACE: /* replace(path, val) */ + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_replace(path, val))) { + return_err(POINTER, "failed to replace `path`"); + } + break; + case PATCH_OP_MOVE: /* val = remove(from), add(path, val) */ + if (unlikely(from_len == 0 && path_len == 0)) break; + val = ptr_remove(from); + if (unlikely(!val)) { + return_err(POINTER, "failed to remove `from`"); + } + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_COPY: /* val = get(from).copy, add(path, val) */ + val = ptr_get(from); + if (unlikely(!val)) { + return_err(POINTER, "failed to get `from`"); + } + if (unlikely(path_len == 0)) { root = val; break; } + val = yyjson_mut_val_mut_copy(doc, val); + if (unlikely(!val)) return_err_copy(); + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_TEST: /* test = get(path), test.eq(val) */ + test = ptr_get(path); + if (unlikely(!test)) { + return_err(POINTER, "failed to get `path`"); + } + if (unlikely(!yyjson_mut_equals(val, test))) { + return_err(EQUAL, "failed to test equal"); + } + break; + default: + return_err(INVALID_MEMBER, "unsupported `op`"); + } + } + return root; +} + +yyjson_mut_val *yyjson_mut_patch(yyjson_mut_doc *doc, + yyjson_mut_val *orig, + yyjson_mut_val *patch, + yyjson_patch_err *err) { + yyjson_mut_val *root, *obj; + yyjson_mut_arr_iter iter; + yyjson_patch_err err_tmp; + if (!err) err = &err_tmp; + memset(err, 0, sizeof(*err)); + memset(&iter, 0, sizeof(iter)); + + if (unlikely(!doc || !orig || !patch)) { + return_err(INVALID_PARAMETER, "input parameter is NULL"); + } + if (unlikely(!yyjson_mut_is_arr(patch))) { + return_err(INVALID_PARAMETER, "input patch is not array"); + } + root = yyjson_mut_val_mut_copy(doc, orig); + if (unlikely(!root)) return_err_copy(); + + /* iterate through the patch array */ + yyjson_mut_arr_iter_init(patch, &iter); + while ((obj = yyjson_mut_arr_iter_next(&iter))) { + patch_op op_enum; + yyjson_mut_val *op, *path, *from = NULL, *value; + yyjson_mut_val *val = NULL, *test; + usize path_len, from_len = 0; + if (!unsafe_yyjson_is_obj(obj)) { + return_err(INVALID_OPERATION, "JSON patch operation is not object"); + } + + /* get required member: op */ + op = yyjson_mut_obj_get(obj, "op"); + if (unlikely(!op)) return_err_key("`op`"); + if (unlikely(!yyjson_mut_is_str(op))) return_err_val("`op`"); + op_enum = patch_op_get((yyjson_val *)(void *)op); + + /* get required member: path */ + path = yyjson_mut_obj_get(obj, "path"); + if (unlikely(!path)) return_err_key("`path`"); + if (unlikely(!yyjson_mut_is_str(path))) return_err_val("`path`"); + path_len = unsafe_yyjson_get_len(path); + + /* get required member: value, from */ + switch ((int)op_enum) { + case PATCH_OP_ADD: case PATCH_OP_REPLACE: case PATCH_OP_TEST: + value = yyjson_mut_obj_get(obj, "value"); + if (unlikely(!value)) return_err_key("`value`"); + val = yyjson_mut_val_mut_copy(doc, value); + if (unlikely(!val)) return_err_copy(); + break; + case PATCH_OP_MOVE: case PATCH_OP_COPY: + from = yyjson_mut_obj_get(obj, "from"); + if (unlikely(!from)) return_err_key("`from`"); + if (unlikely(!yyjson_mut_is_str(from))) { + return_err_val("`from`"); + } + from_len = unsafe_yyjson_get_len(from); + break; + default: + break; + } + + /* perform an operation */ + switch ((int)op_enum) { + case PATCH_OP_ADD: /* add(path, val) */ + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_REMOVE: /* remove(path) */ + if (unlikely(!ptr_remove(path))) { + return_err(POINTER, "failed to remove `path`"); + } + break; + case PATCH_OP_REPLACE: /* replace(path, val) */ + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_replace(path, val))) { + return_err(POINTER, "failed to replace `path`"); + } + break; + case PATCH_OP_MOVE: /* val = remove(from), add(path, val) */ + if (unlikely(from_len == 0 && path_len == 0)) break; + val = ptr_remove(from); + if (unlikely(!val)) { + return_err(POINTER, "failed to remove `from`"); + } + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_COPY: /* val = get(from).copy, add(path, val) */ + val = ptr_get(from); + if (unlikely(!val)) { + return_err(POINTER, "failed to get `from`"); + } + if (unlikely(path_len == 0)) { root = val; break; } + val = yyjson_mut_val_mut_copy(doc, val); + if (unlikely(!val)) return_err_copy(); + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_TEST: /* test = get(path), test.eq(val) */ + test = ptr_get(path); + if (unlikely(!test)) { + return_err(POINTER, "failed to get `path`"); + } + if (unlikely(!yyjson_mut_equals(val, test))) { + return_err(EQUAL, "failed to test equal"); + } + break; + default: + return_err(INVALID_MEMBER, "unsupported `op`"); + } + } + return root; +} + +/* macros for yyjson_patch */ +#undef return_err +#undef return_err_copy +#undef return_err_key +#undef return_err_val +#undef ptr_get +#undef ptr_add +#undef ptr_remove +#undef ptr_replace + + + +/*============================================================================== + * JSON Merge-Patch API (RFC 7386) + *============================================================================*/ + +yyjson_mut_val *yyjson_merge_patch(yyjson_mut_doc *doc, + yyjson_val *orig, + yyjson_val *patch) { + usize idx, max; + yyjson_val *key, *orig_val, *patch_val, local_orig; + yyjson_mut_val *builder, *mut_key, *mut_val, *merged_val; + + if (unlikely(!yyjson_is_obj(patch))) { + return yyjson_val_mut_copy(doc, patch); + } + + builder = yyjson_mut_obj(doc); + if (unlikely(!builder)) return NULL; + + memset(&local_orig, 0, sizeof(local_orig)); + if (!yyjson_is_obj(orig)) { + orig = &local_orig; + orig->tag = builder->tag; + orig->uni = builder->uni; + } + + /* If orig is contributing, copy any items not modified by the patch */ + if (orig != &local_orig) { + yyjson_obj_foreach(orig, idx, max, key, orig_val) { + patch_val = yyjson_obj_getn(patch, + unsafe_yyjson_get_str(key), + unsafe_yyjson_get_len(key)); + if (!patch_val) { + mut_key = yyjson_val_mut_copy(doc, key); + mut_val = yyjson_val_mut_copy(doc, orig_val); + if (!yyjson_mut_obj_add(builder, mut_key, mut_val)) return NULL; + } + } + } + + /* Merge items modified by the patch. */ + yyjson_obj_foreach(patch, idx, max, key, patch_val) { + /* null indicates the field is removed. */ + if (unsafe_yyjson_is_null(patch_val)) { + continue; + } + mut_key = yyjson_val_mut_copy(doc, key); + orig_val = yyjson_obj_getn(orig, + unsafe_yyjson_get_str(key), + unsafe_yyjson_get_len(key)); + merged_val = yyjson_merge_patch(doc, orig_val, patch_val); + if (!yyjson_mut_obj_add(builder, mut_key, merged_val)) return NULL; + } + + return builder; +} + +yyjson_mut_val *yyjson_mut_merge_patch(yyjson_mut_doc *doc, + yyjson_mut_val *orig, + yyjson_mut_val *patch) { + usize idx, max; + yyjson_mut_val *key, *orig_val, *patch_val, local_orig; + yyjson_mut_val *builder, *mut_key, *mut_val, *merged_val; + + if (unlikely(!yyjson_mut_is_obj(patch))) { + return yyjson_mut_val_mut_copy(doc, patch); + } + + builder = yyjson_mut_obj(doc); + if (unlikely(!builder)) return NULL; + + memset(&local_orig, 0, sizeof(local_orig)); + if (!yyjson_mut_is_obj(orig)) { + orig = &local_orig; + orig->tag = builder->tag; + orig->uni = builder->uni; + } + + /* If orig is contributing, copy any items not modified by the patch */ + if (orig != &local_orig) { + yyjson_mut_obj_foreach(orig, idx, max, key, orig_val) { + patch_val = yyjson_mut_obj_getn(patch, + unsafe_yyjson_get_str(key), + unsafe_yyjson_get_len(key)); + if (!patch_val) { + mut_key = yyjson_mut_val_mut_copy(doc, key); + mut_val = yyjson_mut_val_mut_copy(doc, orig_val); + if (!yyjson_mut_obj_add(builder, mut_key, mut_val)) return NULL; + } + } + } + + /* Merge items modified by the patch. */ + yyjson_mut_obj_foreach(patch, idx, max, key, patch_val) { + /* null indicates the field is removed. */ + if (unsafe_yyjson_is_null(patch_val)) { + continue; + } + mut_key = yyjson_mut_val_mut_copy(doc, key); + orig_val = yyjson_mut_obj_getn(orig, + unsafe_yyjson_get_str(key), + unsafe_yyjson_get_len(key)); + merged_val = yyjson_mut_merge_patch(doc, orig_val, patch_val); + if (!yyjson_mut_obj_add(builder, mut_key, merged_val)) return NULL; + } + + return builder; +} + +#endif /* YYJSON_DISABLE_UTILS */ + + + +/*============================================================================== + * Power10 Lookup Table + * These data are used by the floating-point number reader and writer. + *============================================================================*/ + +#if (!YYJSON_DISABLE_READER || !YYJSON_DISABLE_WRITER) && \ + (!YYJSON_DISABLE_FAST_FP_CONV) + +/** Minimum decimal exponent in pow10_sig_table. */ +#define POW10_SIG_TABLE_MIN_EXP -343 + +/** Maximum decimal exponent in pow10_sig_table. */ +#define POW10_SIG_TABLE_MAX_EXP 324 + +/** Minimum exact decimal exponent in pow10_sig_table */ +#define POW10_SIG_TABLE_MIN_EXACT_EXP 0 + +/** Maximum exact decimal exponent in pow10_sig_table */ +#define POW10_SIG_TABLE_MAX_EXACT_EXP 55 + +/** Normalized significant 128 bits of pow10, no rounded up (size: 10.4KB). + This lookup table is used by both the double number reader and writer. + (generate with misc/make_tables.c) */ +static const u64 pow10_sig_table[] = { + U64(0xBF29DCAB, 0xA82FDEAE), U64(0x7432EE87, 0x3880FC33), /* ~= 10^-343 */ + U64(0xEEF453D6, 0x923BD65A), U64(0x113FAA29, 0x06A13B3F), /* ~= 10^-342 */ + U64(0x9558B466, 0x1B6565F8), U64(0x4AC7CA59, 0xA424C507), /* ~= 10^-341 */ + U64(0xBAAEE17F, 0xA23EBF76), U64(0x5D79BCF0, 0x0D2DF649), /* ~= 10^-340 */ + U64(0xE95A99DF, 0x8ACE6F53), U64(0xF4D82C2C, 0x107973DC), /* ~= 10^-339 */ + U64(0x91D8A02B, 0xB6C10594), U64(0x79071B9B, 0x8A4BE869), /* ~= 10^-338 */ + U64(0xB64EC836, 0xA47146F9), U64(0x9748E282, 0x6CDEE284), /* ~= 10^-337 */ + U64(0xE3E27A44, 0x4D8D98B7), U64(0xFD1B1B23, 0x08169B25), /* ~= 10^-336 */ + U64(0x8E6D8C6A, 0xB0787F72), U64(0xFE30F0F5, 0xE50E20F7), /* ~= 10^-335 */ + U64(0xB208EF85, 0x5C969F4F), U64(0xBDBD2D33, 0x5E51A935), /* ~= 10^-334 */ + U64(0xDE8B2B66, 0xB3BC4723), U64(0xAD2C7880, 0x35E61382), /* ~= 10^-333 */ + U64(0x8B16FB20, 0x3055AC76), U64(0x4C3BCB50, 0x21AFCC31), /* ~= 10^-332 */ + U64(0xADDCB9E8, 0x3C6B1793), U64(0xDF4ABE24, 0x2A1BBF3D), /* ~= 10^-331 */ + U64(0xD953E862, 0x4B85DD78), U64(0xD71D6DAD, 0x34A2AF0D), /* ~= 10^-330 */ + U64(0x87D4713D, 0x6F33AA6B), U64(0x8672648C, 0x40E5AD68), /* ~= 10^-329 */ + U64(0xA9C98D8C, 0xCB009506), U64(0x680EFDAF, 0x511F18C2), /* ~= 10^-328 */ + U64(0xD43BF0EF, 0xFDC0BA48), U64(0x0212BD1B, 0x2566DEF2), /* ~= 10^-327 */ + U64(0x84A57695, 0xFE98746D), U64(0x014BB630, 0xF7604B57), /* ~= 10^-326 */ + U64(0xA5CED43B, 0x7E3E9188), U64(0x419EA3BD, 0x35385E2D), /* ~= 10^-325 */ + U64(0xCF42894A, 0x5DCE35EA), U64(0x52064CAC, 0x828675B9), /* ~= 10^-324 */ + U64(0x818995CE, 0x7AA0E1B2), U64(0x7343EFEB, 0xD1940993), /* ~= 10^-323 */ + U64(0xA1EBFB42, 0x19491A1F), U64(0x1014EBE6, 0xC5F90BF8), /* ~= 10^-322 */ + U64(0xCA66FA12, 0x9F9B60A6), U64(0xD41A26E0, 0x77774EF6), /* ~= 10^-321 */ + U64(0xFD00B897, 0x478238D0), U64(0x8920B098, 0x955522B4), /* ~= 10^-320 */ + U64(0x9E20735E, 0x8CB16382), U64(0x55B46E5F, 0x5D5535B0), /* ~= 10^-319 */ + U64(0xC5A89036, 0x2FDDBC62), U64(0xEB2189F7, 0x34AA831D), /* ~= 10^-318 */ + U64(0xF712B443, 0xBBD52B7B), U64(0xA5E9EC75, 0x01D523E4), /* ~= 10^-317 */ + U64(0x9A6BB0AA, 0x55653B2D), U64(0x47B233C9, 0x2125366E), /* ~= 10^-316 */ + U64(0xC1069CD4, 0xEABE89F8), U64(0x999EC0BB, 0x696E840A), /* ~= 10^-315 */ + U64(0xF148440A, 0x256E2C76), U64(0xC00670EA, 0x43CA250D), /* ~= 10^-314 */ + U64(0x96CD2A86, 0x5764DBCA), U64(0x38040692, 0x6A5E5728), /* ~= 10^-313 */ + U64(0xBC807527, 0xED3E12BC), U64(0xC6050837, 0x04F5ECF2), /* ~= 10^-312 */ + U64(0xEBA09271, 0xE88D976B), U64(0xF7864A44, 0xC633682E), /* ~= 10^-311 */ + U64(0x93445B87, 0x31587EA3), U64(0x7AB3EE6A, 0xFBE0211D), /* ~= 10^-310 */ + U64(0xB8157268, 0xFDAE9E4C), U64(0x5960EA05, 0xBAD82964), /* ~= 10^-309 */ + U64(0xE61ACF03, 0x3D1A45DF), U64(0x6FB92487, 0x298E33BD), /* ~= 10^-308 */ + U64(0x8FD0C162, 0x06306BAB), U64(0xA5D3B6D4, 0x79F8E056), /* ~= 10^-307 */ + U64(0xB3C4F1BA, 0x87BC8696), U64(0x8F48A489, 0x9877186C), /* ~= 10^-306 */ + U64(0xE0B62E29, 0x29ABA83C), U64(0x331ACDAB, 0xFE94DE87), /* ~= 10^-305 */ + U64(0x8C71DCD9, 0xBA0B4925), U64(0x9FF0C08B, 0x7F1D0B14), /* ~= 10^-304 */ + U64(0xAF8E5410, 0x288E1B6F), U64(0x07ECF0AE, 0x5EE44DD9), /* ~= 10^-303 */ + U64(0xDB71E914, 0x32B1A24A), U64(0xC9E82CD9, 0xF69D6150), /* ~= 10^-302 */ + U64(0x892731AC, 0x9FAF056E), U64(0xBE311C08, 0x3A225CD2), /* ~= 10^-301 */ + U64(0xAB70FE17, 0xC79AC6CA), U64(0x6DBD630A, 0x48AAF406), /* ~= 10^-300 */ + U64(0xD64D3D9D, 0xB981787D), U64(0x092CBBCC, 0xDAD5B108), /* ~= 10^-299 */ + U64(0x85F04682, 0x93F0EB4E), U64(0x25BBF560, 0x08C58EA5), /* ~= 10^-298 */ + U64(0xA76C5823, 0x38ED2621), U64(0xAF2AF2B8, 0x0AF6F24E), /* ~= 10^-297 */ + U64(0xD1476E2C, 0x07286FAA), U64(0x1AF5AF66, 0x0DB4AEE1), /* ~= 10^-296 */ + U64(0x82CCA4DB, 0x847945CA), U64(0x50D98D9F, 0xC890ED4D), /* ~= 10^-295 */ + U64(0xA37FCE12, 0x6597973C), U64(0xE50FF107, 0xBAB528A0), /* ~= 10^-294 */ + U64(0xCC5FC196, 0xFEFD7D0C), U64(0x1E53ED49, 0xA96272C8), /* ~= 10^-293 */ + U64(0xFF77B1FC, 0xBEBCDC4F), U64(0x25E8E89C, 0x13BB0F7A), /* ~= 10^-292 */ + U64(0x9FAACF3D, 0xF73609B1), U64(0x77B19161, 0x8C54E9AC), /* ~= 10^-291 */ + U64(0xC795830D, 0x75038C1D), U64(0xD59DF5B9, 0xEF6A2417), /* ~= 10^-290 */ + U64(0xF97AE3D0, 0xD2446F25), U64(0x4B057328, 0x6B44AD1D), /* ~= 10^-289 */ + U64(0x9BECCE62, 0x836AC577), U64(0x4EE367F9, 0x430AEC32), /* ~= 10^-288 */ + U64(0xC2E801FB, 0x244576D5), U64(0x229C41F7, 0x93CDA73F), /* ~= 10^-287 */ + U64(0xF3A20279, 0xED56D48A), U64(0x6B435275, 0x78C1110F), /* ~= 10^-286 */ + U64(0x9845418C, 0x345644D6), U64(0x830A1389, 0x6B78AAA9), /* ~= 10^-285 */ + U64(0xBE5691EF, 0x416BD60C), U64(0x23CC986B, 0xC656D553), /* ~= 10^-284 */ + U64(0xEDEC366B, 0x11C6CB8F), U64(0x2CBFBE86, 0xB7EC8AA8), /* ~= 10^-283 */ + U64(0x94B3A202, 0xEB1C3F39), U64(0x7BF7D714, 0x32F3D6A9), /* ~= 10^-282 */ + U64(0xB9E08A83, 0xA5E34F07), U64(0xDAF5CCD9, 0x3FB0CC53), /* ~= 10^-281 */ + U64(0xE858AD24, 0x8F5C22C9), U64(0xD1B3400F, 0x8F9CFF68), /* ~= 10^-280 */ + U64(0x91376C36, 0xD99995BE), U64(0x23100809, 0xB9C21FA1), /* ~= 10^-279 */ + U64(0xB5854744, 0x8FFFFB2D), U64(0xABD40A0C, 0x2832A78A), /* ~= 10^-278 */ + U64(0xE2E69915, 0xB3FFF9F9), U64(0x16C90C8F, 0x323F516C), /* ~= 10^-277 */ + U64(0x8DD01FAD, 0x907FFC3B), U64(0xAE3DA7D9, 0x7F6792E3), /* ~= 10^-276 */ + U64(0xB1442798, 0xF49FFB4A), U64(0x99CD11CF, 0xDF41779C), /* ~= 10^-275 */ + U64(0xDD95317F, 0x31C7FA1D), U64(0x40405643, 0xD711D583), /* ~= 10^-274 */ + U64(0x8A7D3EEF, 0x7F1CFC52), U64(0x482835EA, 0x666B2572), /* ~= 10^-273 */ + U64(0xAD1C8EAB, 0x5EE43B66), U64(0xDA324365, 0x0005EECF), /* ~= 10^-272 */ + U64(0xD863B256, 0x369D4A40), U64(0x90BED43E, 0x40076A82), /* ~= 10^-271 */ + U64(0x873E4F75, 0xE2224E68), U64(0x5A7744A6, 0xE804A291), /* ~= 10^-270 */ + U64(0xA90DE353, 0x5AAAE202), U64(0x711515D0, 0xA205CB36), /* ~= 10^-269 */ + U64(0xD3515C28, 0x31559A83), U64(0x0D5A5B44, 0xCA873E03), /* ~= 10^-268 */ + U64(0x8412D999, 0x1ED58091), U64(0xE858790A, 0xFE9486C2), /* ~= 10^-267 */ + U64(0xA5178FFF, 0x668AE0B6), U64(0x626E974D, 0xBE39A872), /* ~= 10^-266 */ + U64(0xCE5D73FF, 0x402D98E3), U64(0xFB0A3D21, 0x2DC8128F), /* ~= 10^-265 */ + U64(0x80FA687F, 0x881C7F8E), U64(0x7CE66634, 0xBC9D0B99), /* ~= 10^-264 */ + U64(0xA139029F, 0x6A239F72), U64(0x1C1FFFC1, 0xEBC44E80), /* ~= 10^-263 */ + U64(0xC9874347, 0x44AC874E), U64(0xA327FFB2, 0x66B56220), /* ~= 10^-262 */ + U64(0xFBE91419, 0x15D7A922), U64(0x4BF1FF9F, 0x0062BAA8), /* ~= 10^-261 */ + U64(0x9D71AC8F, 0xADA6C9B5), U64(0x6F773FC3, 0x603DB4A9), /* ~= 10^-260 */ + U64(0xC4CE17B3, 0x99107C22), U64(0xCB550FB4, 0x384D21D3), /* ~= 10^-259 */ + U64(0xF6019DA0, 0x7F549B2B), U64(0x7E2A53A1, 0x46606A48), /* ~= 10^-258 */ + U64(0x99C10284, 0x4F94E0FB), U64(0x2EDA7444, 0xCBFC426D), /* ~= 10^-257 */ + U64(0xC0314325, 0x637A1939), U64(0xFA911155, 0xFEFB5308), /* ~= 10^-256 */ + U64(0xF03D93EE, 0xBC589F88), U64(0x793555AB, 0x7EBA27CA), /* ~= 10^-255 */ + U64(0x96267C75, 0x35B763B5), U64(0x4BC1558B, 0x2F3458DE), /* ~= 10^-254 */ + U64(0xBBB01B92, 0x83253CA2), U64(0x9EB1AAED, 0xFB016F16), /* ~= 10^-253 */ + U64(0xEA9C2277, 0x23EE8BCB), U64(0x465E15A9, 0x79C1CADC), /* ~= 10^-252 */ + U64(0x92A1958A, 0x7675175F), U64(0x0BFACD89, 0xEC191EC9), /* ~= 10^-251 */ + U64(0xB749FAED, 0x14125D36), U64(0xCEF980EC, 0x671F667B), /* ~= 10^-250 */ + U64(0xE51C79A8, 0x5916F484), U64(0x82B7E127, 0x80E7401A), /* ~= 10^-249 */ + U64(0x8F31CC09, 0x37AE58D2), U64(0xD1B2ECB8, 0xB0908810), /* ~= 10^-248 */ + U64(0xB2FE3F0B, 0x8599EF07), U64(0x861FA7E6, 0xDCB4AA15), /* ~= 10^-247 */ + U64(0xDFBDCECE, 0x67006AC9), U64(0x67A791E0, 0x93E1D49A), /* ~= 10^-246 */ + U64(0x8BD6A141, 0x006042BD), U64(0xE0C8BB2C, 0x5C6D24E0), /* ~= 10^-245 */ + U64(0xAECC4991, 0x4078536D), U64(0x58FAE9F7, 0x73886E18), /* ~= 10^-244 */ + U64(0xDA7F5BF5, 0x90966848), U64(0xAF39A475, 0x506A899E), /* ~= 10^-243 */ + U64(0x888F9979, 0x7A5E012D), U64(0x6D8406C9, 0x52429603), /* ~= 10^-242 */ + U64(0xAAB37FD7, 0xD8F58178), U64(0xC8E5087B, 0xA6D33B83), /* ~= 10^-241 */ + U64(0xD5605FCD, 0xCF32E1D6), U64(0xFB1E4A9A, 0x90880A64), /* ~= 10^-240 */ + U64(0x855C3BE0, 0xA17FCD26), U64(0x5CF2EEA0, 0x9A55067F), /* ~= 10^-239 */ + U64(0xA6B34AD8, 0xC9DFC06F), U64(0xF42FAA48, 0xC0EA481E), /* ~= 10^-238 */ + U64(0xD0601D8E, 0xFC57B08B), U64(0xF13B94DA, 0xF124DA26), /* ~= 10^-237 */ + U64(0x823C1279, 0x5DB6CE57), U64(0x76C53D08, 0xD6B70858), /* ~= 10^-236 */ + U64(0xA2CB1717, 0xB52481ED), U64(0x54768C4B, 0x0C64CA6E), /* ~= 10^-235 */ + U64(0xCB7DDCDD, 0xA26DA268), U64(0xA9942F5D, 0xCF7DFD09), /* ~= 10^-234 */ + U64(0xFE5D5415, 0x0B090B02), U64(0xD3F93B35, 0x435D7C4C), /* ~= 10^-233 */ + U64(0x9EFA548D, 0x26E5A6E1), U64(0xC47BC501, 0x4A1A6DAF), /* ~= 10^-232 */ + U64(0xC6B8E9B0, 0x709F109A), U64(0x359AB641, 0x9CA1091B), /* ~= 10^-231 */ + U64(0xF867241C, 0x8CC6D4C0), U64(0xC30163D2, 0x03C94B62), /* ~= 10^-230 */ + U64(0x9B407691, 0xD7FC44F8), U64(0x79E0DE63, 0x425DCF1D), /* ~= 10^-229 */ + U64(0xC2109436, 0x4DFB5636), U64(0x985915FC, 0x12F542E4), /* ~= 10^-228 */ + U64(0xF294B943, 0xE17A2BC4), U64(0x3E6F5B7B, 0x17B2939D), /* ~= 10^-227 */ + U64(0x979CF3CA, 0x6CEC5B5A), U64(0xA705992C, 0xEECF9C42), /* ~= 10^-226 */ + U64(0xBD8430BD, 0x08277231), U64(0x50C6FF78, 0x2A838353), /* ~= 10^-225 */ + U64(0xECE53CEC, 0x4A314EBD), U64(0xA4F8BF56, 0x35246428), /* ~= 10^-224 */ + U64(0x940F4613, 0xAE5ED136), U64(0x871B7795, 0xE136BE99), /* ~= 10^-223 */ + U64(0xB9131798, 0x99F68584), U64(0x28E2557B, 0x59846E3F), /* ~= 10^-222 */ + U64(0xE757DD7E, 0xC07426E5), U64(0x331AEADA, 0x2FE589CF), /* ~= 10^-221 */ + U64(0x9096EA6F, 0x3848984F), U64(0x3FF0D2C8, 0x5DEF7621), /* ~= 10^-220 */ + U64(0xB4BCA50B, 0x065ABE63), U64(0x0FED077A, 0x756B53A9), /* ~= 10^-219 */ + U64(0xE1EBCE4D, 0xC7F16DFB), U64(0xD3E84959, 0x12C62894), /* ~= 10^-218 */ + U64(0x8D3360F0, 0x9CF6E4BD), U64(0x64712DD7, 0xABBBD95C), /* ~= 10^-217 */ + U64(0xB080392C, 0xC4349DEC), U64(0xBD8D794D, 0x96AACFB3), /* ~= 10^-216 */ + U64(0xDCA04777, 0xF541C567), U64(0xECF0D7A0, 0xFC5583A0), /* ~= 10^-215 */ + U64(0x89E42CAA, 0xF9491B60), U64(0xF41686C4, 0x9DB57244), /* ~= 10^-214 */ + U64(0xAC5D37D5, 0xB79B6239), U64(0x311C2875, 0xC522CED5), /* ~= 10^-213 */ + U64(0xD77485CB, 0x25823AC7), U64(0x7D633293, 0x366B828B), /* ~= 10^-212 */ + U64(0x86A8D39E, 0xF77164BC), U64(0xAE5DFF9C, 0x02033197), /* ~= 10^-211 */ + U64(0xA8530886, 0xB54DBDEB), U64(0xD9F57F83, 0x0283FDFC), /* ~= 10^-210 */ + U64(0xD267CAA8, 0x62A12D66), U64(0xD072DF63, 0xC324FD7B), /* ~= 10^-209 */ + U64(0x8380DEA9, 0x3DA4BC60), U64(0x4247CB9E, 0x59F71E6D), /* ~= 10^-208 */ + U64(0xA4611653, 0x8D0DEB78), U64(0x52D9BE85, 0xF074E608), /* ~= 10^-207 */ + U64(0xCD795BE8, 0x70516656), U64(0x67902E27, 0x6C921F8B), /* ~= 10^-206 */ + U64(0x806BD971, 0x4632DFF6), U64(0x00BA1CD8, 0xA3DB53B6), /* ~= 10^-205 */ + U64(0xA086CFCD, 0x97BF97F3), U64(0x80E8A40E, 0xCCD228A4), /* ~= 10^-204 */ + U64(0xC8A883C0, 0xFDAF7DF0), U64(0x6122CD12, 0x8006B2CD), /* ~= 10^-203 */ + U64(0xFAD2A4B1, 0x3D1B5D6C), U64(0x796B8057, 0x20085F81), /* ~= 10^-202 */ + U64(0x9CC3A6EE, 0xC6311A63), U64(0xCBE33036, 0x74053BB0), /* ~= 10^-201 */ + U64(0xC3F490AA, 0x77BD60FC), U64(0xBEDBFC44, 0x11068A9C), /* ~= 10^-200 */ + U64(0xF4F1B4D5, 0x15ACB93B), U64(0xEE92FB55, 0x15482D44), /* ~= 10^-199 */ + U64(0x99171105, 0x2D8BF3C5), U64(0x751BDD15, 0x2D4D1C4A), /* ~= 10^-198 */ + U64(0xBF5CD546, 0x78EEF0B6), U64(0xD262D45A, 0x78A0635D), /* ~= 10^-197 */ + U64(0xEF340A98, 0x172AACE4), U64(0x86FB8971, 0x16C87C34), /* ~= 10^-196 */ + U64(0x9580869F, 0x0E7AAC0E), U64(0xD45D35E6, 0xAE3D4DA0), /* ~= 10^-195 */ + U64(0xBAE0A846, 0xD2195712), U64(0x89748360, 0x59CCA109), /* ~= 10^-194 */ + U64(0xE998D258, 0x869FACD7), U64(0x2BD1A438, 0x703FC94B), /* ~= 10^-193 */ + U64(0x91FF8377, 0x5423CC06), U64(0x7B6306A3, 0x4627DDCF), /* ~= 10^-192 */ + U64(0xB67F6455, 0x292CBF08), U64(0x1A3BC84C, 0x17B1D542), /* ~= 10^-191 */ + U64(0xE41F3D6A, 0x7377EECA), U64(0x20CABA5F, 0x1D9E4A93), /* ~= 10^-190 */ + U64(0x8E938662, 0x882AF53E), U64(0x547EB47B, 0x7282EE9C), /* ~= 10^-189 */ + U64(0xB23867FB, 0x2A35B28D), U64(0xE99E619A, 0x4F23AA43), /* ~= 10^-188 */ + U64(0xDEC681F9, 0xF4C31F31), U64(0x6405FA00, 0xE2EC94D4), /* ~= 10^-187 */ + U64(0x8B3C113C, 0x38F9F37E), U64(0xDE83BC40, 0x8DD3DD04), /* ~= 10^-186 */ + U64(0xAE0B158B, 0x4738705E), U64(0x9624AB50, 0xB148D445), /* ~= 10^-185 */ + U64(0xD98DDAEE, 0x19068C76), U64(0x3BADD624, 0xDD9B0957), /* ~= 10^-184 */ + U64(0x87F8A8D4, 0xCFA417C9), U64(0xE54CA5D7, 0x0A80E5D6), /* ~= 10^-183 */ + U64(0xA9F6D30A, 0x038D1DBC), U64(0x5E9FCF4C, 0xCD211F4C), /* ~= 10^-182 */ + U64(0xD47487CC, 0x8470652B), U64(0x7647C320, 0x0069671F), /* ~= 10^-181 */ + U64(0x84C8D4DF, 0xD2C63F3B), U64(0x29ECD9F4, 0x0041E073), /* ~= 10^-180 */ + U64(0xA5FB0A17, 0xC777CF09), U64(0xF4681071, 0x00525890), /* ~= 10^-179 */ + U64(0xCF79CC9D, 0xB955C2CC), U64(0x7182148D, 0x4066EEB4), /* ~= 10^-178 */ + U64(0x81AC1FE2, 0x93D599BF), U64(0xC6F14CD8, 0x48405530), /* ~= 10^-177 */ + U64(0xA21727DB, 0x38CB002F), U64(0xB8ADA00E, 0x5A506A7C), /* ~= 10^-176 */ + U64(0xCA9CF1D2, 0x06FDC03B), U64(0xA6D90811, 0xF0E4851C), /* ~= 10^-175 */ + U64(0xFD442E46, 0x88BD304A), U64(0x908F4A16, 0x6D1DA663), /* ~= 10^-174 */ + U64(0x9E4A9CEC, 0x15763E2E), U64(0x9A598E4E, 0x043287FE), /* ~= 10^-173 */ + U64(0xC5DD4427, 0x1AD3CDBA), U64(0x40EFF1E1, 0x853F29FD), /* ~= 10^-172 */ + U64(0xF7549530, 0xE188C128), U64(0xD12BEE59, 0xE68EF47C), /* ~= 10^-171 */ + U64(0x9A94DD3E, 0x8CF578B9), U64(0x82BB74F8, 0x301958CE), /* ~= 10^-170 */ + U64(0xC13A148E, 0x3032D6E7), U64(0xE36A5236, 0x3C1FAF01), /* ~= 10^-169 */ + U64(0xF18899B1, 0xBC3F8CA1), U64(0xDC44E6C3, 0xCB279AC1), /* ~= 10^-168 */ + U64(0x96F5600F, 0x15A7B7E5), U64(0x29AB103A, 0x5EF8C0B9), /* ~= 10^-167 */ + U64(0xBCB2B812, 0xDB11A5DE), U64(0x7415D448, 0xF6B6F0E7), /* ~= 10^-166 */ + U64(0xEBDF6617, 0x91D60F56), U64(0x111B495B, 0x3464AD21), /* ~= 10^-165 */ + U64(0x936B9FCE, 0xBB25C995), U64(0xCAB10DD9, 0x00BEEC34), /* ~= 10^-164 */ + U64(0xB84687C2, 0x69EF3BFB), U64(0x3D5D514F, 0x40EEA742), /* ~= 10^-163 */ + U64(0xE65829B3, 0x046B0AFA), U64(0x0CB4A5A3, 0x112A5112), /* ~= 10^-162 */ + U64(0x8FF71A0F, 0xE2C2E6DC), U64(0x47F0E785, 0xEABA72AB), /* ~= 10^-161 */ + U64(0xB3F4E093, 0xDB73A093), U64(0x59ED2167, 0x65690F56), /* ~= 10^-160 */ + U64(0xE0F218B8, 0xD25088B8), U64(0x306869C1, 0x3EC3532C), /* ~= 10^-159 */ + U64(0x8C974F73, 0x83725573), U64(0x1E414218, 0xC73A13FB), /* ~= 10^-158 */ + U64(0xAFBD2350, 0x644EEACF), U64(0xE5D1929E, 0xF90898FA), /* ~= 10^-157 */ + U64(0xDBAC6C24, 0x7D62A583), U64(0xDF45F746, 0xB74ABF39), /* ~= 10^-156 */ + U64(0x894BC396, 0xCE5DA772), U64(0x6B8BBA8C, 0x328EB783), /* ~= 10^-155 */ + U64(0xAB9EB47C, 0x81F5114F), U64(0x066EA92F, 0x3F326564), /* ~= 10^-154 */ + U64(0xD686619B, 0xA27255A2), U64(0xC80A537B, 0x0EFEFEBD), /* ~= 10^-153 */ + U64(0x8613FD01, 0x45877585), U64(0xBD06742C, 0xE95F5F36), /* ~= 10^-152 */ + U64(0xA798FC41, 0x96E952E7), U64(0x2C481138, 0x23B73704), /* ~= 10^-151 */ + U64(0xD17F3B51, 0xFCA3A7A0), U64(0xF75A1586, 0x2CA504C5), /* ~= 10^-150 */ + U64(0x82EF8513, 0x3DE648C4), U64(0x9A984D73, 0xDBE722FB), /* ~= 10^-149 */ + U64(0xA3AB6658, 0x0D5FDAF5), U64(0xC13E60D0, 0xD2E0EBBA), /* ~= 10^-148 */ + U64(0xCC963FEE, 0x10B7D1B3), U64(0x318DF905, 0x079926A8), /* ~= 10^-147 */ + U64(0xFFBBCFE9, 0x94E5C61F), U64(0xFDF17746, 0x497F7052), /* ~= 10^-146 */ + U64(0x9FD561F1, 0xFD0F9BD3), U64(0xFEB6EA8B, 0xEDEFA633), /* ~= 10^-145 */ + U64(0xC7CABA6E, 0x7C5382C8), U64(0xFE64A52E, 0xE96B8FC0), /* ~= 10^-144 */ + U64(0xF9BD690A, 0x1B68637B), U64(0x3DFDCE7A, 0xA3C673B0), /* ~= 10^-143 */ + U64(0x9C1661A6, 0x51213E2D), U64(0x06BEA10C, 0xA65C084E), /* ~= 10^-142 */ + U64(0xC31BFA0F, 0xE5698DB8), U64(0x486E494F, 0xCFF30A62), /* ~= 10^-141 */ + U64(0xF3E2F893, 0xDEC3F126), U64(0x5A89DBA3, 0xC3EFCCFA), /* ~= 10^-140 */ + U64(0x986DDB5C, 0x6B3A76B7), U64(0xF8962946, 0x5A75E01C), /* ~= 10^-139 */ + U64(0xBE895233, 0x86091465), U64(0xF6BBB397, 0xF1135823), /* ~= 10^-138 */ + U64(0xEE2BA6C0, 0x678B597F), U64(0x746AA07D, 0xED582E2C), /* ~= 10^-137 */ + U64(0x94DB4838, 0x40B717EF), U64(0xA8C2A44E, 0xB4571CDC), /* ~= 10^-136 */ + U64(0xBA121A46, 0x50E4DDEB), U64(0x92F34D62, 0x616CE413), /* ~= 10^-135 */ + U64(0xE896A0D7, 0xE51E1566), U64(0x77B020BA, 0xF9C81D17), /* ~= 10^-134 */ + U64(0x915E2486, 0xEF32CD60), U64(0x0ACE1474, 0xDC1D122E), /* ~= 10^-133 */ + U64(0xB5B5ADA8, 0xAAFF80B8), U64(0x0D819992, 0x132456BA), /* ~= 10^-132 */ + U64(0xE3231912, 0xD5BF60E6), U64(0x10E1FFF6, 0x97ED6C69), /* ~= 10^-131 */ + U64(0x8DF5EFAB, 0xC5979C8F), U64(0xCA8D3FFA, 0x1EF463C1), /* ~= 10^-130 */ + U64(0xB1736B96, 0xB6FD83B3), U64(0xBD308FF8, 0xA6B17CB2), /* ~= 10^-129 */ + U64(0xDDD0467C, 0x64BCE4A0), U64(0xAC7CB3F6, 0xD05DDBDE), /* ~= 10^-128 */ + U64(0x8AA22C0D, 0xBEF60EE4), U64(0x6BCDF07A, 0x423AA96B), /* ~= 10^-127 */ + U64(0xAD4AB711, 0x2EB3929D), U64(0x86C16C98, 0xD2C953C6), /* ~= 10^-126 */ + U64(0xD89D64D5, 0x7A607744), U64(0xE871C7BF, 0x077BA8B7), /* ~= 10^-125 */ + U64(0x87625F05, 0x6C7C4A8B), U64(0x11471CD7, 0x64AD4972), /* ~= 10^-124 */ + U64(0xA93AF6C6, 0xC79B5D2D), U64(0xD598E40D, 0x3DD89BCF), /* ~= 10^-123 */ + U64(0xD389B478, 0x79823479), U64(0x4AFF1D10, 0x8D4EC2C3), /* ~= 10^-122 */ + U64(0x843610CB, 0x4BF160CB), U64(0xCEDF722A, 0x585139BA), /* ~= 10^-121 */ + U64(0xA54394FE, 0x1EEDB8FE), U64(0xC2974EB4, 0xEE658828), /* ~= 10^-120 */ + U64(0xCE947A3D, 0xA6A9273E), U64(0x733D2262, 0x29FEEA32), /* ~= 10^-119 */ + U64(0x811CCC66, 0x8829B887), U64(0x0806357D, 0x5A3F525F), /* ~= 10^-118 */ + U64(0xA163FF80, 0x2A3426A8), U64(0xCA07C2DC, 0xB0CF26F7), /* ~= 10^-117 */ + U64(0xC9BCFF60, 0x34C13052), U64(0xFC89B393, 0xDD02F0B5), /* ~= 10^-116 */ + U64(0xFC2C3F38, 0x41F17C67), U64(0xBBAC2078, 0xD443ACE2), /* ~= 10^-115 */ + U64(0x9D9BA783, 0x2936EDC0), U64(0xD54B944B, 0x84AA4C0D), /* ~= 10^-114 */ + U64(0xC5029163, 0xF384A931), U64(0x0A9E795E, 0x65D4DF11), /* ~= 10^-113 */ + U64(0xF64335BC, 0xF065D37D), U64(0x4D4617B5, 0xFF4A16D5), /* ~= 10^-112 */ + U64(0x99EA0196, 0x163FA42E), U64(0x504BCED1, 0xBF8E4E45), /* ~= 10^-111 */ + U64(0xC06481FB, 0x9BCF8D39), U64(0xE45EC286, 0x2F71E1D6), /* ~= 10^-110 */ + U64(0xF07DA27A, 0x82C37088), U64(0x5D767327, 0xBB4E5A4C), /* ~= 10^-109 */ + U64(0x964E858C, 0x91BA2655), U64(0x3A6A07F8, 0xD510F86F), /* ~= 10^-108 */ + U64(0xBBE226EF, 0xB628AFEA), U64(0x890489F7, 0x0A55368B), /* ~= 10^-107 */ + U64(0xEADAB0AB, 0xA3B2DBE5), U64(0x2B45AC74, 0xCCEA842E), /* ~= 10^-106 */ + U64(0x92C8AE6B, 0x464FC96F), U64(0x3B0B8BC9, 0x0012929D), /* ~= 10^-105 */ + U64(0xB77ADA06, 0x17E3BBCB), U64(0x09CE6EBB, 0x40173744), /* ~= 10^-104 */ + U64(0xE5599087, 0x9DDCAABD), U64(0xCC420A6A, 0x101D0515), /* ~= 10^-103 */ + U64(0x8F57FA54, 0xC2A9EAB6), U64(0x9FA94682, 0x4A12232D), /* ~= 10^-102 */ + U64(0xB32DF8E9, 0xF3546564), U64(0x47939822, 0xDC96ABF9), /* ~= 10^-101 */ + U64(0xDFF97724, 0x70297EBD), U64(0x59787E2B, 0x93BC56F7), /* ~= 10^-100 */ + U64(0x8BFBEA76, 0xC619EF36), U64(0x57EB4EDB, 0x3C55B65A), /* ~= 10^-99 */ + U64(0xAEFAE514, 0x77A06B03), U64(0xEDE62292, 0x0B6B23F1), /* ~= 10^-98 */ + U64(0xDAB99E59, 0x958885C4), U64(0xE95FAB36, 0x8E45ECED), /* ~= 10^-97 */ + U64(0x88B402F7, 0xFD75539B), U64(0x11DBCB02, 0x18EBB414), /* ~= 10^-96 */ + U64(0xAAE103B5, 0xFCD2A881), U64(0xD652BDC2, 0x9F26A119), /* ~= 10^-95 */ + U64(0xD59944A3, 0x7C0752A2), U64(0x4BE76D33, 0x46F0495F), /* ~= 10^-94 */ + U64(0x857FCAE6, 0x2D8493A5), U64(0x6F70A440, 0x0C562DDB), /* ~= 10^-93 */ + U64(0xA6DFBD9F, 0xB8E5B88E), U64(0xCB4CCD50, 0x0F6BB952), /* ~= 10^-92 */ + U64(0xD097AD07, 0xA71F26B2), U64(0x7E2000A4, 0x1346A7A7), /* ~= 10^-91 */ + U64(0x825ECC24, 0xC873782F), U64(0x8ED40066, 0x8C0C28C8), /* ~= 10^-90 */ + U64(0xA2F67F2D, 0xFA90563B), U64(0x72890080, 0x2F0F32FA), /* ~= 10^-89 */ + U64(0xCBB41EF9, 0x79346BCA), U64(0x4F2B40A0, 0x3AD2FFB9), /* ~= 10^-88 */ + U64(0xFEA126B7, 0xD78186BC), U64(0xE2F610C8, 0x4987BFA8), /* ~= 10^-87 */ + U64(0x9F24B832, 0xE6B0F436), U64(0x0DD9CA7D, 0x2DF4D7C9), /* ~= 10^-86 */ + U64(0xC6EDE63F, 0xA05D3143), U64(0x91503D1C, 0x79720DBB), /* ~= 10^-85 */ + U64(0xF8A95FCF, 0x88747D94), U64(0x75A44C63, 0x97CE912A), /* ~= 10^-84 */ + U64(0x9B69DBE1, 0xB548CE7C), U64(0xC986AFBE, 0x3EE11ABA), /* ~= 10^-83 */ + U64(0xC24452DA, 0x229B021B), U64(0xFBE85BAD, 0xCE996168), /* ~= 10^-82 */ + U64(0xF2D56790, 0xAB41C2A2), U64(0xFAE27299, 0x423FB9C3), /* ~= 10^-81 */ + U64(0x97C560BA, 0x6B0919A5), U64(0xDCCD879F, 0xC967D41A), /* ~= 10^-80 */ + U64(0xBDB6B8E9, 0x05CB600F), U64(0x5400E987, 0xBBC1C920), /* ~= 10^-79 */ + U64(0xED246723, 0x473E3813), U64(0x290123E9, 0xAAB23B68), /* ~= 10^-78 */ + U64(0x9436C076, 0x0C86E30B), U64(0xF9A0B672, 0x0AAF6521), /* ~= 10^-77 */ + U64(0xB9447093, 0x8FA89BCE), U64(0xF808E40E, 0x8D5B3E69), /* ~= 10^-76 */ + U64(0xE7958CB8, 0x7392C2C2), U64(0xB60B1D12, 0x30B20E04), /* ~= 10^-75 */ + U64(0x90BD77F3, 0x483BB9B9), U64(0xB1C6F22B, 0x5E6F48C2), /* ~= 10^-74 */ + U64(0xB4ECD5F0, 0x1A4AA828), U64(0x1E38AEB6, 0x360B1AF3), /* ~= 10^-73 */ + U64(0xE2280B6C, 0x20DD5232), U64(0x25C6DA63, 0xC38DE1B0), /* ~= 10^-72 */ + U64(0x8D590723, 0x948A535F), U64(0x579C487E, 0x5A38AD0E), /* ~= 10^-71 */ + U64(0xB0AF48EC, 0x79ACE837), U64(0x2D835A9D, 0xF0C6D851), /* ~= 10^-70 */ + U64(0xDCDB1B27, 0x98182244), U64(0xF8E43145, 0x6CF88E65), /* ~= 10^-69 */ + U64(0x8A08F0F8, 0xBF0F156B), U64(0x1B8E9ECB, 0x641B58FF), /* ~= 10^-68 */ + U64(0xAC8B2D36, 0xEED2DAC5), U64(0xE272467E, 0x3D222F3F), /* ~= 10^-67 */ + U64(0xD7ADF884, 0xAA879177), U64(0x5B0ED81D, 0xCC6ABB0F), /* ~= 10^-66 */ + U64(0x86CCBB52, 0xEA94BAEA), U64(0x98E94712, 0x9FC2B4E9), /* ~= 10^-65 */ + U64(0xA87FEA27, 0xA539E9A5), U64(0x3F2398D7, 0x47B36224), /* ~= 10^-64 */ + U64(0xD29FE4B1, 0x8E88640E), U64(0x8EEC7F0D, 0x19A03AAD), /* ~= 10^-63 */ + U64(0x83A3EEEE, 0xF9153E89), U64(0x1953CF68, 0x300424AC), /* ~= 10^-62 */ + U64(0xA48CEAAA, 0xB75A8E2B), U64(0x5FA8C342, 0x3C052DD7), /* ~= 10^-61 */ + U64(0xCDB02555, 0x653131B6), U64(0x3792F412, 0xCB06794D), /* ~= 10^-60 */ + U64(0x808E1755, 0x5F3EBF11), U64(0xE2BBD88B, 0xBEE40BD0), /* ~= 10^-59 */ + U64(0xA0B19D2A, 0xB70E6ED6), U64(0x5B6ACEAE, 0xAE9D0EC4), /* ~= 10^-58 */ + U64(0xC8DE0475, 0x64D20A8B), U64(0xF245825A, 0x5A445275), /* ~= 10^-57 */ + U64(0xFB158592, 0xBE068D2E), U64(0xEED6E2F0, 0xF0D56712), /* ~= 10^-56 */ + U64(0x9CED737B, 0xB6C4183D), U64(0x55464DD6, 0x9685606B), /* ~= 10^-55 */ + U64(0xC428D05A, 0xA4751E4C), U64(0xAA97E14C, 0x3C26B886), /* ~= 10^-54 */ + U64(0xF5330471, 0x4D9265DF), U64(0xD53DD99F, 0x4B3066A8), /* ~= 10^-53 */ + U64(0x993FE2C6, 0xD07B7FAB), U64(0xE546A803, 0x8EFE4029), /* ~= 10^-52 */ + U64(0xBF8FDB78, 0x849A5F96), U64(0xDE985204, 0x72BDD033), /* ~= 10^-51 */ + U64(0xEF73D256, 0xA5C0F77C), U64(0x963E6685, 0x8F6D4440), /* ~= 10^-50 */ + U64(0x95A86376, 0x27989AAD), U64(0xDDE70013, 0x79A44AA8), /* ~= 10^-49 */ + U64(0xBB127C53, 0xB17EC159), U64(0x5560C018, 0x580D5D52), /* ~= 10^-48 */ + U64(0xE9D71B68, 0x9DDE71AF), U64(0xAAB8F01E, 0x6E10B4A6), /* ~= 10^-47 */ + U64(0x92267121, 0x62AB070D), U64(0xCAB39613, 0x04CA70E8), /* ~= 10^-46 */ + U64(0xB6B00D69, 0xBB55C8D1), U64(0x3D607B97, 0xC5FD0D22), /* ~= 10^-45 */ + U64(0xE45C10C4, 0x2A2B3B05), U64(0x8CB89A7D, 0xB77C506A), /* ~= 10^-44 */ + U64(0x8EB98A7A, 0x9A5B04E3), U64(0x77F3608E, 0x92ADB242), /* ~= 10^-43 */ + U64(0xB267ED19, 0x40F1C61C), U64(0x55F038B2, 0x37591ED3), /* ~= 10^-42 */ + U64(0xDF01E85F, 0x912E37A3), U64(0x6B6C46DE, 0xC52F6688), /* ~= 10^-41 */ + U64(0x8B61313B, 0xBABCE2C6), U64(0x2323AC4B, 0x3B3DA015), /* ~= 10^-40 */ + U64(0xAE397D8A, 0xA96C1B77), U64(0xABEC975E, 0x0A0D081A), /* ~= 10^-39 */ + U64(0xD9C7DCED, 0x53C72255), U64(0x96E7BD35, 0x8C904A21), /* ~= 10^-38 */ + U64(0x881CEA14, 0x545C7575), U64(0x7E50D641, 0x77DA2E54), /* ~= 10^-37 */ + U64(0xAA242499, 0x697392D2), U64(0xDDE50BD1, 0xD5D0B9E9), /* ~= 10^-36 */ + U64(0xD4AD2DBF, 0xC3D07787), U64(0x955E4EC6, 0x4B44E864), /* ~= 10^-35 */ + U64(0x84EC3C97, 0xDA624AB4), U64(0xBD5AF13B, 0xEF0B113E), /* ~= 10^-34 */ + U64(0xA6274BBD, 0xD0FADD61), U64(0xECB1AD8A, 0xEACDD58E), /* ~= 10^-33 */ + U64(0xCFB11EAD, 0x453994BA), U64(0x67DE18ED, 0xA5814AF2), /* ~= 10^-32 */ + U64(0x81CEB32C, 0x4B43FCF4), U64(0x80EACF94, 0x8770CED7), /* ~= 10^-31 */ + U64(0xA2425FF7, 0x5E14FC31), U64(0xA1258379, 0xA94D028D), /* ~= 10^-30 */ + U64(0xCAD2F7F5, 0x359A3B3E), U64(0x096EE458, 0x13A04330), /* ~= 10^-29 */ + U64(0xFD87B5F2, 0x8300CA0D), U64(0x8BCA9D6E, 0x188853FC), /* ~= 10^-28 */ + U64(0x9E74D1B7, 0x91E07E48), U64(0x775EA264, 0xCF55347D), /* ~= 10^-27 */ + U64(0xC6120625, 0x76589DDA), U64(0x95364AFE, 0x032A819D), /* ~= 10^-26 */ + U64(0xF79687AE, 0xD3EEC551), U64(0x3A83DDBD, 0x83F52204), /* ~= 10^-25 */ + U64(0x9ABE14CD, 0x44753B52), U64(0xC4926A96, 0x72793542), /* ~= 10^-24 */ + U64(0xC16D9A00, 0x95928A27), U64(0x75B7053C, 0x0F178293), /* ~= 10^-23 */ + U64(0xF1C90080, 0xBAF72CB1), U64(0x5324C68B, 0x12DD6338), /* ~= 10^-22 */ + U64(0x971DA050, 0x74DA7BEE), U64(0xD3F6FC16, 0xEBCA5E03), /* ~= 10^-21 */ + U64(0xBCE50864, 0x92111AEA), U64(0x88F4BB1C, 0xA6BCF584), /* ~= 10^-20 */ + U64(0xEC1E4A7D, 0xB69561A5), U64(0x2B31E9E3, 0xD06C32E5), /* ~= 10^-19 */ + U64(0x9392EE8E, 0x921D5D07), U64(0x3AFF322E, 0x62439FCF), /* ~= 10^-18 */ + U64(0xB877AA32, 0x36A4B449), U64(0x09BEFEB9, 0xFAD487C2), /* ~= 10^-17 */ + U64(0xE69594BE, 0xC44DE15B), U64(0x4C2EBE68, 0x7989A9B3), /* ~= 10^-16 */ + U64(0x901D7CF7, 0x3AB0ACD9), U64(0x0F9D3701, 0x4BF60A10), /* ~= 10^-15 */ + U64(0xB424DC35, 0x095CD80F), U64(0x538484C1, 0x9EF38C94), /* ~= 10^-14 */ + U64(0xE12E1342, 0x4BB40E13), U64(0x2865A5F2, 0x06B06FB9), /* ~= 10^-13 */ + U64(0x8CBCCC09, 0x6F5088CB), U64(0xF93F87B7, 0x442E45D3), /* ~= 10^-12 */ + U64(0xAFEBFF0B, 0xCB24AAFE), U64(0xF78F69A5, 0x1539D748), /* ~= 10^-11 */ + U64(0xDBE6FECE, 0xBDEDD5BE), U64(0xB573440E, 0x5A884D1B), /* ~= 10^-10 */ + U64(0x89705F41, 0x36B4A597), U64(0x31680A88, 0xF8953030), /* ~= 10^-9 */ + U64(0xABCC7711, 0x8461CEFC), U64(0xFDC20D2B, 0x36BA7C3D), /* ~= 10^-8 */ + U64(0xD6BF94D5, 0xE57A42BC), U64(0x3D329076, 0x04691B4C), /* ~= 10^-7 */ + U64(0x8637BD05, 0xAF6C69B5), U64(0xA63F9A49, 0xC2C1B10F), /* ~= 10^-6 */ + U64(0xA7C5AC47, 0x1B478423), U64(0x0FCF80DC, 0x33721D53), /* ~= 10^-5 */ + U64(0xD1B71758, 0xE219652B), U64(0xD3C36113, 0x404EA4A8), /* ~= 10^-4 */ + U64(0x83126E97, 0x8D4FDF3B), U64(0x645A1CAC, 0x083126E9), /* ~= 10^-3 */ + U64(0xA3D70A3D, 0x70A3D70A), U64(0x3D70A3D7, 0x0A3D70A3), /* ~= 10^-2 */ + U64(0xCCCCCCCC, 0xCCCCCCCC), U64(0xCCCCCCCC, 0xCCCCCCCC), /* ~= 10^-1 */ + U64(0x80000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^0 */ + U64(0xA0000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^1 */ + U64(0xC8000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^2 */ + U64(0xFA000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^3 */ + U64(0x9C400000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^4 */ + U64(0xC3500000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^5 */ + U64(0xF4240000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^6 */ + U64(0x98968000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^7 */ + U64(0xBEBC2000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^8 */ + U64(0xEE6B2800, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^9 */ + U64(0x9502F900, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^10 */ + U64(0xBA43B740, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^11 */ + U64(0xE8D4A510, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^12 */ + U64(0x9184E72A, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^13 */ + U64(0xB5E620F4, 0x80000000), U64(0x00000000, 0x00000000), /* == 10^14 */ + U64(0xE35FA931, 0xA0000000), U64(0x00000000, 0x00000000), /* == 10^15 */ + U64(0x8E1BC9BF, 0x04000000), U64(0x00000000, 0x00000000), /* == 10^16 */ + U64(0xB1A2BC2E, 0xC5000000), U64(0x00000000, 0x00000000), /* == 10^17 */ + U64(0xDE0B6B3A, 0x76400000), U64(0x00000000, 0x00000000), /* == 10^18 */ + U64(0x8AC72304, 0x89E80000), U64(0x00000000, 0x00000000), /* == 10^19 */ + U64(0xAD78EBC5, 0xAC620000), U64(0x00000000, 0x00000000), /* == 10^20 */ + U64(0xD8D726B7, 0x177A8000), U64(0x00000000, 0x00000000), /* == 10^21 */ + U64(0x87867832, 0x6EAC9000), U64(0x00000000, 0x00000000), /* == 10^22 */ + U64(0xA968163F, 0x0A57B400), U64(0x00000000, 0x00000000), /* == 10^23 */ + U64(0xD3C21BCE, 0xCCEDA100), U64(0x00000000, 0x00000000), /* == 10^24 */ + U64(0x84595161, 0x401484A0), U64(0x00000000, 0x00000000), /* == 10^25 */ + U64(0xA56FA5B9, 0x9019A5C8), U64(0x00000000, 0x00000000), /* == 10^26 */ + U64(0xCECB8F27, 0xF4200F3A), U64(0x00000000, 0x00000000), /* == 10^27 */ + U64(0x813F3978, 0xF8940984), U64(0x40000000, 0x00000000), /* == 10^28 */ + U64(0xA18F07D7, 0x36B90BE5), U64(0x50000000, 0x00000000), /* == 10^29 */ + U64(0xC9F2C9CD, 0x04674EDE), U64(0xA4000000, 0x00000000), /* == 10^30 */ + U64(0xFC6F7C40, 0x45812296), U64(0x4D000000, 0x00000000), /* == 10^31 */ + U64(0x9DC5ADA8, 0x2B70B59D), U64(0xF0200000, 0x00000000), /* == 10^32 */ + U64(0xC5371912, 0x364CE305), U64(0x6C280000, 0x00000000), /* == 10^33 */ + U64(0xF684DF56, 0xC3E01BC6), U64(0xC7320000, 0x00000000), /* == 10^34 */ + U64(0x9A130B96, 0x3A6C115C), U64(0x3C7F4000, 0x00000000), /* == 10^35 */ + U64(0xC097CE7B, 0xC90715B3), U64(0x4B9F1000, 0x00000000), /* == 10^36 */ + U64(0xF0BDC21A, 0xBB48DB20), U64(0x1E86D400, 0x00000000), /* == 10^37 */ + U64(0x96769950, 0xB50D88F4), U64(0x13144480, 0x00000000), /* == 10^38 */ + U64(0xBC143FA4, 0xE250EB31), U64(0x17D955A0, 0x00000000), /* == 10^39 */ + U64(0xEB194F8E, 0x1AE525FD), U64(0x5DCFAB08, 0x00000000), /* == 10^40 */ + U64(0x92EFD1B8, 0xD0CF37BE), U64(0x5AA1CAE5, 0x00000000), /* == 10^41 */ + U64(0xB7ABC627, 0x050305AD), U64(0xF14A3D9E, 0x40000000), /* == 10^42 */ + U64(0xE596B7B0, 0xC643C719), U64(0x6D9CCD05, 0xD0000000), /* == 10^43 */ + U64(0x8F7E32CE, 0x7BEA5C6F), U64(0xE4820023, 0xA2000000), /* == 10^44 */ + U64(0xB35DBF82, 0x1AE4F38B), U64(0xDDA2802C, 0x8A800000), /* == 10^45 */ + U64(0xE0352F62, 0xA19E306E), U64(0xD50B2037, 0xAD200000), /* == 10^46 */ + U64(0x8C213D9D, 0xA502DE45), U64(0x4526F422, 0xCC340000), /* == 10^47 */ + U64(0xAF298D05, 0x0E4395D6), U64(0x9670B12B, 0x7F410000), /* == 10^48 */ + U64(0xDAF3F046, 0x51D47B4C), U64(0x3C0CDD76, 0x5F114000), /* == 10^49 */ + U64(0x88D8762B, 0xF324CD0F), U64(0xA5880A69, 0xFB6AC800), /* == 10^50 */ + U64(0xAB0E93B6, 0xEFEE0053), U64(0x8EEA0D04, 0x7A457A00), /* == 10^51 */ + U64(0xD5D238A4, 0xABE98068), U64(0x72A49045, 0x98D6D880), /* == 10^52 */ + U64(0x85A36366, 0xEB71F041), U64(0x47A6DA2B, 0x7F864750), /* == 10^53 */ + U64(0xA70C3C40, 0xA64E6C51), U64(0x999090B6, 0x5F67D924), /* == 10^54 */ + U64(0xD0CF4B50, 0xCFE20765), U64(0xFFF4B4E3, 0xF741CF6D), /* == 10^55 */ + U64(0x82818F12, 0x81ED449F), U64(0xBFF8F10E, 0x7A8921A4), /* ~= 10^56 */ + U64(0xA321F2D7, 0x226895C7), U64(0xAFF72D52, 0x192B6A0D), /* ~= 10^57 */ + U64(0xCBEA6F8C, 0xEB02BB39), U64(0x9BF4F8A6, 0x9F764490), /* ~= 10^58 */ + U64(0xFEE50B70, 0x25C36A08), U64(0x02F236D0, 0x4753D5B4), /* ~= 10^59 */ + U64(0x9F4F2726, 0x179A2245), U64(0x01D76242, 0x2C946590), /* ~= 10^60 */ + U64(0xC722F0EF, 0x9D80AAD6), U64(0x424D3AD2, 0xB7B97EF5), /* ~= 10^61 */ + U64(0xF8EBAD2B, 0x84E0D58B), U64(0xD2E08987, 0x65A7DEB2), /* ~= 10^62 */ + U64(0x9B934C3B, 0x330C8577), U64(0x63CC55F4, 0x9F88EB2F), /* ~= 10^63 */ + U64(0xC2781F49, 0xFFCFA6D5), U64(0x3CBF6B71, 0xC76B25FB), /* ~= 10^64 */ + U64(0xF316271C, 0x7FC3908A), U64(0x8BEF464E, 0x3945EF7A), /* ~= 10^65 */ + U64(0x97EDD871, 0xCFDA3A56), U64(0x97758BF0, 0xE3CBB5AC), /* ~= 10^66 */ + U64(0xBDE94E8E, 0x43D0C8EC), U64(0x3D52EEED, 0x1CBEA317), /* ~= 10^67 */ + U64(0xED63A231, 0xD4C4FB27), U64(0x4CA7AAA8, 0x63EE4BDD), /* ~= 10^68 */ + U64(0x945E455F, 0x24FB1CF8), U64(0x8FE8CAA9, 0x3E74EF6A), /* ~= 10^69 */ + U64(0xB975D6B6, 0xEE39E436), U64(0xB3E2FD53, 0x8E122B44), /* ~= 10^70 */ + U64(0xE7D34C64, 0xA9C85D44), U64(0x60DBBCA8, 0x7196B616), /* ~= 10^71 */ + U64(0x90E40FBE, 0xEA1D3A4A), U64(0xBC8955E9, 0x46FE31CD), /* ~= 10^72 */ + U64(0xB51D13AE, 0xA4A488DD), U64(0x6BABAB63, 0x98BDBE41), /* ~= 10^73 */ + U64(0xE264589A, 0x4DCDAB14), U64(0xC696963C, 0x7EED2DD1), /* ~= 10^74 */ + U64(0x8D7EB760, 0x70A08AEC), U64(0xFC1E1DE5, 0xCF543CA2), /* ~= 10^75 */ + U64(0xB0DE6538, 0x8CC8ADA8), U64(0x3B25A55F, 0x43294BCB), /* ~= 10^76 */ + U64(0xDD15FE86, 0xAFFAD912), U64(0x49EF0EB7, 0x13F39EBE), /* ~= 10^77 */ + U64(0x8A2DBF14, 0x2DFCC7AB), U64(0x6E356932, 0x6C784337), /* ~= 10^78 */ + U64(0xACB92ED9, 0x397BF996), U64(0x49C2C37F, 0x07965404), /* ~= 10^79 */ + U64(0xD7E77A8F, 0x87DAF7FB), U64(0xDC33745E, 0xC97BE906), /* ~= 10^80 */ + U64(0x86F0AC99, 0xB4E8DAFD), U64(0x69A028BB, 0x3DED71A3), /* ~= 10^81 */ + U64(0xA8ACD7C0, 0x222311BC), U64(0xC40832EA, 0x0D68CE0C), /* ~= 10^82 */ + U64(0xD2D80DB0, 0x2AABD62B), U64(0xF50A3FA4, 0x90C30190), /* ~= 10^83 */ + U64(0x83C7088E, 0x1AAB65DB), U64(0x792667C6, 0xDA79E0FA), /* ~= 10^84 */ + U64(0xA4B8CAB1, 0xA1563F52), U64(0x577001B8, 0x91185938), /* ~= 10^85 */ + U64(0xCDE6FD5E, 0x09ABCF26), U64(0xED4C0226, 0xB55E6F86), /* ~= 10^86 */ + U64(0x80B05E5A, 0xC60B6178), U64(0x544F8158, 0x315B05B4), /* ~= 10^87 */ + U64(0xA0DC75F1, 0x778E39D6), U64(0x696361AE, 0x3DB1C721), /* ~= 10^88 */ + U64(0xC913936D, 0xD571C84C), U64(0x03BC3A19, 0xCD1E38E9), /* ~= 10^89 */ + U64(0xFB587849, 0x4ACE3A5F), U64(0x04AB48A0, 0x4065C723), /* ~= 10^90 */ + U64(0x9D174B2D, 0xCEC0E47B), U64(0x62EB0D64, 0x283F9C76), /* ~= 10^91 */ + U64(0xC45D1DF9, 0x42711D9A), U64(0x3BA5D0BD, 0x324F8394), /* ~= 10^92 */ + U64(0xF5746577, 0x930D6500), U64(0xCA8F44EC, 0x7EE36479), /* ~= 10^93 */ + U64(0x9968BF6A, 0xBBE85F20), U64(0x7E998B13, 0xCF4E1ECB), /* ~= 10^94 */ + U64(0xBFC2EF45, 0x6AE276E8), U64(0x9E3FEDD8, 0xC321A67E), /* ~= 10^95 */ + U64(0xEFB3AB16, 0xC59B14A2), U64(0xC5CFE94E, 0xF3EA101E), /* ~= 10^96 */ + U64(0x95D04AEE, 0x3B80ECE5), U64(0xBBA1F1D1, 0x58724A12), /* ~= 10^97 */ + U64(0xBB445DA9, 0xCA61281F), U64(0x2A8A6E45, 0xAE8EDC97), /* ~= 10^98 */ + U64(0xEA157514, 0x3CF97226), U64(0xF52D09D7, 0x1A3293BD), /* ~= 10^99 */ + U64(0x924D692C, 0xA61BE758), U64(0x593C2626, 0x705F9C56), /* ~= 10^100 */ + U64(0xB6E0C377, 0xCFA2E12E), U64(0x6F8B2FB0, 0x0C77836C), /* ~= 10^101 */ + U64(0xE498F455, 0xC38B997A), U64(0x0B6DFB9C, 0x0F956447), /* ~= 10^102 */ + U64(0x8EDF98B5, 0x9A373FEC), U64(0x4724BD41, 0x89BD5EAC), /* ~= 10^103 */ + U64(0xB2977EE3, 0x00C50FE7), U64(0x58EDEC91, 0xEC2CB657), /* ~= 10^104 */ + U64(0xDF3D5E9B, 0xC0F653E1), U64(0x2F2967B6, 0x6737E3ED), /* ~= 10^105 */ + U64(0x8B865B21, 0x5899F46C), U64(0xBD79E0D2, 0x0082EE74), /* ~= 10^106 */ + U64(0xAE67F1E9, 0xAEC07187), U64(0xECD85906, 0x80A3AA11), /* ~= 10^107 */ + U64(0xDA01EE64, 0x1A708DE9), U64(0xE80E6F48, 0x20CC9495), /* ~= 10^108 */ + U64(0x884134FE, 0x908658B2), U64(0x3109058D, 0x147FDCDD), /* ~= 10^109 */ + U64(0xAA51823E, 0x34A7EEDE), U64(0xBD4B46F0, 0x599FD415), /* ~= 10^110 */ + U64(0xD4E5E2CD, 0xC1D1EA96), U64(0x6C9E18AC, 0x7007C91A), /* ~= 10^111 */ + U64(0x850FADC0, 0x9923329E), U64(0x03E2CF6B, 0xC604DDB0), /* ~= 10^112 */ + U64(0xA6539930, 0xBF6BFF45), U64(0x84DB8346, 0xB786151C), /* ~= 10^113 */ + U64(0xCFE87F7C, 0xEF46FF16), U64(0xE6126418, 0x65679A63), /* ~= 10^114 */ + U64(0x81F14FAE, 0x158C5F6E), U64(0x4FCB7E8F, 0x3F60C07E), /* ~= 10^115 */ + U64(0xA26DA399, 0x9AEF7749), U64(0xE3BE5E33, 0x0F38F09D), /* ~= 10^116 */ + U64(0xCB090C80, 0x01AB551C), U64(0x5CADF5BF, 0xD3072CC5), /* ~= 10^117 */ + U64(0xFDCB4FA0, 0x02162A63), U64(0x73D9732F, 0xC7C8F7F6), /* ~= 10^118 */ + U64(0x9E9F11C4, 0x014DDA7E), U64(0x2867E7FD, 0xDCDD9AFA), /* ~= 10^119 */ + U64(0xC646D635, 0x01A1511D), U64(0xB281E1FD, 0x541501B8), /* ~= 10^120 */ + U64(0xF7D88BC2, 0x4209A565), U64(0x1F225A7C, 0xA91A4226), /* ~= 10^121 */ + U64(0x9AE75759, 0x6946075F), U64(0x3375788D, 0xE9B06958), /* ~= 10^122 */ + U64(0xC1A12D2F, 0xC3978937), U64(0x0052D6B1, 0x641C83AE), /* ~= 10^123 */ + U64(0xF209787B, 0xB47D6B84), U64(0xC0678C5D, 0xBD23A49A), /* ~= 10^124 */ + U64(0x9745EB4D, 0x50CE6332), U64(0xF840B7BA, 0x963646E0), /* ~= 10^125 */ + U64(0xBD176620, 0xA501FBFF), U64(0xB650E5A9, 0x3BC3D898), /* ~= 10^126 */ + U64(0xEC5D3FA8, 0xCE427AFF), U64(0xA3E51F13, 0x8AB4CEBE), /* ~= 10^127 */ + U64(0x93BA47C9, 0x80E98CDF), U64(0xC66F336C, 0x36B10137), /* ~= 10^128 */ + U64(0xB8A8D9BB, 0xE123F017), U64(0xB80B0047, 0x445D4184), /* ~= 10^129 */ + U64(0xE6D3102A, 0xD96CEC1D), U64(0xA60DC059, 0x157491E5), /* ~= 10^130 */ + U64(0x9043EA1A, 0xC7E41392), U64(0x87C89837, 0xAD68DB2F), /* ~= 10^131 */ + U64(0xB454E4A1, 0x79DD1877), U64(0x29BABE45, 0x98C311FB), /* ~= 10^132 */ + U64(0xE16A1DC9, 0xD8545E94), U64(0xF4296DD6, 0xFEF3D67A), /* ~= 10^133 */ + U64(0x8CE2529E, 0x2734BB1D), U64(0x1899E4A6, 0x5F58660C), /* ~= 10^134 */ + U64(0xB01AE745, 0xB101E9E4), U64(0x5EC05DCF, 0xF72E7F8F), /* ~= 10^135 */ + U64(0xDC21A117, 0x1D42645D), U64(0x76707543, 0xF4FA1F73), /* ~= 10^136 */ + U64(0x899504AE, 0x72497EBA), U64(0x6A06494A, 0x791C53A8), /* ~= 10^137 */ + U64(0xABFA45DA, 0x0EDBDE69), U64(0x0487DB9D, 0x17636892), /* ~= 10^138 */ + U64(0xD6F8D750, 0x9292D603), U64(0x45A9D284, 0x5D3C42B6), /* ~= 10^139 */ + U64(0x865B8692, 0x5B9BC5C2), U64(0x0B8A2392, 0xBA45A9B2), /* ~= 10^140 */ + U64(0xA7F26836, 0xF282B732), U64(0x8E6CAC77, 0x68D7141E), /* ~= 10^141 */ + U64(0xD1EF0244, 0xAF2364FF), U64(0x3207D795, 0x430CD926), /* ~= 10^142 */ + U64(0x8335616A, 0xED761F1F), U64(0x7F44E6BD, 0x49E807B8), /* ~= 10^143 */ + U64(0xA402B9C5, 0xA8D3A6E7), U64(0x5F16206C, 0x9C6209A6), /* ~= 10^144 */ + U64(0xCD036837, 0x130890A1), U64(0x36DBA887, 0xC37A8C0F), /* ~= 10^145 */ + U64(0x80222122, 0x6BE55A64), U64(0xC2494954, 0xDA2C9789), /* ~= 10^146 */ + U64(0xA02AA96B, 0x06DEB0FD), U64(0xF2DB9BAA, 0x10B7BD6C), /* ~= 10^147 */ + U64(0xC83553C5, 0xC8965D3D), U64(0x6F928294, 0x94E5ACC7), /* ~= 10^148 */ + U64(0xFA42A8B7, 0x3ABBF48C), U64(0xCB772339, 0xBA1F17F9), /* ~= 10^149 */ + U64(0x9C69A972, 0x84B578D7), U64(0xFF2A7604, 0x14536EFB), /* ~= 10^150 */ + U64(0xC38413CF, 0x25E2D70D), U64(0xFEF51385, 0x19684ABA), /* ~= 10^151 */ + U64(0xF46518C2, 0xEF5B8CD1), U64(0x7EB25866, 0x5FC25D69), /* ~= 10^152 */ + U64(0x98BF2F79, 0xD5993802), U64(0xEF2F773F, 0xFBD97A61), /* ~= 10^153 */ + U64(0xBEEEFB58, 0x4AFF8603), U64(0xAAFB550F, 0xFACFD8FA), /* ~= 10^154 */ + U64(0xEEAABA2E, 0x5DBF6784), U64(0x95BA2A53, 0xF983CF38), /* ~= 10^155 */ + U64(0x952AB45C, 0xFA97A0B2), U64(0xDD945A74, 0x7BF26183), /* ~= 10^156 */ + U64(0xBA756174, 0x393D88DF), U64(0x94F97111, 0x9AEEF9E4), /* ~= 10^157 */ + U64(0xE912B9D1, 0x478CEB17), U64(0x7A37CD56, 0x01AAB85D), /* ~= 10^158 */ + U64(0x91ABB422, 0xCCB812EE), U64(0xAC62E055, 0xC10AB33A), /* ~= 10^159 */ + U64(0xB616A12B, 0x7FE617AA), U64(0x577B986B, 0x314D6009), /* ~= 10^160 */ + U64(0xE39C4976, 0x5FDF9D94), U64(0xED5A7E85, 0xFDA0B80B), /* ~= 10^161 */ + U64(0x8E41ADE9, 0xFBEBC27D), U64(0x14588F13, 0xBE847307), /* ~= 10^162 */ + U64(0xB1D21964, 0x7AE6B31C), U64(0x596EB2D8, 0xAE258FC8), /* ~= 10^163 */ + U64(0xDE469FBD, 0x99A05FE3), U64(0x6FCA5F8E, 0xD9AEF3BB), /* ~= 10^164 */ + U64(0x8AEC23D6, 0x80043BEE), U64(0x25DE7BB9, 0x480D5854), /* ~= 10^165 */ + U64(0xADA72CCC, 0x20054AE9), U64(0xAF561AA7, 0x9A10AE6A), /* ~= 10^166 */ + U64(0xD910F7FF, 0x28069DA4), U64(0x1B2BA151, 0x8094DA04), /* ~= 10^167 */ + U64(0x87AA9AFF, 0x79042286), U64(0x90FB44D2, 0xF05D0842), /* ~= 10^168 */ + U64(0xA99541BF, 0x57452B28), U64(0x353A1607, 0xAC744A53), /* ~= 10^169 */ + U64(0xD3FA922F, 0x2D1675F2), U64(0x42889B89, 0x97915CE8), /* ~= 10^170 */ + U64(0x847C9B5D, 0x7C2E09B7), U64(0x69956135, 0xFEBADA11), /* ~= 10^171 */ + U64(0xA59BC234, 0xDB398C25), U64(0x43FAB983, 0x7E699095), /* ~= 10^172 */ + U64(0xCF02B2C2, 0x1207EF2E), U64(0x94F967E4, 0x5E03F4BB), /* ~= 10^173 */ + U64(0x8161AFB9, 0x4B44F57D), U64(0x1D1BE0EE, 0xBAC278F5), /* ~= 10^174 */ + U64(0xA1BA1BA7, 0x9E1632DC), U64(0x6462D92A, 0x69731732), /* ~= 10^175 */ + U64(0xCA28A291, 0x859BBF93), U64(0x7D7B8F75, 0x03CFDCFE), /* ~= 10^176 */ + U64(0xFCB2CB35, 0xE702AF78), U64(0x5CDA7352, 0x44C3D43E), /* ~= 10^177 */ + U64(0x9DEFBF01, 0xB061ADAB), U64(0x3A088813, 0x6AFA64A7), /* ~= 10^178 */ + U64(0xC56BAEC2, 0x1C7A1916), U64(0x088AAA18, 0x45B8FDD0), /* ~= 10^179 */ + U64(0xF6C69A72, 0xA3989F5B), U64(0x8AAD549E, 0x57273D45), /* ~= 10^180 */ + U64(0x9A3C2087, 0xA63F6399), U64(0x36AC54E2, 0xF678864B), /* ~= 10^181 */ + U64(0xC0CB28A9, 0x8FCF3C7F), U64(0x84576A1B, 0xB416A7DD), /* ~= 10^182 */ + U64(0xF0FDF2D3, 0xF3C30B9F), U64(0x656D44A2, 0xA11C51D5), /* ~= 10^183 */ + U64(0x969EB7C4, 0x7859E743), U64(0x9F644AE5, 0xA4B1B325), /* ~= 10^184 */ + U64(0xBC4665B5, 0x96706114), U64(0x873D5D9F, 0x0DDE1FEE), /* ~= 10^185 */ + U64(0xEB57FF22, 0xFC0C7959), U64(0xA90CB506, 0xD155A7EA), /* ~= 10^186 */ + U64(0x9316FF75, 0xDD87CBD8), U64(0x09A7F124, 0x42D588F2), /* ~= 10^187 */ + U64(0xB7DCBF53, 0x54E9BECE), U64(0x0C11ED6D, 0x538AEB2F), /* ~= 10^188 */ + U64(0xE5D3EF28, 0x2A242E81), U64(0x8F1668C8, 0xA86DA5FA), /* ~= 10^189 */ + U64(0x8FA47579, 0x1A569D10), U64(0xF96E017D, 0x694487BC), /* ~= 10^190 */ + U64(0xB38D92D7, 0x60EC4455), U64(0x37C981DC, 0xC395A9AC), /* ~= 10^191 */ + U64(0xE070F78D, 0x3927556A), U64(0x85BBE253, 0xF47B1417), /* ~= 10^192 */ + U64(0x8C469AB8, 0x43B89562), U64(0x93956D74, 0x78CCEC8E), /* ~= 10^193 */ + U64(0xAF584166, 0x54A6BABB), U64(0x387AC8D1, 0x970027B2), /* ~= 10^194 */ + U64(0xDB2E51BF, 0xE9D0696A), U64(0x06997B05, 0xFCC0319E), /* ~= 10^195 */ + U64(0x88FCF317, 0xF22241E2), U64(0x441FECE3, 0xBDF81F03), /* ~= 10^196 */ + U64(0xAB3C2FDD, 0xEEAAD25A), U64(0xD527E81C, 0xAD7626C3), /* ~= 10^197 */ + U64(0xD60B3BD5, 0x6A5586F1), U64(0x8A71E223, 0xD8D3B074), /* ~= 10^198 */ + U64(0x85C70565, 0x62757456), U64(0xF6872D56, 0x67844E49), /* ~= 10^199 */ + U64(0xA738C6BE, 0xBB12D16C), U64(0xB428F8AC, 0x016561DB), /* ~= 10^200 */ + U64(0xD106F86E, 0x69D785C7), U64(0xE13336D7, 0x01BEBA52), /* ~= 10^201 */ + U64(0x82A45B45, 0x0226B39C), U64(0xECC00246, 0x61173473), /* ~= 10^202 */ + U64(0xA34D7216, 0x42B06084), U64(0x27F002D7, 0xF95D0190), /* ~= 10^203 */ + U64(0xCC20CE9B, 0xD35C78A5), U64(0x31EC038D, 0xF7B441F4), /* ~= 10^204 */ + U64(0xFF290242, 0xC83396CE), U64(0x7E670471, 0x75A15271), /* ~= 10^205 */ + U64(0x9F79A169, 0xBD203E41), U64(0x0F0062C6, 0xE984D386), /* ~= 10^206 */ + U64(0xC75809C4, 0x2C684DD1), U64(0x52C07B78, 0xA3E60868), /* ~= 10^207 */ + U64(0xF92E0C35, 0x37826145), U64(0xA7709A56, 0xCCDF8A82), /* ~= 10^208 */ + U64(0x9BBCC7A1, 0x42B17CCB), U64(0x88A66076, 0x400BB691), /* ~= 10^209 */ + U64(0xC2ABF989, 0x935DDBFE), U64(0x6ACFF893, 0xD00EA435), /* ~= 10^210 */ + U64(0xF356F7EB, 0xF83552FE), U64(0x0583F6B8, 0xC4124D43), /* ~= 10^211 */ + U64(0x98165AF3, 0x7B2153DE), U64(0xC3727A33, 0x7A8B704A), /* ~= 10^212 */ + U64(0xBE1BF1B0, 0x59E9A8D6), U64(0x744F18C0, 0x592E4C5C), /* ~= 10^213 */ + U64(0xEDA2EE1C, 0x7064130C), U64(0x1162DEF0, 0x6F79DF73), /* ~= 10^214 */ + U64(0x9485D4D1, 0xC63E8BE7), U64(0x8ADDCB56, 0x45AC2BA8), /* ~= 10^215 */ + U64(0xB9A74A06, 0x37CE2EE1), U64(0x6D953E2B, 0xD7173692), /* ~= 10^216 */ + U64(0xE8111C87, 0xC5C1BA99), U64(0xC8FA8DB6, 0xCCDD0437), /* ~= 10^217 */ + U64(0x910AB1D4, 0xDB9914A0), U64(0x1D9C9892, 0x400A22A2), /* ~= 10^218 */ + U64(0xB54D5E4A, 0x127F59C8), U64(0x2503BEB6, 0xD00CAB4B), /* ~= 10^219 */ + U64(0xE2A0B5DC, 0x971F303A), U64(0x2E44AE64, 0x840FD61D), /* ~= 10^220 */ + U64(0x8DA471A9, 0xDE737E24), U64(0x5CEAECFE, 0xD289E5D2), /* ~= 10^221 */ + U64(0xB10D8E14, 0x56105DAD), U64(0x7425A83E, 0x872C5F47), /* ~= 10^222 */ + U64(0xDD50F199, 0x6B947518), U64(0xD12F124E, 0x28F77719), /* ~= 10^223 */ + U64(0x8A5296FF, 0xE33CC92F), U64(0x82BD6B70, 0xD99AAA6F), /* ~= 10^224 */ + U64(0xACE73CBF, 0xDC0BFB7B), U64(0x636CC64D, 0x1001550B), /* ~= 10^225 */ + U64(0xD8210BEF, 0xD30EFA5A), U64(0x3C47F7E0, 0x5401AA4E), /* ~= 10^226 */ + U64(0x8714A775, 0xE3E95C78), U64(0x65ACFAEC, 0x34810A71), /* ~= 10^227 */ + U64(0xA8D9D153, 0x5CE3B396), U64(0x7F1839A7, 0x41A14D0D), /* ~= 10^228 */ + U64(0xD31045A8, 0x341CA07C), U64(0x1EDE4811, 0x1209A050), /* ~= 10^229 */ + U64(0x83EA2B89, 0x2091E44D), U64(0x934AED0A, 0xAB460432), /* ~= 10^230 */ + U64(0xA4E4B66B, 0x68B65D60), U64(0xF81DA84D, 0x5617853F), /* ~= 10^231 */ + U64(0xCE1DE406, 0x42E3F4B9), U64(0x36251260, 0xAB9D668E), /* ~= 10^232 */ + U64(0x80D2AE83, 0xE9CE78F3), U64(0xC1D72B7C, 0x6B426019), /* ~= 10^233 */ + U64(0xA1075A24, 0xE4421730), U64(0xB24CF65B, 0x8612F81F), /* ~= 10^234 */ + U64(0xC94930AE, 0x1D529CFC), U64(0xDEE033F2, 0x6797B627), /* ~= 10^235 */ + U64(0xFB9B7CD9, 0xA4A7443C), U64(0x169840EF, 0x017DA3B1), /* ~= 10^236 */ + U64(0x9D412E08, 0x06E88AA5), U64(0x8E1F2895, 0x60EE864E), /* ~= 10^237 */ + U64(0xC491798A, 0x08A2AD4E), U64(0xF1A6F2BA, 0xB92A27E2), /* ~= 10^238 */ + U64(0xF5B5D7EC, 0x8ACB58A2), U64(0xAE10AF69, 0x6774B1DB), /* ~= 10^239 */ + U64(0x9991A6F3, 0xD6BF1765), U64(0xACCA6DA1, 0xE0A8EF29), /* ~= 10^240 */ + U64(0xBFF610B0, 0xCC6EDD3F), U64(0x17FD090A, 0x58D32AF3), /* ~= 10^241 */ + U64(0xEFF394DC, 0xFF8A948E), U64(0xDDFC4B4C, 0xEF07F5B0), /* ~= 10^242 */ + U64(0x95F83D0A, 0x1FB69CD9), U64(0x4ABDAF10, 0x1564F98E), /* ~= 10^243 */ + U64(0xBB764C4C, 0xA7A4440F), U64(0x9D6D1AD4, 0x1ABE37F1), /* ~= 10^244 */ + U64(0xEA53DF5F, 0xD18D5513), U64(0x84C86189, 0x216DC5ED), /* ~= 10^245 */ + U64(0x92746B9B, 0xE2F8552C), U64(0x32FD3CF5, 0xB4E49BB4), /* ~= 10^246 */ + U64(0xB7118682, 0xDBB66A77), U64(0x3FBC8C33, 0x221DC2A1), /* ~= 10^247 */ + U64(0xE4D5E823, 0x92A40515), U64(0x0FABAF3F, 0xEAA5334A), /* ~= 10^248 */ + U64(0x8F05B116, 0x3BA6832D), U64(0x29CB4D87, 0xF2A7400E), /* ~= 10^249 */ + U64(0xB2C71D5B, 0xCA9023F8), U64(0x743E20E9, 0xEF511012), /* ~= 10^250 */ + U64(0xDF78E4B2, 0xBD342CF6), U64(0x914DA924, 0x6B255416), /* ~= 10^251 */ + U64(0x8BAB8EEF, 0xB6409C1A), U64(0x1AD089B6, 0xC2F7548E), /* ~= 10^252 */ + U64(0xAE9672AB, 0xA3D0C320), U64(0xA184AC24, 0x73B529B1), /* ~= 10^253 */ + U64(0xDA3C0F56, 0x8CC4F3E8), U64(0xC9E5D72D, 0x90A2741E), /* ~= 10^254 */ + U64(0x88658996, 0x17FB1871), U64(0x7E2FA67C, 0x7A658892), /* ~= 10^255 */ + U64(0xAA7EEBFB, 0x9DF9DE8D), U64(0xDDBB901B, 0x98FEEAB7), /* ~= 10^256 */ + U64(0xD51EA6FA, 0x85785631), U64(0x552A7422, 0x7F3EA565), /* ~= 10^257 */ + U64(0x8533285C, 0x936B35DE), U64(0xD53A8895, 0x8F87275F), /* ~= 10^258 */ + U64(0xA67FF273, 0xB8460356), U64(0x8A892ABA, 0xF368F137), /* ~= 10^259 */ + U64(0xD01FEF10, 0xA657842C), U64(0x2D2B7569, 0xB0432D85), /* ~= 10^260 */ + U64(0x8213F56A, 0x67F6B29B), U64(0x9C3B2962, 0x0E29FC73), /* ~= 10^261 */ + U64(0xA298F2C5, 0x01F45F42), U64(0x8349F3BA, 0x91B47B8F), /* ~= 10^262 */ + U64(0xCB3F2F76, 0x42717713), U64(0x241C70A9, 0x36219A73), /* ~= 10^263 */ + U64(0xFE0EFB53, 0xD30DD4D7), U64(0xED238CD3, 0x83AA0110), /* ~= 10^264 */ + U64(0x9EC95D14, 0x63E8A506), U64(0xF4363804, 0x324A40AA), /* ~= 10^265 */ + U64(0xC67BB459, 0x7CE2CE48), U64(0xB143C605, 0x3EDCD0D5), /* ~= 10^266 */ + U64(0xF81AA16F, 0xDC1B81DA), U64(0xDD94B786, 0x8E94050A), /* ~= 10^267 */ + U64(0x9B10A4E5, 0xE9913128), U64(0xCA7CF2B4, 0x191C8326), /* ~= 10^268 */ + U64(0xC1D4CE1F, 0x63F57D72), U64(0xFD1C2F61, 0x1F63A3F0), /* ~= 10^269 */ + U64(0xF24A01A7, 0x3CF2DCCF), U64(0xBC633B39, 0x673C8CEC), /* ~= 10^270 */ + U64(0x976E4108, 0x8617CA01), U64(0xD5BE0503, 0xE085D813), /* ~= 10^271 */ + U64(0xBD49D14A, 0xA79DBC82), U64(0x4B2D8644, 0xD8A74E18), /* ~= 10^272 */ + U64(0xEC9C459D, 0x51852BA2), U64(0xDDF8E7D6, 0x0ED1219E), /* ~= 10^273 */ + U64(0x93E1AB82, 0x52F33B45), U64(0xCABB90E5, 0xC942B503), /* ~= 10^274 */ + U64(0xB8DA1662, 0xE7B00A17), U64(0x3D6A751F, 0x3B936243), /* ~= 10^275 */ + U64(0xE7109BFB, 0xA19C0C9D), U64(0x0CC51267, 0x0A783AD4), /* ~= 10^276 */ + U64(0x906A617D, 0x450187E2), U64(0x27FB2B80, 0x668B24C5), /* ~= 10^277 */ + U64(0xB484F9DC, 0x9641E9DA), U64(0xB1F9F660, 0x802DEDF6), /* ~= 10^278 */ + U64(0xE1A63853, 0xBBD26451), U64(0x5E7873F8, 0xA0396973), /* ~= 10^279 */ + U64(0x8D07E334, 0x55637EB2), U64(0xDB0B487B, 0x6423E1E8), /* ~= 10^280 */ + U64(0xB049DC01, 0x6ABC5E5F), U64(0x91CE1A9A, 0x3D2CDA62), /* ~= 10^281 */ + U64(0xDC5C5301, 0xC56B75F7), U64(0x7641A140, 0xCC7810FB), /* ~= 10^282 */ + U64(0x89B9B3E1, 0x1B6329BA), U64(0xA9E904C8, 0x7FCB0A9D), /* ~= 10^283 */ + U64(0xAC2820D9, 0x623BF429), U64(0x546345FA, 0x9FBDCD44), /* ~= 10^284 */ + U64(0xD732290F, 0xBACAF133), U64(0xA97C1779, 0x47AD4095), /* ~= 10^285 */ + U64(0x867F59A9, 0xD4BED6C0), U64(0x49ED8EAB, 0xCCCC485D), /* ~= 10^286 */ + U64(0xA81F3014, 0x49EE8C70), U64(0x5C68F256, 0xBFFF5A74), /* ~= 10^287 */ + U64(0xD226FC19, 0x5C6A2F8C), U64(0x73832EEC, 0x6FFF3111), /* ~= 10^288 */ + U64(0x83585D8F, 0xD9C25DB7), U64(0xC831FD53, 0xC5FF7EAB), /* ~= 10^289 */ + U64(0xA42E74F3, 0xD032F525), U64(0xBA3E7CA8, 0xB77F5E55), /* ~= 10^290 */ + U64(0xCD3A1230, 0xC43FB26F), U64(0x28CE1BD2, 0xE55F35EB), /* ~= 10^291 */ + U64(0x80444B5E, 0x7AA7CF85), U64(0x7980D163, 0xCF5B81B3), /* ~= 10^292 */ + U64(0xA0555E36, 0x1951C366), U64(0xD7E105BC, 0xC332621F), /* ~= 10^293 */ + U64(0xC86AB5C3, 0x9FA63440), U64(0x8DD9472B, 0xF3FEFAA7), /* ~= 10^294 */ + U64(0xFA856334, 0x878FC150), U64(0xB14F98F6, 0xF0FEB951), /* ~= 10^295 */ + U64(0x9C935E00, 0xD4B9D8D2), U64(0x6ED1BF9A, 0x569F33D3), /* ~= 10^296 */ + U64(0xC3B83581, 0x09E84F07), U64(0x0A862F80, 0xEC4700C8), /* ~= 10^297 */ + U64(0xF4A642E1, 0x4C6262C8), U64(0xCD27BB61, 0x2758C0FA), /* ~= 10^298 */ + U64(0x98E7E9CC, 0xCFBD7DBD), U64(0x8038D51C, 0xB897789C), /* ~= 10^299 */ + U64(0xBF21E440, 0x03ACDD2C), U64(0xE0470A63, 0xE6BD56C3), /* ~= 10^300 */ + U64(0xEEEA5D50, 0x04981478), U64(0x1858CCFC, 0xE06CAC74), /* ~= 10^301 */ + U64(0x95527A52, 0x02DF0CCB), U64(0x0F37801E, 0x0C43EBC8), /* ~= 10^302 */ + U64(0xBAA718E6, 0x8396CFFD), U64(0xD3056025, 0x8F54E6BA), /* ~= 10^303 */ + U64(0xE950DF20, 0x247C83FD), U64(0x47C6B82E, 0xF32A2069), /* ~= 10^304 */ + U64(0x91D28B74, 0x16CDD27E), U64(0x4CDC331D, 0x57FA5441), /* ~= 10^305 */ + U64(0xB6472E51, 0x1C81471D), U64(0xE0133FE4, 0xADF8E952), /* ~= 10^306 */ + U64(0xE3D8F9E5, 0x63A198E5), U64(0x58180FDD, 0xD97723A6), /* ~= 10^307 */ + U64(0x8E679C2F, 0x5E44FF8F), U64(0x570F09EA, 0xA7EA7648), /* ~= 10^308 */ + U64(0xB201833B, 0x35D63F73), U64(0x2CD2CC65, 0x51E513DA), /* ~= 10^309 */ + U64(0xDE81E40A, 0x034BCF4F), U64(0xF8077F7E, 0xA65E58D1), /* ~= 10^310 */ + U64(0x8B112E86, 0x420F6191), U64(0xFB04AFAF, 0x27FAF782), /* ~= 10^311 */ + U64(0xADD57A27, 0xD29339F6), U64(0x79C5DB9A, 0xF1F9B563), /* ~= 10^312 */ + U64(0xD94AD8B1, 0xC7380874), U64(0x18375281, 0xAE7822BC), /* ~= 10^313 */ + U64(0x87CEC76F, 0x1C830548), U64(0x8F229391, 0x0D0B15B5), /* ~= 10^314 */ + U64(0xA9C2794A, 0xE3A3C69A), U64(0xB2EB3875, 0x504DDB22), /* ~= 10^315 */ + U64(0xD433179D, 0x9C8CB841), U64(0x5FA60692, 0xA46151EB), /* ~= 10^316 */ + U64(0x849FEEC2, 0x81D7F328), U64(0xDBC7C41B, 0xA6BCD333), /* ~= 10^317 */ + U64(0xA5C7EA73, 0x224DEFF3), U64(0x12B9B522, 0x906C0800), /* ~= 10^318 */ + U64(0xCF39E50F, 0xEAE16BEF), U64(0xD768226B, 0x34870A00), /* ~= 10^319 */ + U64(0x81842F29, 0xF2CCE375), U64(0xE6A11583, 0x00D46640), /* ~= 10^320 */ + U64(0xA1E53AF4, 0x6F801C53), U64(0x60495AE3, 0xC1097FD0), /* ~= 10^321 */ + U64(0xCA5E89B1, 0x8B602368), U64(0x385BB19C, 0xB14BDFC4), /* ~= 10^322 */ + U64(0xFCF62C1D, 0xEE382C42), U64(0x46729E03, 0xDD9ED7B5), /* ~= 10^323 */ + U64(0x9E19DB92, 0xB4E31BA9), U64(0x6C07A2C2, 0x6A8346D1) /* ~= 10^324 */ +}; + +/** + Get the cached pow10 value from pow10_sig_table. + @param exp10 The exponent of pow(10, e). This value must in range + POW10_SIG_TABLE_MIN_EXP to POW10_SIG_TABLE_MAX_EXP. + @param hi The highest 64 bits of pow(10, e). + @param lo The lower 64 bits after `hi`. + */ +static_inline void pow10_table_get_sig(i32 exp10, u64 *hi, u64 *lo) { + i32 idx = exp10 - (POW10_SIG_TABLE_MIN_EXP); + *hi = pow10_sig_table[idx * 2]; + *lo = pow10_sig_table[idx * 2 + 1]; +} + +/** + Get the exponent (base 2) for highest 64 bits significand in pow10_sig_table. + */ +static_inline void pow10_table_get_exp(i32 exp10, i32 *exp2) { + /* e2 = floor(log2(pow(10, e))) - 64 + 1 */ + /* = floor(e * log2(10) - 63) */ + *exp2 = (exp10 * 217706 - 4128768) >> 16; +} + +#endif + + + +/*============================================================================== + * JSON Character Matcher + *============================================================================*/ + +/** Character type */ +typedef u8 char_type; + +/** Whitespace character: ' ', '\\t', '\\n', '\\r'. */ +static const char_type CHAR_TYPE_SPACE = 1 << 0; + +/** Number character: '-', [0-9]. */ +static const char_type CHAR_TYPE_NUMBER = 1 << 1; + +/** JSON Escaped character: '"', '\', [0x00-0x1F]. */ +static const char_type CHAR_TYPE_ESC_ASCII = 1 << 2; + +/** Non-ASCII character: [0x80-0xFF]. */ +static const char_type CHAR_TYPE_NON_ASCII = 1 << 3; + +/** JSON container character: '{', '['. */ +static const char_type CHAR_TYPE_CONTAINER = 1 << 4; + +/** Comment character: '/'. */ +static const char_type CHAR_TYPE_COMMENT = 1 << 5; + +/** Line end character: '\\n', '\\r', '\0'. */ +static const char_type CHAR_TYPE_LINE_END = 1 << 6; + +/** Hexadecimal numeric character: [0-9a-fA-F]. */ +static const char_type CHAR_TYPE_HEX = 1 << 7; + +/** Character type table (generate with misc/make_tables.c) */ +static const char_type char_table[256] = { + 0x44, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x05, 0x45, 0x04, 0x04, 0x45, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x20, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 +}; + +/** Match a character with specified type. */ +static_inline bool char_is_type(u8 c, char_type type) { + return (char_table[c] & type) != 0; +} + +/** Match a whitespace: ' ', '\\t', '\\n', '\\r'. */ +static_inline bool char_is_space(u8 c) { + return char_is_type(c, (char_type)CHAR_TYPE_SPACE); +} + +/** Match a whitespace or comment: ' ', '\\t', '\\n', '\\r', '/'. */ +static_inline bool char_is_space_or_comment(u8 c) { + return char_is_type(c, (char_type)(CHAR_TYPE_SPACE | CHAR_TYPE_COMMENT)); +} + +/** Match a JSON number: '-', [0-9]. */ +static_inline bool char_is_number(u8 c) { + return char_is_type(c, (char_type)CHAR_TYPE_NUMBER); +} + +/** Match a JSON container: '{', '['. */ +static_inline bool char_is_container(u8 c) { + return char_is_type(c, (char_type)CHAR_TYPE_CONTAINER); +} + +/** Match a stop character in ASCII string: '"', '\', [0x00-0x1F,0x80-0xFF]. */ +static_inline bool char_is_ascii_stop(u8 c) { + return char_is_type(c, (char_type)(CHAR_TYPE_ESC_ASCII | + CHAR_TYPE_NON_ASCII)); +} + +/** Match a line end character: '\\n', '\\r', '\0'. */ +static_inline bool char_is_line_end(u8 c) { + return char_is_type(c, (char_type)CHAR_TYPE_LINE_END); +} + +/** Match a hexadecimal numeric character: [0-9a-fA-F]. */ +static_inline bool char_is_hex(u8 c) { + return char_is_type(c, (char_type)CHAR_TYPE_HEX); +} + + + +/*============================================================================== + * Digit Character Matcher + *============================================================================*/ + +/** Digit type */ +typedef u8 digi_type; + +/** Digit: '0'. */ +static const digi_type DIGI_TYPE_ZERO = 1 << 0; + +/** Digit: [1-9]. */ +static const digi_type DIGI_TYPE_NONZERO = 1 << 1; + +/** Plus sign (positive): '+'. */ +static const digi_type DIGI_TYPE_POS = 1 << 2; + +/** Minus sign (negative): '-'. */ +static const digi_type DIGI_TYPE_NEG = 1 << 3; + +/** Decimal point: '.' */ +static const digi_type DIGI_TYPE_DOT = 1 << 4; + +/** Exponent sign: 'e, 'E'. */ +static const digi_type DIGI_TYPE_EXP = 1 << 5; + +/** Digit type table (generate with misc/make_tables.c) */ +static const digi_type digi_table[256] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x10, 0x00, + 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/** Match a character with specified type. */ +static_inline bool digi_is_type(u8 d, digi_type type) { + return (digi_table[d] & type) != 0; +} + +/** Match a sign: '+', '-' */ +static_inline bool digi_is_sign(u8 d) { + return digi_is_type(d, (digi_type)(DIGI_TYPE_POS | DIGI_TYPE_NEG)); +} + +/** Match a none zero digit: [1-9] */ +static_inline bool digi_is_nonzero(u8 d) { + return digi_is_type(d, (digi_type)DIGI_TYPE_NONZERO); +} + +/** Match a digit: [0-9] */ +static_inline bool digi_is_digit(u8 d) { + return digi_is_type(d, (digi_type)(DIGI_TYPE_ZERO | DIGI_TYPE_NONZERO)); +} + +/** Match an exponent sign: 'e', 'E'. */ +static_inline bool digi_is_exp(u8 d) { + return digi_is_type(d, (digi_type)DIGI_TYPE_EXP); +} + +/** Match a floating point indicator: '.', 'e', 'E'. */ +static_inline bool digi_is_fp(u8 d) { + return digi_is_type(d, (digi_type)(DIGI_TYPE_DOT | DIGI_TYPE_EXP)); +} + +/** Match a digit or floating point indicator: [0-9], '.', 'e', 'E'. */ +static_inline bool digi_is_digit_or_fp(u8 d) { + return digi_is_type(d, (digi_type)(DIGI_TYPE_ZERO | DIGI_TYPE_NONZERO | + DIGI_TYPE_DOT | DIGI_TYPE_EXP)); +} + + + +#if !YYJSON_DISABLE_READER + +/*============================================================================== + * Hex Character Reader + * This function is used by JSON reader to read escaped characters. + *============================================================================*/ + +/** + This table is used to convert 4 hex character sequence to a number. + A valid hex character [0-9A-Fa-f] will mapped to it's raw number [0x00, 0x0F], + an invalid hex character will mapped to [0xF0]. + (generate with misc/make_tables.c) + */ +static const u8 hex_conv_table[256] = { + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0 +}; + +/** + Scans an escaped character sequence as a UTF-16 code unit (branchless). + e.g. "\\u005C" should pass "005C" as `cur`. + + This requires the string has 4-byte zero padding. + */ +static_inline bool read_hex_u16(const u8 *cur, u16 *val) { + u16 c0, c1, c2, c3, t0, t1; + c0 = hex_conv_table[cur[0]]; + c1 = hex_conv_table[cur[1]]; + c2 = hex_conv_table[cur[2]]; + c3 = hex_conv_table[cur[3]]; + t0 = (u16)((c0 << 8) | c2); + t1 = (u16)((c1 << 8) | c3); + *val = (u16)((t0 << 4) | t1); + return ((t0 | t1) & (u16)0xF0F0) == 0; +} + + + +/*============================================================================== + * JSON Reader Utils + * These functions are used by JSON reader to read literals and comments. + *============================================================================*/ + +/** Read 'true' literal, '*cur' should be 't'. */ +static_inline bool read_true(u8 **ptr, yyjson_val *val) { + u8 *cur = *ptr; + u8 **end = ptr; + if (likely(byte_match_4(cur, "true"))) { + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; + *end = cur + 4; + return true; + } + return false; +} + +/** Read 'false' literal, '*cur' should be 'f'. */ +static_inline bool read_false(u8 **ptr, yyjson_val *val) { + u8 *cur = *ptr; + u8 **end = ptr; + if (likely(byte_match_4(cur + 1, "alse"))) { + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; + *end = cur + 5; + return true; + } + return false; +} + +/** Read 'null' literal, '*cur' should be 'n'. */ +static_inline bool read_null(u8 **ptr, yyjson_val *val) { + u8 *cur = *ptr; + u8 **end = ptr; + if (likely(byte_match_4(cur, "null"))) { + val->tag = YYJSON_TYPE_NULL; + *end = cur + 4; + return true; + } + return false; +} + +/** Read 'Inf' or 'Infinity' literal (ignoring case). */ +static_inline bool read_inf(bool sign, u8 **ptr, u8 **pre, yyjson_val *val) { + u8 *hdr = *ptr - sign; + u8 *cur = *ptr; + u8 **end = ptr; + if ((cur[0] == 'I' || cur[0] == 'i') && + (cur[1] == 'N' || cur[1] == 'n') && + (cur[2] == 'F' || cur[2] == 'f')) { + if ((cur[3] == 'I' || cur[3] == 'i') && + (cur[4] == 'N' || cur[4] == 'n') && + (cur[5] == 'I' || cur[5] == 'i') && + (cur[6] == 'T' || cur[6] == 't') && + (cur[7] == 'Y' || cur[7] == 'y')) { + cur += 8; + } else { + cur += 3; + } + *end = cur; + if (pre) { + /* add null-terminator for previous raw string */ + if (*pre) **pre = '\0'; + *pre = cur; + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; + val->uni.str = (const char *)hdr; + } else { + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.u64 = f64_raw_get_inf(sign); + } + return true; + } + return false; +} + +/** Read 'NaN' literal (ignoring case). */ +static_inline bool read_nan(bool sign, u8 **ptr, u8 **pre, yyjson_val *val) { + u8 *hdr = *ptr - sign; + u8 *cur = *ptr; + u8 **end = ptr; + if ((cur[0] == 'N' || cur[0] == 'n') && + (cur[1] == 'A' || cur[1] == 'a') && + (cur[2] == 'N' || cur[2] == 'n')) { + cur += 3; + *end = cur; + if (pre) { + /* add null-terminator for previous raw string */ + if (*pre) **pre = '\0'; + *pre = cur; + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; + val->uni.str = (const char *)hdr; + } else { + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.u64 = f64_raw_get_nan(sign); + } + return true; + } + return false; +} + +/** Read 'Inf', 'Infinity' or 'NaN' literal (ignoring case). */ +static_inline bool read_inf_or_nan(bool sign, u8 **ptr, u8 **pre, + yyjson_val *val) { + if (read_inf(sign, ptr, pre, val)) return true; + if (read_nan(sign, ptr, pre, val)) return true; + return false; +} + +/** Read a JSON number as raw string. */ +static_noinline bool read_number_raw(u8 **ptr, + u8 **pre, + yyjson_read_flag flg, + yyjson_val *val, + const char **msg) { + +#define return_err(_pos, _msg) do { \ + *msg = _msg; \ + *end = _pos; \ + return false; \ +} while (false) + +#define return_raw() do { \ + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \ + val->uni.str = (const char *)hdr; \ + *pre = cur; *end = cur; return true; \ +} while (false) + + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + + /* add null-terminator for previous raw string */ + if (*pre) **pre = '\0'; + + /* skip sign */ + cur += (*cur == '-'); + + /* read first digit, check leading zero */ + if (unlikely(!digi_is_digit(*cur))) { + if (has_read_flag(ALLOW_INF_AND_NAN)) { + if (read_inf_or_nan(*hdr == '-', &cur, pre, val)) return_raw(); + } + return_err(cur, "no digit after minus sign"); + } + + /* read integral part */ + if (*cur == '0') { + cur++; + if (unlikely(digi_is_digit(*cur))) { + return_err(cur - 1, "number with leading zero is not allowed"); + } + if (!digi_is_fp(*cur)) return_raw(); + } else { + while (digi_is_digit(*cur)) cur++; + if (!digi_is_fp(*cur)) return_raw(); + } + + /* read fraction part */ + if (*cur == '.') { + cur++; + if (!digi_is_digit(*cur++)) { + return_err(cur, "no digit after decimal point"); + } + while (digi_is_digit(*cur)) cur++; + } + + /* read exponent part */ + if (digi_is_exp(*cur)) { + cur += 1 + digi_is_sign(cur[1]); + if (!digi_is_digit(*cur++)) { + return_err(cur, "no digit after exponent sign"); + } + while (digi_is_digit(*cur)) cur++; + } + + return_raw(); + +#undef return_err +#undef return_raw +} + +/** + Skips spaces and comments as many as possible. + + It will return false in these cases: + 1. No character is skipped. The 'end' pointer is set as input cursor. + 2. A multiline comment is not closed. The 'end' pointer is set as the head + of this comment block. + */ +static_noinline bool skip_spaces_and_comments(u8 **ptr) { + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + while (true) { + if (byte_match_2(cur, "/*")) { + hdr = cur; + cur += 2; + while (true) { + if (byte_match_2(cur, "*/")) { + cur += 2; + break; + } + if (*cur == 0) { + *end = hdr; + return false; + } + cur++; + } + continue; + } + if (byte_match_2(cur, "//")) { + cur += 2; + while (!char_is_line_end(*cur)) cur++; + continue; + } + if (char_is_space(*cur)) { + cur += 1; + while (char_is_space(*cur)) cur++; + continue; + } + break; + } + *end = cur; + return hdr != cur; +} + +/** + Check truncated string. + Returns true if `cur` match `str` but is truncated. + */ +static_inline bool is_truncated_str(u8 *cur, u8 *end, + const char *str, + bool case_sensitive) { + usize len = strlen(str); + if (cur + len <= end || end <= cur) return false; + if (case_sensitive) { + return memcmp(cur, str, (usize)(end - cur)) == 0; + } + for (; cur < end; cur++, str++) { + if ((*cur != (u8)*str) && (*cur != (u8)*str - 'a' + 'A')) { + return false; + } + } + return true; +} + +/** + Check truncated JSON on parsing errors. + Returns true if the input is valid but truncated. + */ +static_noinline bool is_truncated_end(u8 *hdr, u8 *cur, u8 *end, + yyjson_read_code code, + yyjson_read_flag flg) { + if (cur >= end) return true; + if (code == YYJSON_READ_ERROR_LITERAL) { + if (is_truncated_str(cur, end, "true", true) || + is_truncated_str(cur, end, "false", true) || + is_truncated_str(cur, end, "null", true)) { + return true; + } + } + if (code == YYJSON_READ_ERROR_UNEXPECTED_CHARACTER || + code == YYJSON_READ_ERROR_INVALID_NUMBER || + code == YYJSON_READ_ERROR_LITERAL) { + if (has_read_flag(ALLOW_INF_AND_NAN)) { + if (*cur == '-') cur++; + if (is_truncated_str(cur, end, "infinity", false) || + is_truncated_str(cur, end, "nan", false)) { + return true; + } + } + } + if (code == YYJSON_READ_ERROR_UNEXPECTED_CONTENT) { + if (has_read_flag(ALLOW_INF_AND_NAN)) { + if (hdr + 3 <= cur && + is_truncated_str(cur - 3, end, "infinity", false)) { + return true; /* e.g. infin would be read as inf + in */ + } + } + } + if (code == YYJSON_READ_ERROR_INVALID_STRING) { + usize len = (usize)(end - cur); + + /* unicode escape sequence */ + if (*cur == '\\') { + if (len == 1) return true; + if (len <= 5) { + if (*++cur != 'u') return false; + for (++cur; cur < end; cur++) { + if (!char_is_hex(*cur)) return false; + } + return true; + } + return false; + } + + /* 2 to 4 bytes UTF-8, see `read_string()` for details. */ + if (*cur & 0x80) { + u8 c0 = cur[0], c1 = cur[1], c2 = cur[2]; + if (len == 1) { + /* 2 bytes UTF-8, truncated */ + if ((c0 & 0xE0) == 0xC0 && (c0 & 0x1E) != 0x00) return true; + /* 3 bytes UTF-8, truncated */ + if ((c0 & 0xF0) == 0xE0) return true; + /* 4 bytes UTF-8, truncated */ + if ((c0 & 0xF8) == 0xF0 && (c0 & 0x07) <= 0x04) return true; + } + if (len == 2) { + /* 3 bytes UTF-8, truncated */ + if ((c0 & 0xF0) == 0xE0 && + (c1 & 0xC0) == 0x80) { + u8 pat = (u8)(((c0 & 0x0F) << 1) | ((c1 & 0x20) >> 5)); + return 0x01 <= pat && pat != 0x1B; + } + /* 4 bytes UTF-8, truncated */ + if ((c0 & 0xF8) == 0xF0 && + (c1 & 0xC0) == 0x80) { + u8 pat = (u8)(((c0 & 0x07) << 2) | ((c1 & 0x30) >> 4)); + return 0x01 <= pat && pat <= 0x10; + } + } + if (len == 3) { + /* 4 bytes UTF-8, truncated */ + if ((c0 & 0xF8) == 0xF0 && + (c1 & 0xC0) == 0x80 && + (c2 & 0xC0) == 0x80) { + u8 pat = (u8)(((c0 & 0x07) << 2) | ((c1 & 0x30) >> 4)); + return 0x01 <= pat && pat <= 0x10; + } + } + } + } + return false; +} + + + +#if YYJSON_HAS_IEEE_754 && !YYJSON_DISABLE_FAST_FP_CONV /* FP_READER */ + +/*============================================================================== + * BigInt For Floating Point Number Reader + * + * The bigint algorithm is used by floating-point number reader to get correctly + * rounded result for numbers with lots of digits. This part of code is rarely + * used for common numbers. + *============================================================================*/ + +/** Maximum exponent of exact pow10 */ +#define U64_POW10_MAX_EXP 19 + +/** Table: [ 10^0, ..., 10^19 ] (generate with misc/make_tables.c) */ +static const u64 u64_pow10_table[U64_POW10_MAX_EXP + 1] = { + U64(0x00000000, 0x00000001), U64(0x00000000, 0x0000000A), + U64(0x00000000, 0x00000064), U64(0x00000000, 0x000003E8), + U64(0x00000000, 0x00002710), U64(0x00000000, 0x000186A0), + U64(0x00000000, 0x000F4240), U64(0x00000000, 0x00989680), + U64(0x00000000, 0x05F5E100), U64(0x00000000, 0x3B9ACA00), + U64(0x00000002, 0x540BE400), U64(0x00000017, 0x4876E800), + U64(0x000000E8, 0xD4A51000), U64(0x00000918, 0x4E72A000), + U64(0x00005AF3, 0x107A4000), U64(0x00038D7E, 0xA4C68000), + U64(0x002386F2, 0x6FC10000), U64(0x01634578, 0x5D8A0000), + U64(0x0DE0B6B3, 0xA7640000), U64(0x8AC72304, 0x89E80000) +}; + +/** Maximum numbers of chunks used by a bigint (58 is enough here). */ +#define BIGINT_MAX_CHUNKS 64 + +/** Unsigned arbitrarily large integer */ +typedef struct bigint { + u32 used; /* used chunks count, should not be 0 */ + u64 bits[BIGINT_MAX_CHUNKS]; /* chunks */ +} bigint; + +/** + Evaluate 'big += val'. + @param big A big number (can be 0). + @param val An unsigned integer (can be 0). + */ +static_inline void bigint_add_u64(bigint *big, u64 val) { + u32 idx, max; + u64 num = big->bits[0]; + u64 add = num + val; + big->bits[0] = add; + if (likely((add >= num) || (add >= val))) return; + for ((void)(idx = 1), max = big->used; idx < max; idx++) { + if (likely(big->bits[idx] != U64_MAX)) { + big->bits[idx] += 1; + return; + } + big->bits[idx] = 0; + } + big->bits[big->used++] = 1; +} + +/** + Evaluate 'big *= val'. + @param big A big number (can be 0). + @param val An unsigned integer (cannot be 0). + */ +static_inline void bigint_mul_u64(bigint *big, u64 val) { + u32 idx = 0, max = big->used; + u64 hi, lo, carry = 0; + for (; idx < max; idx++) { + if (big->bits[idx]) break; + } + for (; idx < max; idx++) { + u128_mul_add(big->bits[idx], val, carry, &hi, &lo); + big->bits[idx] = lo; + carry = hi; + } + if (carry) big->bits[big->used++] = carry; +} + +/** + Evaluate 'big *= 2^exp'. + @param big A big number (can be 0). + @param exp An exponent integer (can be 0). + */ +static_inline void bigint_mul_pow2(bigint *big, u32 exp) { + u32 shft = exp % 64; + u32 move = exp / 64; + u32 idx = big->used; + if (unlikely(shft == 0)) { + for (; idx > 0; idx--) { + big->bits[idx + move - 1] = big->bits[idx - 1]; + } + big->used += move; + while (move) big->bits[--move] = 0; + } else { + big->bits[idx] = 0; + for (; idx > 0; idx--) { + u64 num = big->bits[idx] << shft; + num |= big->bits[idx - 1] >> (64 - shft); + big->bits[idx + move] = num; + } + big->bits[move] = big->bits[0] << shft; + big->used += move + (big->bits[big->used + move] > 0); + while (move) big->bits[--move] = 0; + } +} + +/** + Evaluate 'big *= 10^exp'. + @param big A big number (can be 0). + @param exp An exponent integer (cannot be 0). + */ +static_inline void bigint_mul_pow10(bigint *big, i32 exp) { + for (; exp >= U64_POW10_MAX_EXP; exp -= U64_POW10_MAX_EXP) { + bigint_mul_u64(big, u64_pow10_table[U64_POW10_MAX_EXP]); + } + if (exp) { + bigint_mul_u64(big, u64_pow10_table[exp]); + } +} + +/** + Compare two bigint. + @return -1 if 'a < b', +1 if 'a > b', 0 if 'a == b'. + */ +static_inline i32 bigint_cmp(bigint *a, bigint *b) { + u32 idx = a->used; + if (a->used < b->used) return -1; + if (a->used > b->used) return +1; + while (idx-- > 0) { + u64 av = a->bits[idx]; + u64 bv = b->bits[idx]; + if (av < bv) return -1; + if (av > bv) return +1; + } + return 0; +} + +/** + Evaluate 'big = val'. + @param big A big number (can be 0). + @param val An unsigned integer (can be 0). + */ +static_inline void bigint_set_u64(bigint *big, u64 val) { + big->used = 1; + big->bits[0] = val; +} + +/** Set a bigint with floating point number string. */ +static_noinline void bigint_set_buf(bigint *big, u64 sig, i32 *exp, + u8 *sig_cut, u8 *sig_end, u8 *dot_pos) { + + if (unlikely(!sig_cut)) { + /* no digit cut, set significant part only */ + bigint_set_u64(big, sig); + return; + + } else { + /* some digits were cut, read them from 'sig_cut' to 'sig_end' */ + u8 *hdr = sig_cut; + u8 *cur = hdr; + u32 len = 0; + u64 val = 0; + bool dig_big_cut = false; + bool has_dot = (hdr < dot_pos) & (dot_pos < sig_end); + u32 dig_len_total = U64_SAFE_DIG + (u32)(sig_end - hdr) - has_dot; + + sig -= (*sig_cut >= '5'); /* sig was rounded before */ + if (dig_len_total > F64_MAX_DEC_DIG) { + dig_big_cut = true; + sig_end -= dig_len_total - (F64_MAX_DEC_DIG + 1); + sig_end -= (dot_pos + 1 == sig_end); + dig_len_total = (F64_MAX_DEC_DIG + 1); + } + *exp -= (i32)dig_len_total - U64_SAFE_DIG; + + big->used = 1; + big->bits[0] = sig; + while (cur < sig_end) { + if (likely(cur != dot_pos)) { + val = val * 10 + (u8)(*cur++ - '0'); + len++; + if (unlikely(cur == sig_end && dig_big_cut)) { + /* The last digit must be non-zero, */ + /* set it to '1' for correct rounding. */ + val = val - (val % 10) + 1; + } + if (len == U64_SAFE_DIG || cur == sig_end) { + bigint_mul_pow10(big, (i32)len); + bigint_add_u64(big, val); + val = 0; + len = 0; + } + } else { + cur++; + } + } + } +} + + + +/*============================================================================== + * Diy Floating Point + *============================================================================*/ + +/** "Do It Yourself Floating Point" struct. */ +typedef struct diy_fp { + u64 sig; /* significand */ + i32 exp; /* exponent, base 2 */ + i32 pad; /* padding, useless */ +} diy_fp; + +/** Get cached rounded diy_fp with pow(10, e) The input value must in range + [POW10_SIG_TABLE_MIN_EXP, POW10_SIG_TABLE_MAX_EXP]. */ +static_inline diy_fp diy_fp_get_cached_pow10(i32 exp10) { + diy_fp fp; + u64 sig_ext; + pow10_table_get_sig(exp10, &fp.sig, &sig_ext); + pow10_table_get_exp(exp10, &fp.exp); + fp.sig += (sig_ext >> 63); + return fp; +} + +/** Returns fp * fp2. */ +static_inline diy_fp diy_fp_mul(diy_fp fp, diy_fp fp2) { + u64 hi, lo; + u128_mul(fp.sig, fp2.sig, &hi, &lo); + fp.sig = hi + (lo >> 63); + fp.exp += fp2.exp + 64; + return fp; +} + +/** Convert diy_fp to IEEE-754 raw value. */ +static_inline u64 diy_fp_to_ieee_raw(diy_fp fp) { + u64 sig = fp.sig; + i32 exp = fp.exp; + u32 lz_bits; + if (unlikely(fp.sig == 0)) return 0; + + lz_bits = u64_lz_bits(sig); + sig <<= lz_bits; + sig >>= F64_BITS - F64_SIG_FULL_BITS; + exp -= (i32)lz_bits; + exp += F64_BITS - F64_SIG_FULL_BITS; + exp += F64_SIG_BITS; + + if (unlikely(exp >= F64_MAX_BIN_EXP)) { + /* overflow */ + return F64_RAW_INF; + } else if (likely(exp >= F64_MIN_BIN_EXP - 1)) { + /* normal */ + exp += F64_EXP_BIAS; + return ((u64)exp << F64_SIG_BITS) | (sig & F64_SIG_MASK); + } else if (likely(exp >= F64_MIN_BIN_EXP - F64_SIG_FULL_BITS)) { + /* subnormal */ + return sig >> (F64_MIN_BIN_EXP - exp - 1); + } else { + /* underflow */ + return 0; + } +} + + + +/*============================================================================== + * JSON Number Reader (IEEE-754) + *============================================================================*/ + +/** Maximum exact pow10 exponent for double value. */ +#define F64_POW10_EXP_MAX_EXACT 22 + +/** Cached pow10 table. */ +static const f64 f64_pow10_table[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, + 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 +}; + +/** + Read a JSON number. + + 1. This function assume that the floating-point number is in IEEE-754 format. + 2. This function support uint64/int64/double number. If an integer number + cannot fit in uint64/int64, it will returns as a double number. If a double + number is infinite, the return value is based on flag. + 3. This function (with inline attribute) may generate a lot of instructions. + */ +static_inline bool read_number(u8 **ptr, + u8 **pre, + yyjson_read_flag flg, + yyjson_val *val, + const char **msg) { + +#define return_err(_pos, _msg) do { \ + *msg = _msg; \ + *end = _pos; \ + return false; \ +} while (false) + +#define return_0() do { \ + val->tag = YYJSON_TYPE_NUM | (u8)((u8)sign << 3); \ + val->uni.u64 = 0; \ + *end = cur; return true; \ +} while (false) + +#define return_i64(_v) do { \ + val->tag = YYJSON_TYPE_NUM | (u8)((u8)sign << 3); \ + val->uni.u64 = (u64)(sign ? (u64)(~(_v) + 1) : (u64)(_v)); \ + *end = cur; return true; \ +} while (false) + +#define return_f64(_v) do { \ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ + val->uni.f64 = sign ? -(f64)(_v) : (f64)(_v); \ + *end = cur; return true; \ +} while (false) + +#define return_f64_bin(_v) do { \ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ + val->uni.u64 = ((u64)sign << 63) | (u64)(_v); \ + *end = cur; return true; \ +} while (false) + +#define return_inf() do { \ + if (has_read_flag(BIGNUM_AS_RAW)) return_raw(); \ + if (has_read_flag(ALLOW_INF_AND_NAN)) return_f64_bin(F64_RAW_INF); \ + else return_err(hdr, "number is infinity when parsed as double"); \ +} while (false) + +#define return_raw() do { \ + if (*pre) **pre = '\0'; /* add null-terminator for previous raw string */ \ + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \ + val->uni.str = (const char *)hdr; \ + *pre = cur; *end = cur; return true; \ +} while (false) + + u8 *sig_cut = NULL; /* significant part cutting position for long number */ + u8 *sig_end = NULL; /* significant part ending position */ + u8 *dot_pos = NULL; /* decimal point position */ + + u64 sig = 0; /* significant part of the number */ + i32 exp = 0; /* exponent part of the number */ + + bool exp_sign; /* temporary exponent sign from literal part */ + i64 exp_sig = 0; /* temporary exponent number from significant part */ + i64 exp_lit = 0; /* temporary exponent number from exponent literal part */ + u64 num; /* temporary number for reading */ + u8 *tmp; /* temporary cursor for reading */ + + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + bool sign; + + /* read number as raw string if has `YYJSON_READ_NUMBER_AS_RAW` flag */ + if (unlikely(pre && !has_read_flag(BIGNUM_AS_RAW))) { + return read_number_raw(ptr, pre, flg, val, msg); + } + + sign = (*hdr == '-'); + cur += sign; + + /* begin with a leading zero or non-digit */ + if (unlikely(!digi_is_nonzero(*cur))) { /* 0 or non-digit char */ + if (unlikely(*cur != '0')) { /* non-digit char */ + if (has_read_flag(ALLOW_INF_AND_NAN)) { + if (read_inf_or_nan(sign, &cur, pre, val)) { + *end = cur; + return true; + } + } + return_err(cur, "no digit after minus sign"); + } + /* begin with 0 */ + if (likely(!digi_is_digit_or_fp(*++cur))) return_0(); + if (likely(*cur == '.')) { + dot_pos = cur++; + if (unlikely(!digi_is_digit(*cur))) { + return_err(cur, "no digit after decimal point"); + } + while (unlikely(*cur == '0')) cur++; + if (likely(digi_is_digit(*cur))) { + /* first non-zero digit after decimal point */ + sig = (u64)(*cur - '0'); /* read first digit */ + cur--; + goto digi_frac_1; /* continue read fraction part */ + } + } + if (unlikely(digi_is_digit(*cur))) { + return_err(cur - 1, "number with leading zero is not allowed"); + } + if (unlikely(digi_is_exp(*cur))) { /* 0 with any exponent is still 0 */ + cur += (usize)1 + digi_is_sign(cur[1]); + if (unlikely(!digi_is_digit(*cur))) { + return_err(cur, "no digit after exponent sign"); + } + while (digi_is_digit(*++cur)); + } + return_f64_bin(0); + } + + /* begin with non-zero digit */ + sig = (u64)(*cur - '0'); + + /* + Read integral part, same as the following code. + + for (int i = 1; i <= 18; i++) { + num = cur[i] - '0'; + if (num <= 9) sig = num + sig * 10; + else goto digi_sepr_i; + } + */ +#define expr_intg(i) \ + if (likely((num = (u64)(cur[i] - (u8)'0')) <= 9)) sig = num + sig * 10; \ + else { goto digi_sepr_##i; } + repeat_in_1_18(expr_intg) +#undef expr_intg + + + cur += 19; /* skip continuous 19 digits */ + if (!digi_is_digit_or_fp(*cur)) { + /* this number is an integer consisting of 19 digits */ + if (sign && (sig > ((u64)1 << 63))) { /* overflow */ + if (has_read_flag(BIGNUM_AS_RAW)) return_raw(); + return_f64(normalized_u64_to_f64(sig)); + } + return_i64(sig); + } + goto digi_intg_more; /* read more digits in integral part */ + + + /* process first non-digit character */ +#define expr_sepr(i) \ + digi_sepr_##i: \ + if (likely(!digi_is_fp(cur[i]))) { cur += i; return_i64(sig); } \ + dot_pos = cur + i; \ + if (likely(cur[i] == '.')) goto digi_frac_##i; \ + cur += i; sig_end = cur; goto digi_exp_more; + repeat_in_1_18(expr_sepr) +#undef expr_sepr + + + /* read fraction part */ +#define expr_frac(i) \ + digi_frac_##i: \ + if (likely((num = (u64)(cur[i + 1] - (u8)'0')) <= 9)) \ + sig = num + sig * 10; \ + else { goto digi_stop_##i; } + repeat_in_1_18(expr_frac) +#undef expr_frac + + cur += 20; /* skip 19 digits and 1 decimal point */ + if (!digi_is_digit(*cur)) goto digi_frac_end; /* fraction part end */ + goto digi_frac_more; /* read more digits in fraction part */ + + + /* significant part end */ +#define expr_stop(i) \ + digi_stop_##i: \ + cur += i + 1; \ + goto digi_frac_end; + repeat_in_1_18(expr_stop) +#undef expr_stop + + + /* read more digits in integral part */ +digi_intg_more: + if (digi_is_digit(*cur)) { + if (!digi_is_digit_or_fp(cur[1])) { + /* this number is an integer consisting of 20 digits */ + num = (u64)(*cur - '0'); + if ((sig < (U64_MAX / 10)) || + (sig == (U64_MAX / 10) && num <= (U64_MAX % 10))) { + sig = num + sig * 10; + cur++; + /* convert to double if overflow */ + if (sign) { + if (has_read_flag(BIGNUM_AS_RAW)) return_raw(); + return_f64(normalized_u64_to_f64(sig)); + } + return_i64(sig); + } + } + } + + if (digi_is_exp(*cur)) { + dot_pos = cur; + goto digi_exp_more; + } + + if (*cur == '.') { + dot_pos = cur++; + if (!digi_is_digit(*cur)) { + return_err(cur, "no digit after decimal point"); + } + } + + + /* read more digits in fraction part */ +digi_frac_more: + sig_cut = cur; /* too large to fit in u64, excess digits need to be cut */ + sig += (*cur >= '5'); /* round */ + while (digi_is_digit(*++cur)); + if (!dot_pos) { + if (!digi_is_fp(*cur) && has_read_flag(BIGNUM_AS_RAW)) { + return_raw(); /* it's a large integer */ + } + dot_pos = cur; + if (*cur == '.') { + if (!digi_is_digit(*++cur)) { + return_err(cur, "no digit after decimal point"); + } + while (digi_is_digit(*cur)) cur++; + } + } + exp_sig = (i64)(dot_pos - sig_cut); + exp_sig += (dot_pos < sig_cut); + + /* ignore trailing zeros */ + tmp = cur - 1; + while (*tmp == '0' || *tmp == '.') tmp--; + if (tmp < sig_cut) { + sig_cut = NULL; + } else { + sig_end = cur; + } + + if (digi_is_exp(*cur)) goto digi_exp_more; + goto digi_exp_finish; + + + /* fraction part end */ +digi_frac_end: + if (unlikely(dot_pos + 1 == cur)) { + return_err(cur, "no digit after decimal point"); + } + sig_end = cur; + exp_sig = -(i64)((u64)(cur - dot_pos) - 1); + if (likely(!digi_is_exp(*cur))) { + if (unlikely(exp_sig < F64_MIN_DEC_EXP - 19)) { + return_f64_bin(0); /* underflow */ + } + exp = (i32)exp_sig; + goto digi_finish; + } else { + goto digi_exp_more; + } + + + /* read exponent part */ +digi_exp_more: + exp_sign = (*++cur == '-'); + cur += digi_is_sign(*cur); + if (unlikely(!digi_is_digit(*cur))) { + return_err(cur, "no digit after exponent sign"); + } + while (*cur == '0') cur++; + + /* read exponent literal */ + tmp = cur; + while (digi_is_digit(*cur)) { + exp_lit = (i64)((u8)(*cur++ - '0') + (u64)exp_lit * 10); + } + if (unlikely(cur - tmp >= U64_SAFE_DIG)) { + if (exp_sign) { + return_f64_bin(0); /* underflow */ + } else { + return_inf(); /* overflow */ + } + } + exp_sig += exp_sign ? -exp_lit : exp_lit; + + + /* validate exponent value */ +digi_exp_finish: + if (unlikely(exp_sig < F64_MIN_DEC_EXP - 19)) { + return_f64_bin(0); /* underflow */ + } + if (unlikely(exp_sig > F64_MAX_DEC_EXP)) { + return_inf(); /* overflow */ + } + exp = (i32)exp_sig; + + + /* all digit read finished */ +digi_finish: + + /* + Fast path 1: + + 1. The floating-point number calculation should be accurate, see the + comments of macro `YYJSON_DOUBLE_MATH_CORRECT`. + 2. Correct rounding should be performed (fegetround() == FE_TONEAREST). + 3. The input of floating point number calculation does not lose precision, + which means: 64 - leading_zero(input) - trailing_zero(input) < 53. + + We don't check all available inputs here, because that would make the code + more complicated, and not friendly to branch predictor. + */ +#if YYJSON_DOUBLE_MATH_CORRECT + if (sig < ((u64)1 << 53) && + exp >= -F64_POW10_EXP_MAX_EXACT && + exp <= +F64_POW10_EXP_MAX_EXACT) { + f64 dbl = (f64)sig; + if (exp < 0) { + dbl /= f64_pow10_table[-exp]; + } else { + dbl *= f64_pow10_table[+exp]; + } + return_f64(dbl); + } +#endif + + /* + Fast path 2: + + To keep it simple, we only accept normal number here, + let the slow path to handle subnormal and infinity number. + */ + if (likely(!sig_cut && + exp > -F64_MAX_DEC_EXP + 1 && + exp < +F64_MAX_DEC_EXP - 20)) { + /* + The result value is exactly equal to (sig * 10^exp), + the exponent part (10^exp) can be converted to (sig2 * 2^exp2). + + The sig2 can be an infinite length number, only the highest 128 bits + is cached in the pow10_sig_table. + + Now we have these bits: + sig1 (normalized 64bit) : aaaaaaaa + sig2 (higher 64bit) : bbbbbbbb + sig2_ext (lower 64bit) : cccccccc + sig2_cut (extra unknown bits) : dddddddddddd.... + + And the calculation process is: + ---------------------------------------- + aaaaaaaa * + bbbbbbbbccccccccdddddddddddd.... + ---------------------------------------- + abababababababab + + acacacacacacacac + + adadadadadadadadadad.... + ---------------------------------------- + [hi____][lo____] + + [hi2___][lo2___] + + [unknown___________....] + ---------------------------------------- + + The addition with carry may affect higher bits, but if there is a 0 + in higher bits, the bits higher than 0 will not be affected. + + `lo2` + `unknown` may get a carry bit and may affect `hi2`, the max + value of `hi2` is 0xFFFFFFFFFFFFFFFE, so `hi2` will not overflow. + + `lo` + `hi2` may also get a carry bit and may affect `hi`, but only + the highest significant 53 bits of `hi` is needed. If there is a 0 + in the lower bits of `hi`, then all the following bits can be dropped. + + To convert the result to IEEE-754 double number, we need to perform + correct rounding: + 1. if bit 54 is 0, round down, + 2. if bit 54 is 1 and any bit beyond bit 54 is 1, round up, + 3. if bit 54 is 1 and all bits beyond bit 54 are 0, round to even, + as the extra bits is unknown, this case will not be handled here. + */ + + u64 raw; + u64 sig1, sig2, sig2_ext, hi, lo, hi2, lo2, add, bits; + i32 exp2; + u32 lz; + bool exact = false, carry, round_up; + + /* convert (10^exp) to (sig2 * 2^exp2) */ + pow10_table_get_sig(exp, &sig2, &sig2_ext); + pow10_table_get_exp(exp, &exp2); + + /* normalize and multiply */ + lz = u64_lz_bits(sig); + sig1 = sig << lz; + exp2 -= (i32)lz; + u128_mul(sig1, sig2, &hi, &lo); + + /* + The `hi` is in range [0x4000000000000000, 0xFFFFFFFFFFFFFFFE], + To get normalized value, `hi` should be shifted to the left by 0 or 1. + + The highest significant 53 bits is used by IEEE-754 double number, + and the bit 54 is used to detect rounding direction. + + The lowest (64 - 54 - 1) bits is used to check whether it contains 0. + */ + bits = hi & (((u64)1 << (64 - 54 - 1)) - 1); + if (bits - 1 < (((u64)1 << (64 - 54 - 1)) - 2)) { + /* + (bits != 0 && bits != 0x1FF) => (bits - 1 < 0x1FF - 1) + The `bits` is not zero, so we don't need to check `round to even` + case. The `bits` contains bit `0`, so we can drop the extra bits + after `0`. + */ + exact = true; + + } else { + /* + (bits == 0 || bits == 0x1FF) + The `bits` is filled with all `0` or all `1`, so we need to check + lower bits with another 64-bit multiplication. + */ + u128_mul(sig1, sig2_ext, &hi2, &lo2); + + add = lo + hi2; + if (add + 1 > (u64)1) { + /* + (add != 0 && add != U64_MAX) => (add + 1 > 1) + The `add` is not zero, so we don't need to check `round to + even` case. The `add` contains bit `0`, so we can drop the + extra bits after `0`. The `hi` cannot be U64_MAX, so it will + not overflow. + */ + carry = add < lo || add < hi2; + hi += carry; + exact = true; + } + } + + if (exact) { + /* normalize */ + lz = hi < ((u64)1 << 63); + hi <<= lz; + exp2 -= (i32)lz; + exp2 += 64; + + /* test the bit 54 and get rounding direction */ + round_up = (hi & ((u64)1 << (64 - 54))) > (u64)0; + hi += (round_up ? ((u64)1 << (64 - 54)) : (u64)0); + + /* test overflow */ + if (hi < ((u64)1 << (64 - 54))) { + hi = ((u64)1 << 63); + exp2 += 1; + } + + /* This is a normal number, convert it to IEEE-754 format. */ + hi >>= F64_BITS - F64_SIG_FULL_BITS; + exp2 += F64_BITS - F64_SIG_FULL_BITS + F64_SIG_BITS; + exp2 += F64_EXP_BIAS; + raw = ((u64)exp2 << F64_SIG_BITS) | (hi & F64_SIG_MASK); + return_f64_bin(raw); + } + } + + /* + Slow path: read double number exactly with diyfp. + 1. Use cached diyfp to get an approximation value. + 2. Use bigcomp to check the approximation value if needed. + + This algorithm refers to google's double-conversion project: + https://github.com/google/double-conversion + */ + { + const i32 ERR_ULP_LOG = 3; + const i32 ERR_ULP = 1 << ERR_ULP_LOG; + const i32 ERR_CACHED_POW = ERR_ULP / 2; + const i32 ERR_MUL_FIXED = ERR_ULP / 2; + const i32 DIY_SIG_BITS = 64; + const i32 EXP_BIAS = F64_EXP_BIAS + F64_SIG_BITS; + const i32 EXP_SUBNORMAL = -EXP_BIAS + 1; + + u64 fp_err; + u32 bits; + i32 order_of_magnitude; + i32 effective_significand_size; + i32 precision_digits_count; + u64 precision_bits; + u64 half_way; + + u64 raw; + diy_fp fp, fp_upper; + bigint big_full, big_comp; + i32 cmp; + + fp.sig = sig; + fp.exp = 0; + fp_err = sig_cut ? (u64)(ERR_ULP / 2) : (u64)0; + + /* normalize */ + bits = u64_lz_bits(fp.sig); + fp.sig <<= bits; + fp.exp -= (i32)bits; + fp_err <<= bits; + + /* multiply and add error */ + fp = diy_fp_mul(fp, diy_fp_get_cached_pow10(exp)); + fp_err += (u64)ERR_CACHED_POW + (fp_err != 0) + (u64)ERR_MUL_FIXED; + + /* normalize */ + bits = u64_lz_bits(fp.sig); + fp.sig <<= bits; + fp.exp -= (i32)bits; + fp_err <<= bits; + + /* effective significand */ + order_of_magnitude = DIY_SIG_BITS + fp.exp; + if (likely(order_of_magnitude >= EXP_SUBNORMAL + F64_SIG_FULL_BITS)) { + effective_significand_size = F64_SIG_FULL_BITS; + } else if (order_of_magnitude <= EXP_SUBNORMAL) { + effective_significand_size = 0; + } else { + effective_significand_size = order_of_magnitude - EXP_SUBNORMAL; + } + + /* precision digits count */ + precision_digits_count = DIY_SIG_BITS - effective_significand_size; + if (unlikely(precision_digits_count + ERR_ULP_LOG >= DIY_SIG_BITS)) { + i32 shr = (precision_digits_count + ERR_ULP_LOG) - DIY_SIG_BITS + 1; + fp.sig >>= shr; + fp.exp += shr; + fp_err = (fp_err >> shr) + 1 + (u32)ERR_ULP; + precision_digits_count -= shr; + } + + /* half way */ + precision_bits = fp.sig & (((u64)1 << precision_digits_count) - 1); + precision_bits *= (u32)ERR_ULP; + half_way = (u64)1 << (precision_digits_count - 1); + half_way *= (u32)ERR_ULP; + + /* rounding */ + fp.sig >>= precision_digits_count; + fp.sig += (precision_bits >= half_way + fp_err); + fp.exp += precision_digits_count; + + /* get IEEE double raw value */ + raw = diy_fp_to_ieee_raw(fp); + if (unlikely(raw == F64_RAW_INF)) return_inf(); + if (likely(precision_bits <= half_way - fp_err || + precision_bits >= half_way + fp_err)) { + return_f64_bin(raw); /* number is accurate */ + } + /* now the number is the correct value, or the next lower value */ + + /* upper boundary */ + if (raw & F64_EXP_MASK) { + fp_upper.sig = (raw & F64_SIG_MASK) + ((u64)1 << F64_SIG_BITS); + fp_upper.exp = (i32)((raw & F64_EXP_MASK) >> F64_SIG_BITS); + } else { + fp_upper.sig = (raw & F64_SIG_MASK); + fp_upper.exp = 1; + } + fp_upper.exp -= F64_EXP_BIAS + F64_SIG_BITS; + fp_upper.sig <<= 1; + fp_upper.exp -= 1; + fp_upper.sig += 1; /* add half ulp */ + + /* compare with bigint */ + bigint_set_buf(&big_full, sig, &exp, sig_cut, sig_end, dot_pos); + bigint_set_u64(&big_comp, fp_upper.sig); + if (exp >= 0) { + bigint_mul_pow10(&big_full, +exp); + } else { + bigint_mul_pow10(&big_comp, -exp); + } + if (fp_upper.exp > 0) { + bigint_mul_pow2(&big_comp, (u32)+fp_upper.exp); + } else { + bigint_mul_pow2(&big_full, (u32)-fp_upper.exp); + } + cmp = bigint_cmp(&big_full, &big_comp); + if (likely(cmp != 0)) { + /* round down or round up */ + raw += (cmp > 0); + } else { + /* falls midway, round to even */ + raw += (raw & 1); + } + + if (unlikely(raw == F64_RAW_INF)) return_inf(); + return_f64_bin(raw); + } + +#undef return_err +#undef return_inf +#undef return_0 +#undef return_i64 +#undef return_f64 +#undef return_f64_bin +#undef return_raw +} + + + +#else /* FP_READER */ + +/** + Read a JSON number. + This is a fallback function if the custom number reader is disabled. + This function use libc's strtod() to read floating-point number. + */ +static_inline bool read_number(u8 **ptr, + u8 **pre, + yyjson_read_flag flg, + yyjson_val *val, + const char **msg) { + +#define return_err(_pos, _msg) do { \ + *msg = _msg; \ + *end = _pos; \ + return false; \ +} while (false) + +#define return_0() do { \ + val->tag = YYJSON_TYPE_NUM | (u64)((u8)sign << 3); \ + val->uni.u64 = 0; \ + *end = cur; return true; \ +} while (false) + +#define return_i64(_v) do { \ + val->tag = YYJSON_TYPE_NUM | (u64)((u8)sign << 3); \ + val->uni.u64 = (u64)(sign ? (u64)(~(_v) + 1) : (u64)(_v)); \ + *end = cur; return true; \ +} while (false) + +#define return_f64(_v) do { \ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ + val->uni.f64 = sign ? -(f64)(_v) : (f64)(_v); \ + *end = cur; return true; \ +} while (false) + +#define return_f64_bin(_v) do { \ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ + val->uni.u64 = ((u64)sign << 63) | (u64)(_v); \ + *end = cur; return true; \ +} while (false) + +#define return_inf() do { \ + if (has_read_flag(BIGNUM_AS_RAW)) return_raw(); \ + if (has_read_flag(ALLOW_INF_AND_NAN)) return_f64_bin(F64_RAW_INF); \ + else return_err(hdr, "number is infinity when parsed as double"); \ +} while (false) + +#define return_raw() do { \ + if (*pre) **pre = '\0'; /* add null-terminator for previous raw string */ \ + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \ + val->uni.str = (const char *)hdr; \ + *pre = cur; *end = cur; return true; \ +} while (false) + + u64 sig, num; + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + u8 *dot = NULL; + u8 *f64_end = NULL; + bool sign; + + /* read number as raw string if has `YYJSON_READ_NUMBER_AS_RAW` flag */ + if (unlikely(pre && !has_read_flag(BIGNUM_AS_RAW))) { + return read_number_raw(ptr, pre, flg, val, msg); + } + + sign = (*hdr == '-'); + cur += sign; + sig = (u8)(*cur - '0'); + + /* read first digit, check leading zero */ + if (unlikely(!digi_is_digit(*cur))) { + if (has_read_flag(ALLOW_INF_AND_NAN)) { + if (read_inf_or_nan(sign, &cur, pre, val)) { + *end = cur; + return true; + } + } + return_err(cur, "no digit after minus sign"); + } + if (*cur == '0') { + cur++; + if (unlikely(digi_is_digit(*cur))) { + return_err(cur - 1, "number with leading zero is not allowed"); + } + if (!digi_is_fp(*cur)) return_0(); + goto read_double; + } + + /* read continuous digits, up to 19 characters */ +#define expr_intg(i) \ + if (likely((num = (u64)(cur[i] - (u8)'0')) <= 9)) sig = num + sig * 10; \ + else { cur += i; goto intg_end; } + repeat_in_1_18(expr_intg) +#undef expr_intg + + /* here are 19 continuous digits, skip them */ + cur += 19; + if (digi_is_digit(cur[0]) && !digi_is_digit_or_fp(cur[1])) { + /* this number is an integer consisting of 20 digits */ + num = (u8)(*cur - '0'); + if ((sig < (U64_MAX / 10)) || + (sig == (U64_MAX / 10) && num <= (U64_MAX % 10))) { + sig = num + sig * 10; + cur++; + if (sign) { + if (has_read_flag(BIGNUM_AS_RAW)) return_raw(); + return_f64(normalized_u64_to_f64(sig)); + } + return_i64(sig); + } + } + +intg_end: + /* continuous digits ended */ + if (!digi_is_digit_or_fp(*cur)) { + /* this number is an integer consisting of 1 to 19 digits */ + if (sign && (sig > ((u64)1 << 63))) { + if (has_read_flag(BIGNUM_AS_RAW)) return_raw(); + return_f64(normalized_u64_to_f64(sig)); + } + return_i64(sig); + } + +read_double: + /* this number should be read as double */ + while (digi_is_digit(*cur)) cur++; + if (!digi_is_fp(*cur) && has_read_flag(BIGNUM_AS_RAW)) { + return_raw(); /* it's a large integer */ + } + if (*cur == '.') { + /* skip fraction part */ + dot = cur; + cur++; + if (!digi_is_digit(*cur)) { + return_err(cur, "no digit after decimal point"); + } + cur++; + while (digi_is_digit(*cur)) cur++; + } + if (digi_is_exp(*cur)) { + /* skip exponent part */ + cur += 1 + digi_is_sign(cur[1]); + if (!digi_is_digit(*cur)) { + return_err(cur, "no digit after exponent sign"); + } + cur++; + while (digi_is_digit(*cur)) cur++; + } + + /* + libc's strtod() is used to parse the floating-point number. + + Note that the decimal point character used by strtod() is locale-dependent, + and the rounding direction may affected by fesetround(). + + For currently known locales, (en, zh, ja, ko, am, he, hi) use '.' as the + decimal point, while other locales use ',' as the decimal point. + + Here strtod() is called twice for different locales, but if another thread + happens calls setlocale() between two strtod(), parsing may still fail. + */ + val->uni.f64 = strtod((const char *)hdr, (char **)&f64_end); + if (unlikely(f64_end != cur)) { + /* replace '.' with ',' for locale */ + bool cut = (*cur == ','); + if (cut) *cur = ' '; + if (dot) *dot = ','; + val->uni.f64 = strtod((const char *)hdr, (char **)&f64_end); + /* restore ',' to '.' */ + if (cut) *cur = ','; + if (dot) *dot = '.'; + if (unlikely(f64_end != cur)) { + return_err(hdr, "strtod() failed to parse the number"); + } + } + if (unlikely(val->uni.f64 >= HUGE_VAL || val->uni.f64 <= -HUGE_VAL)) { + return_inf(); + } + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + *end = cur; + return true; + +#undef return_err +#undef return_0 +#undef return_i64 +#undef return_f64 +#undef return_f64_bin +#undef return_inf +#undef return_raw +} + +#endif /* FP_READER */ + + + +/*============================================================================== + * JSON String Reader + *============================================================================*/ + +/** + Read a JSON string. + @param ptr The head pointer of string before '"' prefix (inout). + @param lst JSON last position. + @param inv Allow invalid unicode. + @param val The string value to be written. + @param msg The error message pointer. + @return Whether success. + */ +static_inline bool read_string(u8 **ptr, + u8 *lst, + bool inv, + yyjson_val *val, + const char **msg) { + /* + Each unicode code point is encoded as 1 to 4 bytes in UTF-8 encoding, + we use 4-byte mask and pattern value to validate UTF-8 byte sequence, + this requires the input data to have 4-byte zero padding. + --------------------------------------------------- + 1 byte + unicode range [U+0000, U+007F] + unicode min [.......0] + unicode max [.1111111] + bit pattern [0.......] + --------------------------------------------------- + 2 byte + unicode range [U+0080, U+07FF] + unicode min [......10 ..000000] + unicode max [...11111 ..111111] + bit require [...xxxx. ........] (1E 00) + bit mask [xxx..... xx......] (E0 C0) + bit pattern [110..... 10......] (C0 80) + --------------------------------------------------- + 3 byte + unicode range [U+0800, U+FFFF] + unicode min [........ ..100000 ..000000] + unicode max [....1111 ..111111 ..111111] + bit require [....xxxx ..x..... ........] (0F 20 00) + bit mask [xxxx.... xx...... xx......] (F0 C0 C0) + bit pattern [1110.... 10...... 10......] (E0 80 80) + --------------------------------------------------- + 3 byte invalid (reserved for surrogate halves) + unicode range [U+D800, U+DFFF] + unicode min [....1101 ..100000 ..000000] + unicode max [....1101 ..111111 ..111111] + bit mask [....xxxx ..x..... ........] (0F 20 00) + bit pattern [....1101 ..1..... ........] (0D 20 00) + --------------------------------------------------- + 4 byte + unicode range [U+10000, U+10FFFF] + unicode min [........ ...10000 ..000000 ..000000] + unicode max [.....100 ..001111 ..111111 ..111111] + bit require [.....xxx ..xx.... ........ ........] (07 30 00 00) + bit mask [xxxxx... xx...... xx...... xx......] (F8 C0 C0 C0) + bit pattern [11110... 10...... 10...... 10......] (F0 80 80 80) + --------------------------------------------------- + */ +#if YYJSON_ENDIAN == YYJSON_BIG_ENDIAN + const u32 b1_mask = 0x80000000UL; + const u32 b1_patt = 0x00000000UL; + const u32 b2_mask = 0xE0C00000UL; + const u32 b2_patt = 0xC0800000UL; + const u32 b2_requ = 0x1E000000UL; + const u32 b3_mask = 0xF0C0C000UL; + const u32 b3_patt = 0xE0808000UL; + const u32 b3_requ = 0x0F200000UL; + const u32 b3_erro = 0x0D200000UL; + const u32 b4_mask = 0xF8C0C0C0UL; + const u32 b4_patt = 0xF0808080UL; + const u32 b4_requ = 0x07300000UL; + const u32 b4_err0 = 0x04000000UL; + const u32 b4_err1 = 0x03300000UL; +#elif YYJSON_ENDIAN == YYJSON_LITTLE_ENDIAN + const u32 b1_mask = 0x00000080UL; + const u32 b1_patt = 0x00000000UL; + const u32 b2_mask = 0x0000C0E0UL; + const u32 b2_patt = 0x000080C0UL; + const u32 b2_requ = 0x0000001EUL; + const u32 b3_mask = 0x00C0C0F0UL; + const u32 b3_patt = 0x008080E0UL; + const u32 b3_requ = 0x0000200FUL; + const u32 b3_erro = 0x0000200DUL; + const u32 b4_mask = 0xC0C0C0F8UL; + const u32 b4_patt = 0x808080F0UL; + const u32 b4_requ = 0x00003007UL; + const u32 b4_err0 = 0x00000004UL; + const u32 b4_err1 = 0x00003003UL; +#else + /* this should be evaluated at compile-time */ + v32_uni b1_mask_uni = {{ 0x80, 0x00, 0x00, 0x00 }}; + v32_uni b1_patt_uni = {{ 0x00, 0x00, 0x00, 0x00 }}; + v32_uni b2_mask_uni = {{ 0xE0, 0xC0, 0x00, 0x00 }}; + v32_uni b2_patt_uni = {{ 0xC0, 0x80, 0x00, 0x00 }}; + v32_uni b2_requ_uni = {{ 0x1E, 0x00, 0x00, 0x00 }}; + v32_uni b3_mask_uni = {{ 0xF0, 0xC0, 0xC0, 0x00 }}; + v32_uni b3_patt_uni = {{ 0xE0, 0x80, 0x80, 0x00 }}; + v32_uni b3_requ_uni = {{ 0x0F, 0x20, 0x00, 0x00 }}; + v32_uni b3_erro_uni = {{ 0x0D, 0x20, 0x00, 0x00 }}; + v32_uni b4_mask_uni = {{ 0xF8, 0xC0, 0xC0, 0xC0 }}; + v32_uni b4_patt_uni = {{ 0xF0, 0x80, 0x80, 0x80 }}; + v32_uni b4_requ_uni = {{ 0x07, 0x30, 0x00, 0x00 }}; + v32_uni b4_err0_uni = {{ 0x04, 0x00, 0x00, 0x00 }}; + v32_uni b4_err1_uni = {{ 0x03, 0x30, 0x00, 0x00 }}; + u32 b1_mask = b1_mask_uni.u; + u32 b1_patt = b1_patt_uni.u; + u32 b2_mask = b2_mask_uni.u; + u32 b2_patt = b2_patt_uni.u; + u32 b2_requ = b2_requ_uni.u; + u32 b3_mask = b3_mask_uni.u; + u32 b3_patt = b3_patt_uni.u; + u32 b3_requ = b3_requ_uni.u; + u32 b3_erro = b3_erro_uni.u; + u32 b4_mask = b4_mask_uni.u; + u32 b4_patt = b4_patt_uni.u; + u32 b4_requ = b4_requ_uni.u; + u32 b4_err0 = b4_err0_uni.u; + u32 b4_err1 = b4_err1_uni.u; +#endif + +#define is_valid_seq_1(uni) ( \ + ((uni & b1_mask) == b1_patt) \ +) + +#define is_valid_seq_2(uni) ( \ + ((uni & b2_mask) == b2_patt) && \ + ((uni & b2_requ)) \ +) + +#define is_valid_seq_3(uni) ( \ + ((uni & b3_mask) == b3_patt) && \ + ((tmp = (uni & b3_requ))) && \ + ((tmp != b3_erro)) \ +) + +#define is_valid_seq_4(uni) ( \ + ((uni & b4_mask) == b4_patt) && \ + ((tmp = (uni & b4_requ))) && \ + ((tmp & b4_err0) == 0 || (tmp & b4_err1) == 0) \ +) + +#define return_err(_end, _msg) do { \ + *msg = _msg; \ + *end = _end; \ + return false; \ +} while (false) + + u8 *cur = *ptr; + u8 **end = ptr; + u8 *src = ++cur, *dst, *pos; + u16 hi, lo; + u32 uni, tmp; + +skip_ascii: + /* Most strings have no escaped characters, so we can jump them quickly. */ + +skip_ascii_begin: + /* + We want to make loop unrolling, as shown in the following code. Some + compiler may not generate instructions as expected, so we rewrite it with + explicit goto statements. We hope the compiler can generate instructions + like this: https://godbolt.org/z/8vjsYq + + while (true) repeat16({ + if (likely(!(char_is_ascii_stop(*src)))) src++; + else break; + }) + */ +#define expr_jump(i) \ + if (likely(!char_is_ascii_stop(src[i]))) {} \ + else goto skip_ascii_stop##i; + +#define expr_stop(i) \ + skip_ascii_stop##i: \ + src += i; \ + goto skip_ascii_end; + + repeat16_incr(expr_jump) + src += 16; + goto skip_ascii_begin; + repeat16_incr(expr_stop) + +#undef expr_jump +#undef expr_stop + +skip_ascii_end: + + /* + GCC may store src[i] in a register at each line of expr_jump(i) above. + These instructions are useless and will degrade performance. + This inline asm is a hint for gcc: "the memory has been modified, + do not cache it". + + MSVC, Clang, ICC can generate expected instructions without this hint. + */ +#if YYJSON_IS_REAL_GCC + __asm__ volatile("":"=m"(*src)); +#endif + if (likely(*src == '"')) { + val->tag = ((u64)(src - cur) << YYJSON_TAG_BIT) | + (u64)(YYJSON_TYPE_STR | YYJSON_SUBTYPE_NOESC); + val->uni.str = (const char *)cur; + *src = '\0'; + *end = src + 1; + return true; + } + +skip_utf8: + if (*src & 0x80) { /* non-ASCII character */ + /* + Non-ASCII character appears here, which means that the text is likely + to be written in non-English or emoticons. According to some common + data set statistics, byte sequences of the same length may appear + consecutively. We process the byte sequences of the same length in each + loop, which is more friendly to branch prediction. + */ + pos = src; +#if YYJSON_DISABLE_UTF8_VALIDATION + while (true) repeat8({ + if (likely((*src & 0xF0) == 0xE0)) src += 3; + else break; + }) + if (*src < 0x80) goto skip_ascii; + while (true) repeat8({ + if (likely((*src & 0xE0) == 0xC0)) src += 2; + else break; + }) + while (true) repeat8({ + if (likely((*src & 0xF8) == 0xF0)) src += 4; + else break; + }) +#else + uni = byte_load_4(src); + while (is_valid_seq_3(uni)) { + src += 3; + uni = byte_load_4(src); + } + if (is_valid_seq_1(uni)) goto skip_ascii; + while (is_valid_seq_2(uni)) { + src += 2; + uni = byte_load_4(src); + } + while (is_valid_seq_4(uni)) { + src += 4; + uni = byte_load_4(src); + } +#endif + if (unlikely(pos == src)) { + if (!inv) return_err(src, "invalid UTF-8 encoding in string"); + ++src; + } + goto skip_ascii; + } + + /* The escape character appears, we need to copy it. */ + dst = src; +copy_escape: + if (likely(*src == '\\')) { + switch (*++src) { + case '"': *dst++ = '"'; src++; break; + case '\\': *dst++ = '\\'; src++; break; + case '/': *dst++ = '/'; src++; break; + case 'b': *dst++ = '\b'; src++; break; + case 'f': *dst++ = '\f'; src++; break; + case 'n': *dst++ = '\n'; src++; break; + case 'r': *dst++ = '\r'; src++; break; + case 't': *dst++ = '\t'; src++; break; + case 'u': + if (unlikely(!read_hex_u16(++src, &hi))) { + return_err(src - 2, "invalid escaped sequence in string"); + } + src += 4; + if (likely((hi & 0xF800) != 0xD800)) { + /* a BMP character */ + if (hi >= 0x800) { + *dst++ = (u8)(0xE0 | (hi >> 12)); + *dst++ = (u8)(0x80 | ((hi >> 6) & 0x3F)); + *dst++ = (u8)(0x80 | (hi & 0x3F)); + } else if (hi >= 0x80) { + *dst++ = (u8)(0xC0 | (hi >> 6)); + *dst++ = (u8)(0x80 | (hi & 0x3F)); + } else { + *dst++ = (u8)hi; + } + } else { + /* a non-BMP character, represented as a surrogate pair */ + if (unlikely((hi & 0xFC00) != 0xD800)) { + return_err(src - 6, "invalid high surrogate in string"); + } + if (unlikely(!byte_match_2(src, "\\u"))) { + return_err(src, "no low surrogate in string"); + } + if (unlikely(!read_hex_u16(src + 2, &lo))) { + return_err(src, "invalid escaped sequence in string"); + } + if (unlikely((lo & 0xFC00) != 0xDC00)) { + return_err(src, "invalid low surrogate in string"); + } + uni = ((((u32)hi - 0xD800) << 10) | + ((u32)lo - 0xDC00)) + 0x10000; + *dst++ = (u8)(0xF0 | (uni >> 18)); + *dst++ = (u8)(0x80 | ((uni >> 12) & 0x3F)); + *dst++ = (u8)(0x80 | ((uni >> 6) & 0x3F)); + *dst++ = (u8)(0x80 | (uni & 0x3F)); + src += 6; + } + break; + default: return_err(src, "invalid escaped character in string"); + } + } else if (likely(*src == '"')) { + val->tag = ((u64)(dst - cur) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = (const char *)cur; + *dst = '\0'; + *end = src + 1; + return true; + } else { + if (!inv) return_err(src, "unexpected control character in string"); + if (src >= lst) return_err(src, "unclosed string"); + *dst++ = *src++; + } + +copy_ascii: + /* + Copy continuous ASCII, loop unrolling, same as the following code: + + while (true) repeat16({ + if (unlikely(char_is_ascii_stop(*src))) break; + *dst++ = *src++; + }) + */ +#if YYJSON_IS_REAL_GCC +# define expr_jump(i) \ + if (likely(!(char_is_ascii_stop(src[i])))) {} \ + else { __asm__ volatile("":"=m"(src[i])); goto copy_ascii_stop_##i; } +#else +# define expr_jump(i) \ + if (likely(!(char_is_ascii_stop(src[i])))) {} \ + else { goto copy_ascii_stop_##i; } +#endif + repeat16_incr(expr_jump) +#undef expr_jump + + byte_move_16(dst, src); + src += 16; + dst += 16; + goto copy_ascii; + + /* + The memory will be moved forward by at least 1 byte. So the `byte_move` + can be one byte more than needed to reduce the number of instructions. + */ +copy_ascii_stop_0: + goto copy_utf8; +copy_ascii_stop_1: + byte_move_2(dst, src); + src += 1; + dst += 1; + goto copy_utf8; +copy_ascii_stop_2: + byte_move_2(dst, src); + src += 2; + dst += 2; + goto copy_utf8; +copy_ascii_stop_3: + byte_move_4(dst, src); + src += 3; + dst += 3; + goto copy_utf8; +copy_ascii_stop_4: + byte_move_4(dst, src); + src += 4; + dst += 4; + goto copy_utf8; +copy_ascii_stop_5: + byte_move_4(dst, src); + byte_move_2(dst + 4, src + 4); + src += 5; + dst += 5; + goto copy_utf8; +copy_ascii_stop_6: + byte_move_4(dst, src); + byte_move_2(dst + 4, src + 4); + src += 6; + dst += 6; + goto copy_utf8; +copy_ascii_stop_7: + byte_move_8(dst, src); + src += 7; + dst += 7; + goto copy_utf8; +copy_ascii_stop_8: + byte_move_8(dst, src); + src += 8; + dst += 8; + goto copy_utf8; +copy_ascii_stop_9: + byte_move_8(dst, src); + byte_move_2(dst + 8, src + 8); + src += 9; + dst += 9; + goto copy_utf8; +copy_ascii_stop_10: + byte_move_8(dst, src); + byte_move_2(dst + 8, src + 8); + src += 10; + dst += 10; + goto copy_utf8; +copy_ascii_stop_11: + byte_move_8(dst, src); + byte_move_4(dst + 8, src + 8); + src += 11; + dst += 11; + goto copy_utf8; +copy_ascii_stop_12: + byte_move_8(dst, src); + byte_move_4(dst + 8, src + 8); + src += 12; + dst += 12; + goto copy_utf8; +copy_ascii_stop_13: + byte_move_8(dst, src); + byte_move_4(dst + 8, src + 8); + byte_move_2(dst + 12, src + 12); + src += 13; + dst += 13; + goto copy_utf8; +copy_ascii_stop_14: + byte_move_8(dst, src); + byte_move_4(dst + 8, src + 8); + byte_move_2(dst + 12, src + 12); + src += 14; + dst += 14; + goto copy_utf8; +copy_ascii_stop_15: + byte_move_16(dst, src); + src += 15; + dst += 15; + goto copy_utf8; + +copy_utf8: + if (*src & 0x80) { /* non-ASCII character */ + pos = src; + uni = byte_load_4(src); +#if YYJSON_DISABLE_UTF8_VALIDATION + while (true) repeat4({ + if ((uni & b3_mask) == b3_patt) { + byte_copy_4(dst, &uni); + dst += 3; + src += 3; + uni = byte_load_4(src); + } else break; + }) + if ((uni & b1_mask) == b1_patt) goto copy_ascii; + while (true) repeat4({ + if ((uni & b2_mask) == b2_patt) { + byte_copy_2(dst, &uni); + dst += 2; + src += 2; + uni = byte_load_4(src); + } else break; + }) + while (true) repeat4({ + if ((uni & b4_mask) == b4_patt) { + byte_copy_4(dst, &uni); + dst += 4; + src += 4; + uni = byte_load_4(src); + } else break; + }) +#else + while (is_valid_seq_3(uni)) { + byte_copy_4(dst, &uni); + dst += 3; + src += 3; + uni = byte_load_4(src); + } + if (is_valid_seq_1(uni)) goto copy_ascii; + while (is_valid_seq_2(uni)) { + byte_copy_2(dst, &uni); + dst += 2; + src += 2; + uni = byte_load_4(src); + } + while (is_valid_seq_4(uni)) { + byte_copy_4(dst, &uni); + dst += 4; + src += 4; + uni = byte_load_4(src); + } +#endif + if (unlikely(pos == src)) { + if (!inv) return_err(src, "invalid UTF-8 encoding in string"); + goto copy_ascii_stop_1; + } + goto copy_ascii; + } + goto copy_escape; + +#undef return_err +#undef is_valid_seq_1 +#undef is_valid_seq_2 +#undef is_valid_seq_3 +#undef is_valid_seq_4 +} + + + +/*============================================================================== + * JSON Reader Implementation + * + * We use goto statements to build the finite state machine (FSM). + * The FSM's state was held by program counter (PC) and the 'goto' make the + * state transitions. + *============================================================================*/ + +/** Read single value JSON document. */ +static_noinline yyjson_doc *read_root_single(u8 *hdr, + u8 *cur, + u8 *end, + yyjson_alc alc, + yyjson_read_flag flg, + yyjson_read_err *err) { + +#define return_err(_pos, _code, _msg) do { \ + if (is_truncated_end(hdr, _pos, end, YYJSON_READ_ERROR_##_code, flg)) { \ + err->pos = (usize)(end - hdr); \ + err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \ + err->msg = "unexpected end of data"; \ + } else { \ + err->pos = (usize)(_pos - hdr); \ + err->code = YYJSON_READ_ERROR_##_code; \ + err->msg = _msg; \ + } \ + if (val_hdr) alc.free(alc.ctx, (void *)val_hdr); \ + return NULL; \ +} while (false) + + usize hdr_len; /* value count used by doc */ + usize alc_num; /* value count capacity */ + yyjson_val *val_hdr; /* the head of allocated values */ + yyjson_val *val; /* current value */ + yyjson_doc *doc; /* the JSON document, equals to val_hdr */ + const char *msg; /* error message */ + + bool raw; /* read number as raw */ + bool inv; /* allow invalid unicode */ + u8 *raw_end; /* raw end for null-terminator */ + u8 **pre; /* previous raw end pointer */ + + hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); + hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; + alc_num = hdr_len + 1; /* single value */ + + val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_num * sizeof(yyjson_val)); + if (unlikely(!val_hdr)) goto fail_alloc; + val = val_hdr + hdr_len; + raw = has_read_flag(NUMBER_AS_RAW) || has_read_flag(BIGNUM_AS_RAW); + inv = has_read_flag(ALLOW_INVALID_UNICODE) != 0; + raw_end = NULL; + pre = raw ? &raw_end : NULL; + + if (char_is_number(*cur)) { + if (likely(read_number(&cur, pre, flg, val, &msg))) goto doc_end; + goto fail_number; + } + if (*cur == '"') { + if (likely(read_string(&cur, end, inv, val, &msg))) goto doc_end; + goto fail_string; + } + if (*cur == 't') { + if (likely(read_true(&cur, val))) goto doc_end; + goto fail_literal; + } + if (*cur == 'f') { + if (likely(read_false(&cur, val))) goto doc_end; + goto fail_literal; + } + if (*cur == 'n') { + if (likely(read_null(&cur, val))) goto doc_end; + if (has_read_flag(ALLOW_INF_AND_NAN)) { + if (read_nan(false, &cur, pre, val)) goto doc_end; + } + goto fail_literal; + } + if (has_read_flag(ALLOW_INF_AND_NAN)) { + if (read_inf_or_nan(false, &cur, pre, val)) goto doc_end; + } + goto fail_character; + +doc_end: + /* check invalid contents after json document */ + if (unlikely(cur < end) && !has_read_flag(STOP_WHEN_DONE)) { + if (has_read_flag(ALLOW_COMMENTS)) { + if (!skip_spaces_and_comments(&cur)) { + if (byte_match_2(cur, "/*")) goto fail_comment; + } + } else { + while (char_is_space(*cur)) cur++; + } + if (unlikely(cur < end)) goto fail_garbage; + } + + if (pre && *pre) **pre = '\0'; + doc = (yyjson_doc *)val_hdr; + doc->root = val_hdr + hdr_len; + doc->alc = alc; + doc->dat_read = (usize)(cur - hdr); + doc->val_read = 1; + doc->str_pool = has_read_flag(INSITU) ? NULL : (char *)hdr; + return doc; + +fail_string: + return_err(cur, INVALID_STRING, msg); +fail_number: + return_err(cur, INVALID_NUMBER, msg); +fail_alloc: + return_err(cur, MEMORY_ALLOCATION, "memory allocation failed"); +fail_literal: + return_err(cur, LITERAL, "invalid literal"); +fail_comment: + return_err(cur, INVALID_COMMENT, "unclosed multiline comment"); +fail_character: + return_err(cur, UNEXPECTED_CHARACTER, "unexpected character"); +fail_garbage: + return_err(cur, UNEXPECTED_CONTENT, "unexpected content after document"); + +#undef return_err +} + +/** Read JSON document (accept all style, but optimized for minify). */ +static_inline yyjson_doc *read_root_minify(u8 *hdr, + u8 *cur, + u8 *end, + yyjson_alc alc, + yyjson_read_flag flg, + yyjson_read_err *err) { + +#define return_err(_pos, _code, _msg) do { \ + if (is_truncated_end(hdr, _pos, end, YYJSON_READ_ERROR_##_code, flg)) { \ + err->pos = (usize)(end - hdr); \ + err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \ + err->msg = "unexpected end of data"; \ + } else { \ + err->pos = (usize)(_pos - hdr); \ + err->code = YYJSON_READ_ERROR_##_code; \ + err->msg = _msg; \ + } \ + if (val_hdr) alc.free(alc.ctx, (void *)val_hdr); \ + return NULL; \ +} while (false) + +#define val_incr() do { \ + val++; \ + if (unlikely(val >= val_end)) { \ + usize alc_old = alc_len; \ + alc_len += alc_len / 2; \ + if ((sizeof(usize) < 8) && (alc_len >= alc_max)) goto fail_alloc; \ + val_tmp = (yyjson_val *)alc.realloc(alc.ctx, (void *)val_hdr, \ + alc_old * sizeof(yyjson_val), \ + alc_len * sizeof(yyjson_val)); \ + if ((!val_tmp)) goto fail_alloc; \ + val = val_tmp + (usize)(val - val_hdr); \ + ctn = val_tmp + (usize)(ctn - val_hdr); \ + val_hdr = val_tmp; \ + val_end = val_tmp + (alc_len - 2); \ + } \ +} while (false) + + usize dat_len; /* data length in bytes, hint for allocator */ + usize hdr_len; /* value count used by yyjson_doc */ + usize alc_len; /* value count allocated */ + usize alc_max; /* maximum value count for allocator */ + usize ctn_len; /* the number of elements in current container */ + yyjson_val *val_hdr; /* the head of allocated values */ + yyjson_val *val_end; /* the end of allocated values */ + yyjson_val *val_tmp; /* temporary pointer for realloc */ + yyjson_val *val; /* current JSON value */ + yyjson_val *ctn; /* current container */ + yyjson_val *ctn_parent; /* parent of current container */ + yyjson_doc *doc; /* the JSON document, equals to val_hdr */ + const char *msg; /* error message */ + + bool raw; /* read number as raw */ + bool inv; /* allow invalid unicode */ + u8 *raw_end; /* raw end for null-terminator */ + u8 **pre; /* previous raw end pointer */ + + dat_len = has_read_flag(STOP_WHEN_DONE) ? 256 : (usize)(end - cur); + hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); + hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; + alc_max = USIZE_MAX / sizeof(yyjson_val); + alc_len = hdr_len + (dat_len / YYJSON_READER_ESTIMATED_MINIFY_RATIO) + 4; + alc_len = yyjson_min(alc_len, alc_max); + + val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_len * sizeof(yyjson_val)); + if (unlikely(!val_hdr)) goto fail_alloc; + val_end = val_hdr + (alc_len - 2); /* padding for key-value pair reading */ + val = val_hdr + hdr_len; + ctn = val; + ctn_len = 0; + raw = has_read_flag(NUMBER_AS_RAW) || has_read_flag(BIGNUM_AS_RAW); + inv = has_read_flag(ALLOW_INVALID_UNICODE) != 0; + raw_end = NULL; + pre = raw ? &raw_end : NULL; + + if (*cur++ == '{') { + ctn->tag = YYJSON_TYPE_OBJ; + ctn->uni.ofs = 0; + goto obj_key_begin; + } else { + ctn->tag = YYJSON_TYPE_ARR; + ctn->uni.ofs = 0; + goto arr_val_begin; + } + +arr_begin: + /* save current container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + + /* create a new array value, save parent container offset */ + val_incr(); + val->tag = YYJSON_TYPE_ARR; + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + + /* push the new array value as current container */ + ctn = val; + ctn_len = 0; + +arr_val_begin: + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (char_is_number(*cur)) { + val_incr(); + ctn_len++; + if (likely(read_number(&cur, pre, flg, val, &msg))) goto arr_val_end; + goto fail_number; + } + if (*cur == '"') { + val_incr(); + ctn_len++; + if (likely(read_string(&cur, end, inv, val, &msg))) goto arr_val_end; + goto fail_string; + } + if (*cur == 't') { + val_incr(); + ctn_len++; + if (likely(read_true(&cur, val))) goto arr_val_end; + goto fail_literal; + } + if (*cur == 'f') { + val_incr(); + ctn_len++; + if (likely(read_false(&cur, val))) goto arr_val_end; + goto fail_literal; + } + if (*cur == 'n') { + val_incr(); + ctn_len++; + if (likely(read_null(&cur, val))) goto arr_val_end; + if (has_read_flag(ALLOW_INF_AND_NAN)) { + if (read_nan(false, &cur, pre, val)) goto arr_val_end; + } + goto fail_literal; + } + if (*cur == ']') { + cur++; + if (likely(ctn_len == 0)) goto arr_end; + if (has_read_flag(ALLOW_TRAILING_COMMAS)) goto arr_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_begin; + } + if (has_read_flag(ALLOW_INF_AND_NAN) && + (*cur == 'i' || *cur == 'I' || *cur == 'N')) { + val_incr(); + ctn_len++; + if (read_inf_or_nan(false, &cur, pre, val)) goto arr_val_end; + goto fail_character; + } + if (has_read_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto arr_val_begin; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +arr_val_end: + if (*cur == ',') { + cur++; + goto arr_val_begin; + } + if (*cur == ']') { + cur++; + goto arr_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_end; + } + if (has_read_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto arr_val_end; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +arr_end: + /* get parent container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + + /* save the next sibling value offset */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = ((ctn_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; + if (unlikely(ctn == ctn_parent)) goto doc_end; + + /* pop parent as current container */ + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +obj_begin: + /* push container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + val_incr(); + val->tag = YYJSON_TYPE_OBJ; + /* offset to the parent */ + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + ctn = val; + ctn_len = 0; + +obj_key_begin: + if (likely(*cur == '"')) { + val_incr(); + ctn_len++; + if (likely(read_string(&cur, end, inv, val, &msg))) goto obj_key_end; + goto fail_string; + } + if (likely(*cur == '}')) { + cur++; + if (likely(ctn_len == 0)) goto obj_end; + if (has_read_flag(ALLOW_TRAILING_COMMAS)) goto obj_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_begin; + } + if (has_read_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_key_begin; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_key_end: + if (*cur == ':') { + cur++; + goto obj_val_begin; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_end; + } + if (has_read_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_key_end; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_val_begin: + if (*cur == '"') { + val++; + ctn_len++; + if (likely(read_string(&cur, end, inv, val, &msg))) goto obj_val_end; + goto fail_string; + } + if (char_is_number(*cur)) { + val++; + ctn_len++; + if (likely(read_number(&cur, pre, flg, val, &msg))) goto obj_val_end; + goto fail_number; + } + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (*cur == 't') { + val++; + ctn_len++; + if (likely(read_true(&cur, val))) goto obj_val_end; + goto fail_literal; + } + if (*cur == 'f') { + val++; + ctn_len++; + if (likely(read_false(&cur, val))) goto obj_val_end; + goto fail_literal; + } + if (*cur == 'n') { + val++; + ctn_len++; + if (likely(read_null(&cur, val))) goto obj_val_end; + if (has_read_flag(ALLOW_INF_AND_NAN)) { + if (read_nan(false, &cur, pre, val)) goto obj_val_end; + } + goto fail_literal; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_begin; + } + if (has_read_flag(ALLOW_INF_AND_NAN) && + (*cur == 'i' || *cur == 'I' || *cur == 'N')) { + val++; + ctn_len++; + if (read_inf_or_nan(false, &cur, pre, val)) goto obj_val_end; + goto fail_character; + } + if (has_read_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_val_begin; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_val_end: + if (likely(*cur == ',')) { + cur++; + goto obj_key_begin; + } + if (likely(*cur == '}')) { + cur++; + goto obj_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_end; + } + if (has_read_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_val_end; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_end: + /* pop container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + /* point to the next value */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = (ctn_len << (YYJSON_TAG_BIT - 1)) | YYJSON_TYPE_OBJ; + if (unlikely(ctn == ctn_parent)) goto doc_end; + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +doc_end: + /* check invalid contents after json document */ + if (unlikely(cur < end) && !has_read_flag(STOP_WHEN_DONE)) { + if (has_read_flag(ALLOW_COMMENTS)) { + skip_spaces_and_comments(&cur); + if (byte_match_2(cur, "/*")) goto fail_comment; + } else { + while (char_is_space(*cur)) cur++; + } + if (unlikely(cur < end)) goto fail_garbage; + } + + if (pre && *pre) **pre = '\0'; + doc = (yyjson_doc *)val_hdr; + doc->root = val_hdr + hdr_len; + doc->alc = alc; + doc->dat_read = (usize)(cur - hdr); + doc->val_read = (usize)((val - doc->root) + 1); + doc->str_pool = has_read_flag(INSITU) ? NULL : (char *)hdr; + return doc; + +fail_string: + return_err(cur, INVALID_STRING, msg); +fail_number: + return_err(cur, INVALID_NUMBER, msg); +fail_alloc: + return_err(cur, MEMORY_ALLOCATION, "memory allocation failed"); +fail_trailing_comma: + return_err(cur, JSON_STRUCTURE, "trailing comma is not allowed"); +fail_literal: + return_err(cur, LITERAL, "invalid literal"); +fail_comment: + return_err(cur, INVALID_COMMENT, "unclosed multiline comment"); +fail_character: + return_err(cur, UNEXPECTED_CHARACTER, "unexpected character"); +fail_garbage: + return_err(cur, UNEXPECTED_CONTENT, "unexpected content after document"); + +#undef val_incr +#undef return_err +} + +/** Read JSON document (accept all style, but optimized for pretty). */ +static_inline yyjson_doc *read_root_pretty(u8 *hdr, + u8 *cur, + u8 *end, + yyjson_alc alc, + yyjson_read_flag flg, + yyjson_read_err *err) { + +#define return_err(_pos, _code, _msg) do { \ + if (is_truncated_end(hdr, _pos, end, YYJSON_READ_ERROR_##_code, flg)) { \ + err->pos = (usize)(end - hdr); \ + err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \ + err->msg = "unexpected end of data"; \ + } else { \ + err->pos = (usize)(_pos - hdr); \ + err->code = YYJSON_READ_ERROR_##_code; \ + err->msg = _msg; \ + } \ + if (val_hdr) alc.free(alc.ctx, (void *)val_hdr); \ + return NULL; \ +} while (false) + +#define val_incr() do { \ + val++; \ + if (unlikely(val >= val_end)) { \ + usize alc_old = alc_len; \ + alc_len += alc_len / 2; \ + if ((sizeof(usize) < 8) && (alc_len >= alc_max)) goto fail_alloc; \ + val_tmp = (yyjson_val *)alc.realloc(alc.ctx, (void *)val_hdr, \ + alc_old * sizeof(yyjson_val), \ + alc_len * sizeof(yyjson_val)); \ + if ((!val_tmp)) goto fail_alloc; \ + val = val_tmp + (usize)(val - val_hdr); \ + ctn = val_tmp + (usize)(ctn - val_hdr); \ + val_hdr = val_tmp; \ + val_end = val_tmp + (alc_len - 2); \ + } \ +} while (false) + + usize dat_len; /* data length in bytes, hint for allocator */ + usize hdr_len; /* value count used by yyjson_doc */ + usize alc_len; /* value count allocated */ + usize alc_max; /* maximum value count for allocator */ + usize ctn_len; /* the number of elements in current container */ + yyjson_val *val_hdr; /* the head of allocated values */ + yyjson_val *val_end; /* the end of allocated values */ + yyjson_val *val_tmp; /* temporary pointer for realloc */ + yyjson_val *val; /* current JSON value */ + yyjson_val *ctn; /* current container */ + yyjson_val *ctn_parent; /* parent of current container */ + yyjson_doc *doc; /* the JSON document, equals to val_hdr */ + const char *msg; /* error message */ + + bool raw; /* read number as raw */ + bool inv; /* allow invalid unicode */ + u8 *raw_end; /* raw end for null-terminator */ + u8 **pre; /* previous raw end pointer */ + + dat_len = has_read_flag(STOP_WHEN_DONE) ? 256 : (usize)(end - cur); + hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); + hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; + alc_max = USIZE_MAX / sizeof(yyjson_val); + alc_len = hdr_len + (dat_len / YYJSON_READER_ESTIMATED_PRETTY_RATIO) + 4; + alc_len = yyjson_min(alc_len, alc_max); + + val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_len * sizeof(yyjson_val)); + if (unlikely(!val_hdr)) goto fail_alloc; + val_end = val_hdr + (alc_len - 2); /* padding for key-value pair reading */ + val = val_hdr + hdr_len; + ctn = val; + ctn_len = 0; + raw = has_read_flag(NUMBER_AS_RAW) || has_read_flag(BIGNUM_AS_RAW); + inv = has_read_flag(ALLOW_INVALID_UNICODE) != 0; + raw_end = NULL; + pre = raw ? &raw_end : NULL; + + if (*cur++ == '{') { + ctn->tag = YYJSON_TYPE_OBJ; + ctn->uni.ofs = 0; + if (*cur == '\n') cur++; + goto obj_key_begin; + } else { + ctn->tag = YYJSON_TYPE_ARR; + ctn->uni.ofs = 0; + if (*cur == '\n') cur++; + goto arr_val_begin; + } + +arr_begin: + /* save current container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + + /* create a new array value, save parent container offset */ + val_incr(); + val->tag = YYJSON_TYPE_ARR; + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + + /* push the new array value as current container */ + ctn = val; + ctn_len = 0; + if (*cur == '\n') cur++; + +arr_val_begin: +#if YYJSON_IS_REAL_GCC + while (true) repeat16({ + if (byte_match_2(cur, " ")) cur += 2; + else break; + }) +#else + while (true) repeat16({ + if (likely(byte_match_2(cur, " "))) cur += 2; + else break; + }) +#endif + + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (char_is_number(*cur)) { + val_incr(); + ctn_len++; + if (likely(read_number(&cur, pre, flg, val, &msg))) goto arr_val_end; + goto fail_number; + } + if (*cur == '"') { + val_incr(); + ctn_len++; + if (likely(read_string(&cur, end, inv, val, &msg))) goto arr_val_end; + goto fail_string; + } + if (*cur == 't') { + val_incr(); + ctn_len++; + if (likely(read_true(&cur, val))) goto arr_val_end; + goto fail_literal; + } + if (*cur == 'f') { + val_incr(); + ctn_len++; + if (likely(read_false(&cur, val))) goto arr_val_end; + goto fail_literal; + } + if (*cur == 'n') { + val_incr(); + ctn_len++; + if (likely(read_null(&cur, val))) goto arr_val_end; + if (has_read_flag(ALLOW_INF_AND_NAN)) { + if (read_nan(false, &cur, pre, val)) goto arr_val_end; + } + goto fail_literal; + } + if (*cur == ']') { + cur++; + if (likely(ctn_len == 0)) goto arr_end; + if (has_read_flag(ALLOW_TRAILING_COMMAS)) goto arr_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_begin; + } + if (has_read_flag(ALLOW_INF_AND_NAN) && + (*cur == 'i' || *cur == 'I' || *cur == 'N')) { + val_incr(); + ctn_len++; + if (read_inf_or_nan(false, &cur, pre, val)) goto arr_val_end; + goto fail_character; + } + if (has_read_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto arr_val_begin; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +arr_val_end: + if (byte_match_2(cur, ",\n")) { + cur += 2; + goto arr_val_begin; + } + if (*cur == ',') { + cur++; + goto arr_val_begin; + } + if (*cur == ']') { + cur++; + goto arr_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_end; + } + if (has_read_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto arr_val_end; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +arr_end: + /* get parent container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + + /* save the next sibling value offset */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = ((ctn_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; + if (unlikely(ctn == ctn_parent)) goto doc_end; + + /* pop parent as current container */ + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if (*cur == '\n') cur++; + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +obj_begin: + /* push container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + val_incr(); + val->tag = YYJSON_TYPE_OBJ; + /* offset to the parent */ + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + ctn = val; + ctn_len = 0; + if (*cur == '\n') cur++; + +obj_key_begin: +#if YYJSON_IS_REAL_GCC + while (true) repeat16({ + if (byte_match_2(cur, " ")) cur += 2; + else break; + }) +#else + while (true) repeat16({ + if (likely(byte_match_2(cur, " "))) cur += 2; + else break; + }) +#endif + if (likely(*cur == '"')) { + val_incr(); + ctn_len++; + if (likely(read_string(&cur, end, inv, val, &msg))) goto obj_key_end; + goto fail_string; + } + if (likely(*cur == '}')) { + cur++; + if (likely(ctn_len == 0)) goto obj_end; + if (has_read_flag(ALLOW_TRAILING_COMMAS)) goto obj_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_begin; + } + if (has_read_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_key_begin; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_key_end: + if (byte_match_2(cur, ": ")) { + cur += 2; + goto obj_val_begin; + } + if (*cur == ':') { + cur++; + goto obj_val_begin; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_end; + } + if (has_read_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_key_end; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_val_begin: + if (*cur == '"') { + val++; + ctn_len++; + if (likely(read_string(&cur, end, inv, val, &msg))) goto obj_val_end; + goto fail_string; + } + if (char_is_number(*cur)) { + val++; + ctn_len++; + if (likely(read_number(&cur, pre, flg, val, &msg))) goto obj_val_end; + goto fail_number; + } + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (*cur == 't') { + val++; + ctn_len++; + if (likely(read_true(&cur, val))) goto obj_val_end; + goto fail_literal; + } + if (*cur == 'f') { + val++; + ctn_len++; + if (likely(read_false(&cur, val))) goto obj_val_end; + goto fail_literal; + } + if (*cur == 'n') { + val++; + ctn_len++; + if (likely(read_null(&cur, val))) goto obj_val_end; + if (has_read_flag(ALLOW_INF_AND_NAN)) { + if (read_nan(false, &cur, pre, val)) goto obj_val_end; + } + goto fail_literal; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_begin; + } + if (has_read_flag(ALLOW_INF_AND_NAN) && + (*cur == 'i' || *cur == 'I' || *cur == 'N')) { + val++; + ctn_len++; + if (read_inf_or_nan(false, &cur, pre, val)) goto obj_val_end; + goto fail_character; + } + if (has_read_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_val_begin; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_val_end: + if (byte_match_2(cur, ",\n")) { + cur += 2; + goto obj_key_begin; + } + if (likely(*cur == ',')) { + cur++; + goto obj_key_begin; + } + if (likely(*cur == '}')) { + cur++; + goto obj_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_end; + } + if (has_read_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_val_end; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_end: + /* pop container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + /* point to the next value */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = (ctn_len << (YYJSON_TAG_BIT - 1)) | YYJSON_TYPE_OBJ; + if (unlikely(ctn == ctn_parent)) goto doc_end; + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if (*cur == '\n') cur++; + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +doc_end: + /* check invalid contents after json document */ + if (unlikely(cur < end) && !has_read_flag(STOP_WHEN_DONE)) { + if (has_read_flag(ALLOW_COMMENTS)) { + skip_spaces_and_comments(&cur); + if (byte_match_2(cur, "/*")) goto fail_comment; + } else { + while (char_is_space(*cur)) cur++; + } + if (unlikely(cur < end)) goto fail_garbage; + } + + if (pre && *pre) **pre = '\0'; + doc = (yyjson_doc *)val_hdr; + doc->root = val_hdr + hdr_len; + doc->alc = alc; + doc->dat_read = (usize)(cur - hdr); + doc->val_read = (usize)((val - val_hdr)) - hdr_len + 1; + doc->str_pool = has_read_flag(INSITU) ? NULL : (char *)hdr; + return doc; + +fail_string: + return_err(cur, INVALID_STRING, msg); +fail_number: + return_err(cur, INVALID_NUMBER, msg); +fail_alloc: + return_err(cur, MEMORY_ALLOCATION, "memory allocation failed"); +fail_trailing_comma: + return_err(cur, JSON_STRUCTURE, "trailing comma is not allowed"); +fail_literal: + return_err(cur, LITERAL, "invalid literal"); +fail_comment: + return_err(cur, INVALID_COMMENT, "unclosed multiline comment"); +fail_character: + return_err(cur, UNEXPECTED_CHARACTER, "unexpected character"); +fail_garbage: + return_err(cur, UNEXPECTED_CONTENT, "unexpected content after document"); + +#undef val_incr +#undef return_err +} + + + +/*============================================================================== + * JSON Reader Entrance + *============================================================================*/ + +yyjson_doc *yyjson_read_opts(char *dat, + usize len, + yyjson_read_flag flg, + const yyjson_alc *alc_ptr, + yyjson_read_err *err) { + +#define return_err(_pos, _code, _msg) do { \ + err->pos = (usize)(_pos); \ + err->msg = _msg; \ + err->code = YYJSON_READ_ERROR_##_code; \ + if (!has_read_flag(INSITU) && hdr) alc.free(alc.ctx, (void *)hdr); \ + return NULL; \ +} while (false) + + yyjson_read_err dummy_err; + yyjson_alc alc; + yyjson_doc *doc; + u8 *hdr = NULL, *end, *cur; + + /* validate input parameters */ + if (!err) err = &dummy_err; + if (likely(!alc_ptr)) { + alc = YYJSON_DEFAULT_ALC; + } else { + alc = *alc_ptr; + } + if (unlikely(!dat)) { + return_err(0, INVALID_PARAMETER, "input data is NULL"); + } + if (unlikely(!len)) { + return_err(0, INVALID_PARAMETER, "input length is 0"); + } + + /* add 4-byte zero padding for input data if necessary */ + if (has_read_flag(INSITU)) { + hdr = (u8 *)dat; + end = (u8 *)dat + len; + cur = (u8 *)dat; + } else { + if (unlikely(len >= USIZE_MAX - YYJSON_PADDING_SIZE)) { + return_err(0, MEMORY_ALLOCATION, "memory allocation failed"); + } + hdr = (u8 *)alc.malloc(alc.ctx, len + YYJSON_PADDING_SIZE); + if (unlikely(!hdr)) { + return_err(0, MEMORY_ALLOCATION, "memory allocation failed"); + } + end = hdr + len; + cur = hdr; + memcpy(hdr, dat, len); + memset(end, 0, YYJSON_PADDING_SIZE); + } + + /* skip empty contents before json document */ + if (unlikely(char_is_space_or_comment(*cur))) { + if (has_read_flag(ALLOW_COMMENTS)) { + if (!skip_spaces_and_comments(&cur)) { + return_err(cur - hdr, INVALID_COMMENT, + "unclosed multiline comment"); + } + } else { + if (likely(char_is_space(*cur))) { + while (char_is_space(*++cur)); + } + } + if (unlikely(cur >= end)) { + return_err(0, EMPTY_CONTENT, "input data is empty"); + } + } + + /* read json document */ + if (likely(char_is_container(*cur))) { + if (char_is_space(cur[1]) && char_is_space(cur[2])) { + doc = read_root_pretty(hdr, cur, end, alc, flg, err); + } else { + doc = read_root_minify(hdr, cur, end, alc, flg, err); + } + } else { + doc = read_root_single(hdr, cur, end, alc, flg, err); + } + + /* check result */ + if (likely(doc)) { + memset(err, 0, sizeof(yyjson_read_err)); + } else { + /* RFC 8259: JSON text MUST be encoded using UTF-8 */ + if (err->pos == 0 && err->code != YYJSON_READ_ERROR_MEMORY_ALLOCATION) { + if ((hdr[0] == 0xEF && hdr[1] == 0xBB && hdr[2] == 0xBF)) { + err->msg = "byte order mark (BOM) is not supported"; + } else if (len >= 4 && + ((hdr[0] == 0x00 && hdr[1] == 0x00 && + hdr[2] == 0xFE && hdr[3] == 0xFF) || + (hdr[0] == 0xFF && hdr[1] == 0xFE && + hdr[2] == 0x00 && hdr[3] == 0x00))) { + err->msg = "UTF-32 encoding is not supported"; + } else if (len >= 2 && + ((hdr[0] == 0xFE && hdr[1] == 0xFF) || + (hdr[0] == 0xFF && hdr[1] == 0xFE))) { + err->msg = "UTF-16 encoding is not supported"; + } + } + if (!has_read_flag(INSITU)) alc.free(alc.ctx, (void *)hdr); + } + return doc; + +#undef return_err +} + +yyjson_doc *yyjson_read_file(const char *path, + yyjson_read_flag flg, + const yyjson_alc *alc_ptr, + yyjson_read_err *err) { +#define return_err(_code, _msg) do { \ + err->pos = 0; \ + err->msg = _msg; \ + err->code = YYJSON_READ_ERROR_##_code; \ + return NULL; \ +} while (false) + + yyjson_read_err dummy_err; + yyjson_doc *doc; + FILE *file; + + if (!err) err = &dummy_err; + if (unlikely(!path)) return_err(INVALID_PARAMETER, "input path is NULL"); + + file = fopen_readonly(path); + if (unlikely(!file)) return_err(FILE_OPEN, "file opening failed"); + + doc = yyjson_read_fp(file, flg, alc_ptr, err); + fclose(file); + return doc; + +#undef return_err +} + +yyjson_doc *yyjson_read_fp(FILE *file, + yyjson_read_flag flg, + const yyjson_alc *alc_ptr, + yyjson_read_err *err) { +#define return_err(_code, _msg) do { \ + err->pos = 0; \ + err->msg = _msg; \ + err->code = YYJSON_READ_ERROR_##_code; \ + if (buf) alc.free(alc.ctx, buf); \ + return NULL; \ +} while (false) + + yyjson_read_err dummy_err; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + yyjson_doc *doc; + + long file_size = 0, file_pos; + void *buf = NULL; + usize buf_size = 0; + + /* validate input parameters */ + if (!err) err = &dummy_err; + if (unlikely(!file)) return_err(INVALID_PARAMETER, "input file is NULL"); + + /* get current position */ + file_pos = ftell(file); + if (file_pos != -1) { + /* get total file size, may fail */ + if (fseek(file, 0, SEEK_END) == 0) file_size = ftell(file); + /* reset to original position, may fail */ + if (fseek(file, file_pos, SEEK_SET) != 0) file_size = 0; + /* get file size from current postion to end */ + if (file_size > 0) file_size -= file_pos; + } + + /* read file */ + if (file_size > 0) { + /* read the entire file in one call */ + buf_size = (usize)file_size + YYJSON_PADDING_SIZE; + buf = alc.malloc(alc.ctx, buf_size); + if (buf == NULL) { + return_err(MEMORY_ALLOCATION, "fail to alloc memory"); + } + if (fread_safe(buf, (usize)file_size, file) != (usize)file_size) { + return_err(FILE_READ, "file reading failed"); + } + } else { + /* failed to get file size, read it as a stream */ + usize chunk_min = (usize)64; + usize chunk_max = (usize)512 * 1024 * 1024; + usize chunk_now = chunk_min; + usize read_size; + void *tmp; + + buf_size = YYJSON_PADDING_SIZE; + while (true) { + if (buf_size + chunk_now < buf_size) { /* overflow */ + return_err(MEMORY_ALLOCATION, "fail to alloc memory"); + } + buf_size += chunk_now; + if (!buf) { + buf = alc.malloc(alc.ctx, buf_size); + if (!buf) return_err(MEMORY_ALLOCATION, "fail to alloc memory"); + } else { + tmp = alc.realloc(alc.ctx, buf, buf_size - chunk_now, buf_size); + if (!tmp) return_err(MEMORY_ALLOCATION, "fail to alloc memory"); + buf = tmp; + } + tmp = ((u8 *)buf) + buf_size - YYJSON_PADDING_SIZE - chunk_now; + read_size = fread_safe(tmp, chunk_now, file); + file_size += (long)read_size; + if (read_size != chunk_now) break; + + chunk_now *= 2; + if (chunk_now > chunk_max) chunk_now = chunk_max; + } + } + + /* read JSON */ + memset((u8 *)buf + file_size, 0, YYJSON_PADDING_SIZE); + flg |= YYJSON_READ_INSITU; + doc = yyjson_read_opts((char *)buf, (usize)file_size, flg, &alc, err); + if (doc) { + doc->str_pool = (char *)buf; + return doc; + } else { + alc.free(alc.ctx, buf); + return NULL; + } + +#undef return_err +} + +const char *yyjson_read_number(const char *dat, + yyjson_val *val, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err) { +#define return_err(_pos, _code, _msg) do { \ + err->pos = _pos > hdr ? (usize)(_pos - hdr) : 0; \ + err->msg = _msg; \ + err->code = YYJSON_READ_ERROR_##_code; \ + return NULL; \ +} while (false) + + u8 *hdr = constcast(u8 *)dat, *cur = hdr; + bool raw; /* read number as raw */ + u8 *raw_end; /* raw end for null-terminator */ + u8 **pre; /* previous raw end pointer */ + const char *msg; + yyjson_read_err dummy_err; + +#if !YYJSON_HAS_IEEE_754 || YYJSON_DISABLE_FAST_FP_CONV + u8 buf[128]; + usize dat_len; +#endif + + if (!err) err = &dummy_err; + if (unlikely(!dat)) { + return_err(cur, INVALID_PARAMETER, "input data is NULL"); + } + if (unlikely(!val)) { + return_err(cur, INVALID_PARAMETER, "output value is NULL"); + } + +#if !YYJSON_HAS_IEEE_754 || YYJSON_DISABLE_FAST_FP_CONV + if (!alc) alc = &YYJSON_DEFAULT_ALC; + dat_len = strlen(dat); + if (dat_len < sizeof(buf)) { + memcpy(buf, dat, dat_len + 1); + hdr = buf; + cur = hdr; + } else { + hdr = (u8 *)alc->malloc(alc->ctx, dat_len + 1); + cur = hdr; + if (unlikely(!hdr)) { + return_err(cur, MEMORY_ALLOCATION, "memory allocation failed"); + } + memcpy(hdr, dat, dat_len + 1); + } + hdr[dat_len] = 0; +#endif + + raw = (flg & (YYJSON_READ_NUMBER_AS_RAW | YYJSON_READ_BIGNUM_AS_RAW)) != 0; + raw_end = NULL; + pre = raw ? &raw_end : NULL; + +#if !YYJSON_HAS_IEEE_754 || YYJSON_DISABLE_FAST_FP_CONV + if (!read_number(&cur, pre, flg, val, &msg)) { + if (dat_len >= sizeof(buf)) alc->free(alc->ctx, hdr); + return_err(cur, INVALID_NUMBER, msg); + } + if (dat_len >= sizeof(buf)) alc->free(alc->ctx, hdr); + if (yyjson_is_raw(val)) val->uni.str = dat; + return dat + (cur - hdr); +#else + if (!read_number(&cur, pre, flg, val, &msg)) { + return_err(cur, INVALID_NUMBER, msg); + } + return (const char *)cur; +#endif + +#undef return_err +} + +#endif /* YYJSON_DISABLE_READER */ + + + +#if !YYJSON_DISABLE_WRITER + +/*============================================================================== + * Integer Writer + * + * The maximum value of uint32_t is 4294967295 (10 digits), + * these digits are named as 'aabbccddee' here. + * + * Although most compilers may convert the "division by constant value" into + * "multiply and shift", manual conversion can still help some compilers + * generate fewer and better instructions. + * + * Reference: + * Division by Invariant Integers using Multiplication, 1994. + * https://gmplib.org/~tege/divcnst-pldi94.pdf + * Improved division by invariant integers, 2011. + * https://gmplib.org/~tege/division-paper.pdf + *============================================================================*/ + +/** Digit table from 00 to 99. */ +yyjson_align(2) +static const char digit_table[200] = { + '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', + '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', + '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', + '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', + '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', + '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', + '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', + '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', + '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', + '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', + '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', + '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', + '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', + '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', + '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', + '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', + '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', + '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', + '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', + '9', '5', '9', '6', '9', '7', '9', '8', '9', '9' +}; + +static_inline u8 *write_u32_len_8(u32 val, u8 *buf) { + u32 aa, bb, cc, dd, aabb, ccdd; /* 8 digits: aabbccdd */ + aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */ + ccdd = val - aabb * 10000; /* (val % 10000) */ + aa = (aabb * 5243) >> 19; /* (aabb / 100) */ + cc = (ccdd * 5243) >> 19; /* (ccdd / 100) */ + bb = aabb - aa * 100; /* (aabb % 100) */ + dd = ccdd - cc * 100; /* (ccdd % 100) */ + byte_copy_2(buf + 0, digit_table + aa * 2); + byte_copy_2(buf + 2, digit_table + bb * 2); + byte_copy_2(buf + 4, digit_table + cc * 2); + byte_copy_2(buf + 6, digit_table + dd * 2); + return buf + 8; +} + +static_inline u8 *write_u32_len_4(u32 val, u8 *buf) { + u32 aa, bb; /* 4 digits: aabb */ + aa = (val * 5243) >> 19; /* (val / 100) */ + bb = val - aa * 100; /* (val % 100) */ + byte_copy_2(buf + 0, digit_table + aa * 2); + byte_copy_2(buf + 2, digit_table + bb * 2); + return buf + 4; +} + +static_inline u8 *write_u32_len_1_8(u32 val, u8 *buf) { + u32 aa, bb, cc, dd, aabb, bbcc, ccdd, lz; + + if (val < 100) { /* 1-2 digits: aa */ + lz = val < 10; /* leading zero: 0 or 1 */ + byte_copy_2(buf + 0, digit_table + val * 2 + lz); + buf -= lz; + return buf + 2; + + } else if (val < 10000) { /* 3-4 digits: aabb */ + aa = (val * 5243) >> 19; /* (val / 100) */ + bb = val - aa * 100; /* (val % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + byte_copy_2(buf + 0, digit_table + aa * 2 + lz); + buf -= lz; + byte_copy_2(buf + 2, digit_table + bb * 2); + return buf + 4; + + } else if (val < 1000000) { /* 5-6 digits: aabbcc */ + aa = (u32)(((u64)val * 429497) >> 32); /* (val / 10000) */ + bbcc = val - aa * 10000; /* (val % 10000) */ + bb = (bbcc * 5243) >> 19; /* (bbcc / 100) */ + cc = bbcc - bb * 100; /* (bbcc % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + byte_copy_2(buf + 0, digit_table + aa * 2 + lz); + buf -= lz; + byte_copy_2(buf + 2, digit_table + bb * 2); + byte_copy_2(buf + 4, digit_table + cc * 2); + return buf + 6; + + } else { /* 7-8 digits: aabbccdd */ + aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */ + ccdd = val - aabb * 10000; /* (val % 10000) */ + aa = (aabb * 5243) >> 19; /* (aabb / 100) */ + cc = (ccdd * 5243) >> 19; /* (ccdd / 100) */ + bb = aabb - aa * 100; /* (aabb % 100) */ + dd = ccdd - cc * 100; /* (ccdd % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + byte_copy_2(buf + 0, digit_table + aa * 2 + lz); + buf -= lz; + byte_copy_2(buf + 2, digit_table + bb * 2); + byte_copy_2(buf + 4, digit_table + cc * 2); + byte_copy_2(buf + 6, digit_table + dd * 2); + return buf + 8; + } +} + +static_inline u8 *write_u64_len_5_8(u32 val, u8 *buf) { + u32 aa, bb, cc, dd, aabb, bbcc, ccdd, lz; + + if (val < 1000000) { /* 5-6 digits: aabbcc */ + aa = (u32)(((u64)val * 429497) >> 32); /* (val / 10000) */ + bbcc = val - aa * 10000; /* (val % 10000) */ + bb = (bbcc * 5243) >> 19; /* (bbcc / 100) */ + cc = bbcc - bb * 100; /* (bbcc % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + byte_copy_2(buf + 0, digit_table + aa * 2 + lz); + buf -= lz; + byte_copy_2(buf + 2, digit_table + bb * 2); + byte_copy_2(buf + 4, digit_table + cc * 2); + return buf + 6; + + } else { /* 7-8 digits: aabbccdd */ + aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */ + ccdd = val - aabb * 10000; /* (val % 10000) */ + aa = (aabb * 5243) >> 19; /* (aabb / 100) */ + cc = (ccdd * 5243) >> 19; /* (ccdd / 100) */ + bb = aabb - aa * 100; /* (aabb % 100) */ + dd = ccdd - cc * 100; /* (ccdd % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + byte_copy_2(buf + 0, digit_table + aa * 2 + lz); + buf -= lz; + byte_copy_2(buf + 2, digit_table + bb * 2); + byte_copy_2(buf + 4, digit_table + cc * 2); + byte_copy_2(buf + 6, digit_table + dd * 2); + return buf + 8; + } +} + +static_inline u8 *write_u64(u64 val, u8 *buf) { + u64 tmp, hgh; + u32 mid, low; + + if (val < 100000000) { /* 1-8 digits */ + buf = write_u32_len_1_8((u32)val, buf); + return buf; + + } else if (val < (u64)100000000 * 100000000) { /* 9-16 digits */ + hgh = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ + buf = write_u32_len_1_8((u32)hgh, buf); + buf = write_u32_len_8(low, buf); + return buf; + + } else { /* 17-20 digits */ + tmp = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - tmp * 100000000); /* (val % 100000000) */ + hgh = (u32)(tmp / 10000); /* (tmp / 10000) */ + mid = (u32)(tmp - hgh * 10000); /* (tmp % 10000) */ + buf = write_u64_len_5_8((u32)hgh, buf); + buf = write_u32_len_4(mid, buf); + buf = write_u32_len_8(low, buf); + return buf; + } +} + + + +/*============================================================================== + * Number Writer + *============================================================================*/ + +#if YYJSON_HAS_IEEE_754 && !YYJSON_DISABLE_FAST_FP_CONV /* FP_WRITER */ + +/** Trailing zero count table for number 0 to 99. + (generate with misc/make_tables.c) */ +static const u8 dec_trailing_zero_table[] = { + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/** Write an unsigned integer with a length of 1 to 16. */ +static_inline u8 *write_u64_len_1_to_16(u64 val, u8 *buf) { + u64 hgh; + u32 low; + if (val < 100000000) { /* 1-8 digits */ + buf = write_u32_len_1_8((u32)val, buf); + return buf; + } else { /* 9-16 digits */ + hgh = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ + buf = write_u32_len_1_8((u32)hgh, buf); + buf = write_u32_len_8(low, buf); + return buf; + } +} + +/** Write an unsigned integer with a length of 1 to 17. */ +static_inline u8 *write_u64_len_1_to_17(u64 val, u8 *buf) { + u64 hgh; + u32 mid, low, one; + if (val >= (u64)100000000 * 10000000) { /* len: 16 to 17 */ + hgh = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ + one = (u32)(hgh / 100000000); /* (hgh / 100000000) */ + mid = (u32)(hgh - (u64)one * 100000000); /* (hgh % 100000000) */ + *buf = (u8)((u8)one + (u8)'0'); + buf += one > 0; + buf = write_u32_len_8(mid, buf); + buf = write_u32_len_8(low, buf); + return buf; + } else if (val >= (u64)100000000){ /* len: 9 to 15 */ + hgh = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ + buf = write_u32_len_1_8((u32)hgh, buf); + buf = write_u32_len_8(low, buf); + return buf; + } else { /* len: 1 to 8 */ + buf = write_u32_len_1_8((u32)val, buf); + return buf; + } +} + +/** + Write an unsigned integer with a length of 15 to 17 with trailing zero trimmed. + These digits are named as "aabbccddeeffgghhii" here. + For example, input 1234567890123000, output "1234567890123". + */ +static_inline u8 *write_u64_len_15_to_17_trim(u8 *buf, u64 sig) { + bool lz; /* leading zero */ + u32 tz1, tz2, tz; /* trailing zero */ + + u32 abbccddee = (u32)(sig / 100000000); + u32 ffgghhii = (u32)(sig - (u64)abbccddee * 100000000); + u32 abbcc = abbccddee / 10000; /* (abbccddee / 10000) */ + u32 ddee = abbccddee - abbcc * 10000; /* (abbccddee % 10000) */ + u32 abb = (u32)(((u64)abbcc * 167773) >> 24); /* (abbcc / 100) */ + u32 a = (abb * 41) >> 12; /* (abb / 100) */ + u32 bb = abb - a * 100; /* (abb % 100) */ + u32 cc = abbcc - abb * 100; /* (abbcc % 100) */ + + /* write abbcc */ + buf[0] = (u8)(a + '0'); + buf += a > 0; + lz = bb < 10 && a == 0; + byte_copy_2(buf + 0, digit_table + bb * 2 + lz); + buf -= lz; + byte_copy_2(buf + 2, digit_table + cc * 2); + + if (ffgghhii) { + u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ + u32 ee = ddee - dd * 100; /* (ddee % 100) */ + u32 ffgg = (u32)(((u64)ffgghhii * 109951163) >> 40); /* (val / 10000) */ + u32 hhii = ffgghhii - ffgg * 10000; /* (val % 10000) */ + u32 ff = (ffgg * 5243) >> 19; /* (aabb / 100) */ + u32 gg = ffgg - ff * 100; /* (aabb % 100) */ + byte_copy_2(buf + 4, digit_table + dd * 2); + byte_copy_2(buf + 6, digit_table + ee * 2); + byte_copy_2(buf + 8, digit_table + ff * 2); + byte_copy_2(buf + 10, digit_table + gg * 2); + if (hhii) { + u32 hh = (hhii * 5243) >> 19; /* (ccdd / 100) */ + u32 ii = hhii - hh * 100; /* (ccdd % 100) */ + byte_copy_2(buf + 12, digit_table + hh * 2); + byte_copy_2(buf + 14, digit_table + ii * 2); + tz1 = dec_trailing_zero_table[hh]; + tz2 = dec_trailing_zero_table[ii]; + tz = ii ? tz2 : (tz1 + 2); + buf += 16 - tz; + return buf; + } else { + tz1 = dec_trailing_zero_table[ff]; + tz2 = dec_trailing_zero_table[gg]; + tz = gg ? tz2 : (tz1 + 2); + buf += 12 - tz; + return buf; + } + } else { + if (ddee) { + u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ + u32 ee = ddee - dd * 100; /* (ddee % 100) */ + byte_copy_2(buf + 4, digit_table + dd * 2); + byte_copy_2(buf + 6, digit_table + ee * 2); + tz1 = dec_trailing_zero_table[dd]; + tz2 = dec_trailing_zero_table[ee]; + tz = ee ? tz2 : (tz1 + 2); + buf += 8 - tz; + return buf; + } else { + tz1 = dec_trailing_zero_table[bb]; + tz2 = dec_trailing_zero_table[cc]; + tz = cc ? tz2 : (tz1 + tz2); + buf += 4 - tz; + return buf; + } + } +} + +/** Write a signed integer in the range -324 to 308. */ +static_inline u8 *write_f64_exp(i32 exp, u8 *buf) { + buf[0] = '-'; + buf += exp < 0; + exp = exp < 0 ? -exp : exp; + if (exp < 100) { + u32 lz = exp < 10; + byte_copy_2(buf + 0, digit_table + (u32)exp * 2 + lz); + return buf + 2 - lz; + } else { + u32 hi = ((u32)exp * 656) >> 16; /* exp / 100 */ + u32 lo = (u32)exp - hi * 100; /* exp % 100 */ + buf[0] = (u8)((u8)hi + (u8)'0'); + byte_copy_2(buf + 1, digit_table + lo * 2); + return buf + 3; + } +} + +/** Multiplies 128-bit integer and returns highest 64-bit rounded value. */ +static_inline u64 round_to_odd(u64 hi, u64 lo, u64 cp) { + u64 x_hi, x_lo, y_hi, y_lo; + u128_mul(cp, lo, &x_hi, &x_lo); + u128_mul_add(cp, hi, x_hi, &y_hi, &y_lo); + return y_hi | (y_lo > 1); +} + +/** + Convert double number from binary to decimal. + The output significand is shortest decimal but may have trailing zeros. + + This function use the Schubfach algorithm: + Raffaello Giulietti, The Schubfach way to render doubles (5th version), 2022. + https://drive.google.com/file/d/1gp5xv4CAa78SVgCeWfGqqI4FfYYYuNFb + https://mail.openjdk.java.net/pipermail/core-libs-dev/2021-November/083536.html + https://github.com/openjdk/jdk/pull/3402 (Java implementation) + https://github.com/abolz/Drachennest (C++ implementation) + + See also: + Dragonbox: A New Floating-Point Binary-to-Decimal Conversion Algorithm, 2022. + https://github.com/jk-jeon/dragonbox/blob/master/other_files/Dragonbox.pdf + https://github.com/jk-jeon/dragonbox + + @param sig_raw The raw value of significand in IEEE 754 format. + @param exp_raw The raw value of exponent in IEEE 754 format. + @param sig_bin The decoded value of significand in binary. + @param exp_bin The decoded value of exponent in binary. + @param sig_dec The output value of significand in decimal. + @param exp_dec The output value of exponent in decimal. + @warning The input double number should not be 0, inf, nan. + */ +static_inline void f64_bin_to_dec(u64 sig_raw, u32 exp_raw, + u64 sig_bin, i32 exp_bin, + u64 *sig_dec, i32 *exp_dec) { + + bool is_even, regular_spacing, u_inside, w_inside, round_up; + u64 s, sp, cb, cbl, cbr, vb, vbl, vbr, pow10hi, pow10lo, upper, lower, mid; + i32 k, h, exp10; + + is_even = !(sig_bin & 1); + regular_spacing = (sig_raw == 0 && exp_raw > 1); + + cbl = 4 * sig_bin - 2 + regular_spacing; + cb = 4 * sig_bin; + cbr = 4 * sig_bin + 2; + + /* exp_bin: [-1074, 971] */ + /* k = regular_spacing ? floor(log10(pow(2, exp_bin))) */ + /* : floor(log10(pow(2, exp_bin) * 3.0 / 4.0)) */ + /* = regular_spacing ? floor(exp_bin * log10(2)) */ + /* : floor(exp_bin * log10(2) + log10(3.0 / 4.0)) */ + k = (i32)(exp_bin * 315653 - (regular_spacing ? 131237 : 0)) >> 20; + + /* k: [-324, 292] */ + /* h = exp_bin + floor(log2(pow(10, e))) */ + /* = exp_bin + floor(log2(10) * e) */ + exp10 = -k; + h = exp_bin + ((exp10 * 217707) >> 16) + 1; + + pow10_table_get_sig(exp10, &pow10hi, &pow10lo); + pow10lo += (exp10 < POW10_SIG_TABLE_MIN_EXACT_EXP || + exp10 > POW10_SIG_TABLE_MAX_EXACT_EXP); + vbl = round_to_odd(pow10hi, pow10lo, cbl << h); + vb = round_to_odd(pow10hi, pow10lo, cb << h); + vbr = round_to_odd(pow10hi, pow10lo, cbr << h); + + lower = vbl + !is_even; + upper = vbr - !is_even; + + s = vb / 4; + if (s >= 10) { + sp = s / 10; + u_inside = (lower <= 40 * sp); + w_inside = (upper >= 40 * sp + 40); + if (u_inside != w_inside) { + *sig_dec = sp + w_inside; + *exp_dec = k + 1; + return; + } + } + + u_inside = (lower <= 4 * s); + w_inside = (upper >= 4 * s + 4); + + mid = 4 * s + 2; + round_up = (vb > mid) || (vb == mid && (s & 1) != 0); + + *sig_dec = s + ((u_inside != w_inside) ? w_inside : round_up); + *exp_dec = k; +} + +/** + Write a double number (requires 32 bytes buffer). + + We follows the ECMAScript specification to print floating point numbers, + but with the following changes: + 1. Keep the negative sign of 0.0 to preserve input information. + 2. Keep decimal point to indicate the number is floating point. + 3. Remove positive sign of exponent part. + */ +static_inline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { + u64 sig_bin, sig_dec, sig_raw; + i32 exp_bin, exp_dec, sig_len, dot_pos, i, max; + u32 exp_raw, hi, lo; + u8 *hdr, *num_hdr, *num_end, *dot_end; + bool sign; + + /* decode raw bytes from IEEE-754 double format. */ + sign = (bool)(raw >> (F64_BITS - 1)); + sig_raw = raw & F64_SIG_MASK; + exp_raw = (u32)((raw & F64_EXP_MASK) >> F64_SIG_BITS); + + /* return inf and nan */ + if (unlikely(exp_raw == ((u32)1 << F64_EXP_BITS) - 1)) { + if (has_write_flag(INF_AND_NAN_AS_NULL)) { + byte_copy_4(buf, "null"); + return buf + 4; + } + else if (has_write_flag(ALLOW_INF_AND_NAN)) { + if (sig_raw == 0) { + buf[0] = '-'; + buf += sign; + byte_copy_8(buf, "Infinity"); + buf += 8; + return buf; + } else { + byte_copy_4(buf, "NaN"); + return buf + 3; + } + } + return NULL; + } + + /* add sign for all finite double value, including 0.0 and inf */ + buf[0] = '-'; + buf += sign; + hdr = buf; + + /* return zero */ + if ((raw << 1) == 0) { + byte_copy_4(buf, "0.0"); + buf += 3; + return buf; + } + + if (likely(exp_raw != 0)) { + /* normal number */ + sig_bin = sig_raw | ((u64)1 << F64_SIG_BITS); + exp_bin = (i32)exp_raw - F64_EXP_BIAS - F64_SIG_BITS; + + /* fast path for small integer number without fraction */ + if (-F64_SIG_BITS <= exp_bin && exp_bin <= 0) { + if (u64_tz_bits(sig_bin) >= (u32)-exp_bin) { + /* number is integer in range 1 to 0x1FFFFFFFFFFFFF */ + sig_dec = sig_bin >> -exp_bin; + buf = write_u64_len_1_to_16(sig_dec, buf); + byte_copy_2(buf, ".0"); + buf += 2; + return buf; + } + } + + /* binary to decimal */ + f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); + + /* the sig length is 15 to 17 */ + sig_len = 17; + sig_len -= (sig_dec < (u64)100000000 * 100000000); + sig_len -= (sig_dec < (u64)100000000 * 10000000); + + /* the decimal point position relative to the first digit */ + dot_pos = sig_len + exp_dec; + + if (-6 < dot_pos && dot_pos <= 21) { + /* no need to write exponent part */ + if (dot_pos <= 0) { + /* dot before first digit */ + /* such as 0.1234, 0.000001234 */ + num_hdr = hdr + (2 - dot_pos); + num_end = write_u64_len_15_to_17_trim(num_hdr, sig_dec); + hdr[0] = '0'; + hdr[1] = '.'; + hdr += 2; + max = -dot_pos; + for (i = 0; i < max; i++) hdr[i] = '0'; + return num_end; + } else { + /* dot after first digit */ + /* such as 1.234, 1234.0, 123400000000000000000.0 */ + memset(hdr + 0, '0', 8); + memset(hdr + 8, '0', 8); + memset(hdr + 16, '0', 8); + num_hdr = hdr + 1; + num_end = write_u64_len_15_to_17_trim(num_hdr, sig_dec); + for (i = 0; i < dot_pos; i++) hdr[i] = hdr[i + 1]; + hdr[dot_pos] = '.'; + dot_end = hdr + dot_pos + 2; + return dot_end < num_end ? num_end : dot_end; + } + } else { + /* write with scientific notation */ + /* such as 1.234e56 */ + u8 *end = write_u64_len_15_to_17_trim(buf + 1, sig_dec); + end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */ + exp_dec += sig_len - 1; + hdr[0] = hdr[1]; + hdr[1] = '.'; + end[0] = 'e'; + buf = write_f64_exp(exp_dec, end + 1); + return buf; + } + + } else { + /* subnormal number */ + sig_bin = sig_raw; + exp_bin = 1 - F64_EXP_BIAS - F64_SIG_BITS; + + /* binary to decimal */ + f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); + + /* write significand part */ + buf = write_u64_len_1_to_17(sig_dec, buf + 1); + hdr[0] = hdr[1]; + hdr[1] = '.'; + do { + buf--; + exp_dec++; + } while (*buf == '0'); + exp_dec += (i32)(buf - hdr - 2); + buf += (*buf != '.'); + buf[0] = 'e'; + buf++; + + /* write exponent part */ + buf[0] = '-'; + buf++; + exp_dec = -exp_dec; + hi = ((u32)exp_dec * 656) >> 16; /* exp / 100 */ + lo = (u32)exp_dec - hi * 100; /* exp % 100 */ + buf[0] = (u8)((u8)hi + (u8)'0'); + byte_copy_2(buf + 1, digit_table + lo * 2); + buf += 3; + return buf; + } +} + +#else /* FP_WRITER */ + +/** Write a double number (requires 32 bytes buffer). */ +static_inline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { + /* + For IEEE 754, `DBL_DECIMAL_DIG` is 17 for round-trip. + For non-IEEE formats, 17 is used to avoid buffer overflow, + round-trip is not guaranteed. + */ +#if defined(DBL_DECIMAL_DIG) && DBL_DECIMAL_DIG != 17 + int dig = DBL_DECIMAL_DIG > 17 ? 17 : DBL_DECIMAL_DIG; +#else + int dig = 17; +#endif + + /* + The snprintf() function is locale-dependent. For currently known locales, + (en, zh, ja, ko, am, he, hi) use '.' as the decimal point, while other + locales use ',' as the decimal point. we need to replace ',' with '.' + to avoid the locale setting. + */ + f64 val = f64_from_raw(raw); +#if YYJSON_MSC_VER >= 1400 + int len = sprintf_s((char *)buf, 32, "%.*g", dig, val); +#elif defined(snprintf) || (YYJSON_STDC_VER >= 199901L) + int len = snprintf((char *)buf, 32, "%.*g", dig, val); +#else + int len = sprintf((char *)buf, "%.*g", dig, val); +#endif + + u8 *cur = buf; + if (unlikely(len < 1)) return NULL; + cur += (*cur == '-'); + if (unlikely(!digi_is_digit(*cur))) { + /* nan, inf, or bad output */ + if (has_write_flag(INF_AND_NAN_AS_NULL)) { + byte_copy_4(buf, "null"); + return buf + 4; + } + else if (has_write_flag(ALLOW_INF_AND_NAN)) { + if (*cur == 'i') { + byte_copy_8(cur, "Infinity"); + cur += 8; + return cur; + } else if (*cur == 'n') { + byte_copy_4(buf, "NaN"); + return buf + 3; + } + } + return NULL; + } else { + /* finite number */ + int i = 0; + bool fp = false; + for (; i < len; i++) { + if (buf[i] == ',') buf[i] = '.'; + if (digi_is_fp((u8)buf[i])) fp = true; + } + if (!fp) { + buf[len++] = '.'; + buf[len++] = '0'; + } + } + return buf + len; +} + +#endif /* FP_WRITER */ + +/** Write a JSON number (requires 32 bytes buffer). */ +static_inline u8 *write_number(u8 *cur, yyjson_val *val, + yyjson_write_flag flg) { + if (val->tag & YYJSON_SUBTYPE_REAL) { + u64 raw = val->uni.u64; + return write_f64_raw(cur, raw, flg); + } else { + u64 pos = val->uni.u64; + u64 neg = ~pos + 1; + usize sgn = ((val->tag & YYJSON_SUBTYPE_SINT) > 0) & ((i64)pos < 0); + *cur = '-'; + return write_u64(sgn ? neg : pos, cur + sgn); + } +} + + + +/*============================================================================== + * String Writer + *============================================================================*/ + +/** Character encode type, if (type > CHAR_ENC_ERR_1) bytes = type / 2; */ +typedef u8 char_enc_type; +#define CHAR_ENC_CPY_1 0 /* 1-byte UTF-8, copy. */ +#define CHAR_ENC_ERR_1 1 /* 1-byte UTF-8, error. */ +#define CHAR_ENC_ESC_A 2 /* 1-byte ASCII, escaped as '\x'. */ +#define CHAR_ENC_ESC_1 3 /* 1-byte UTF-8, escaped as '\uXXXX'. */ +#define CHAR_ENC_CPY_2 4 /* 2-byte UTF-8, copy. */ +#define CHAR_ENC_ESC_2 5 /* 2-byte UTF-8, escaped as '\uXXXX'. */ +#define CHAR_ENC_CPY_3 6 /* 3-byte UTF-8, copy. */ +#define CHAR_ENC_ESC_3 7 /* 3-byte UTF-8, escaped as '\uXXXX'. */ +#define CHAR_ENC_CPY_4 8 /* 4-byte UTF-8, copy. */ +#define CHAR_ENC_ESC_4 9 /* 4-byte UTF-8, escaped as '\uXXXX\uXXXX'. */ + +/** Character encode type table: don't escape unicode, don't escape '/'. + (generate with misc/make_tables.c) */ +static const char_enc_type enc_table_cpy[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +/** Character encode type table: don't escape unicode, escape '/'. + (generate with misc/make_tables.c) */ +static const char_enc_type enc_table_cpy_slash[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +/** Character encode type table: escape unicode, don't escape '/'. + (generate with misc/make_tables.c) */ +static const char_enc_type enc_table_esc[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +/** Character encode type table: escape unicode, escape '/'. + (generate with misc/make_tables.c) */ +static const char_enc_type enc_table_esc_slash[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +/** Escaped hex character table: ["00" "01" "02" ... "FD" "FE" "FF"]. + (generate with misc/make_tables.c) */ +yyjson_align(2) +static const u8 esc_hex_char_table[512] = { + '0', '0', '0', '1', '0', '2', '0', '3', + '0', '4', '0', '5', '0', '6', '0', '7', + '0', '8', '0', '9', '0', 'A', '0', 'B', + '0', 'C', '0', 'D', '0', 'E', '0', 'F', + '1', '0', '1', '1', '1', '2', '1', '3', + '1', '4', '1', '5', '1', '6', '1', '7', + '1', '8', '1', '9', '1', 'A', '1', 'B', + '1', 'C', '1', 'D', '1', 'E', '1', 'F', + '2', '0', '2', '1', '2', '2', '2', '3', + '2', '4', '2', '5', '2', '6', '2', '7', + '2', '8', '2', '9', '2', 'A', '2', 'B', + '2', 'C', '2', 'D', '2', 'E', '2', 'F', + '3', '0', '3', '1', '3', '2', '3', '3', + '3', '4', '3', '5', '3', '6', '3', '7', + '3', '8', '3', '9', '3', 'A', '3', 'B', + '3', 'C', '3', 'D', '3', 'E', '3', 'F', + '4', '0', '4', '1', '4', '2', '4', '3', + '4', '4', '4', '5', '4', '6', '4', '7', + '4', '8', '4', '9', '4', 'A', '4', 'B', + '4', 'C', '4', 'D', '4', 'E', '4', 'F', + '5', '0', '5', '1', '5', '2', '5', '3', + '5', '4', '5', '5', '5', '6', '5', '7', + '5', '8', '5', '9', '5', 'A', '5', 'B', + '5', 'C', '5', 'D', '5', 'E', '5', 'F', + '6', '0', '6', '1', '6', '2', '6', '3', + '6', '4', '6', '5', '6', '6', '6', '7', + '6', '8', '6', '9', '6', 'A', '6', 'B', + '6', 'C', '6', 'D', '6', 'E', '6', 'F', + '7', '0', '7', '1', '7', '2', '7', '3', + '7', '4', '7', '5', '7', '6', '7', '7', + '7', '8', '7', '9', '7', 'A', '7', 'B', + '7', 'C', '7', 'D', '7', 'E', '7', 'F', + '8', '0', '8', '1', '8', '2', '8', '3', + '8', '4', '8', '5', '8', '6', '8', '7', + '8', '8', '8', '9', '8', 'A', '8', 'B', + '8', 'C', '8', 'D', '8', 'E', '8', 'F', + '9', '0', '9', '1', '9', '2', '9', '3', + '9', '4', '9', '5', '9', '6', '9', '7', + '9', '8', '9', '9', '9', 'A', '9', 'B', + '9', 'C', '9', 'D', '9', 'E', '9', 'F', + 'A', '0', 'A', '1', 'A', '2', 'A', '3', + 'A', '4', 'A', '5', 'A', '6', 'A', '7', + 'A', '8', 'A', '9', 'A', 'A', 'A', 'B', + 'A', 'C', 'A', 'D', 'A', 'E', 'A', 'F', + 'B', '0', 'B', '1', 'B', '2', 'B', '3', + 'B', '4', 'B', '5', 'B', '6', 'B', '7', + 'B', '8', 'B', '9', 'B', 'A', 'B', 'B', + 'B', 'C', 'B', 'D', 'B', 'E', 'B', 'F', + 'C', '0', 'C', '1', 'C', '2', 'C', '3', + 'C', '4', 'C', '5', 'C', '6', 'C', '7', + 'C', '8', 'C', '9', 'C', 'A', 'C', 'B', + 'C', 'C', 'C', 'D', 'C', 'E', 'C', 'F', + 'D', '0', 'D', '1', 'D', '2', 'D', '3', + 'D', '4', 'D', '5', 'D', '6', 'D', '7', + 'D', '8', 'D', '9', 'D', 'A', 'D', 'B', + 'D', 'C', 'D', 'D', 'D', 'E', 'D', 'F', + 'E', '0', 'E', '1', 'E', '2', 'E', '3', + 'E', '4', 'E', '5', 'E', '6', 'E', '7', + 'E', '8', 'E', '9', 'E', 'A', 'E', 'B', + 'E', 'C', 'E', 'D', 'E', 'E', 'E', 'F', + 'F', '0', 'F', '1', 'F', '2', 'F', '3', + 'F', '4', 'F', '5', 'F', '6', 'F', '7', + 'F', '8', 'F', '9', 'F', 'A', 'F', 'B', + 'F', 'C', 'F', 'D', 'F', 'E', 'F', 'F' +}; + +/** Escaped single character table. (generate with misc/make_tables.c) */ +yyjson_align(2) +static const u8 esc_single_char_table[512] = { + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + '\\', 'b', '\\', 't', '\\', 'n', ' ', ' ', + '\\', 'f', '\\', 'r', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', '\\', '"', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', '\\', '/', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + '\\', '\\', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' +}; + +/** Returns the encode table with options. */ +static_inline const char_enc_type *get_enc_table_with_flag( + yyjson_read_flag flg) { + if (has_write_flag(ESCAPE_UNICODE)) { + if (has_write_flag(ESCAPE_SLASHES)) { + return enc_table_esc_slash; + } else { + return enc_table_esc; + } + } else { + if (has_write_flag(ESCAPE_SLASHES)) { + return enc_table_cpy_slash; + } else { + return enc_table_cpy; + } + } +} + +/** Write raw string. */ +static_inline u8 *write_raw(u8 *cur, const u8 *raw, usize raw_len) { + memcpy(cur, raw, raw_len); + return cur + raw_len; +} + +/** + Write string no-escape. + @param cur Buffer cursor. + @param str A UTF-8 string, null-terminator is not required. + @param str_len Length of string in bytes. + @return The buffer cursor after string. + */ +static_inline u8 *write_string_noesc(u8 *cur, const u8 *str, usize str_len) { + *cur++ = '"'; + while (str_len >= 16) { + byte_copy_16(cur, str); + cur += 16; + str += 16; + str_len -= 16; + } + while (str_len >= 4) { + byte_copy_4(cur, str); + cur += 4; + str += 4; + str_len -= 4; + } + while (str_len) { + *cur++ = *str++; + str_len -= 1; + } + *cur++ = '"'; + return cur; +} + +/** + Write UTF-8 string (requires len * 6 + 2 bytes buffer). + @param cur Buffer cursor. + @param esc Escape unicode. + @param inv Allow invalid unicode. + @param str A UTF-8 string, null-terminator is not required. + @param str_len Length of string in bytes. + @param enc_table Encode type table for character. + @return The buffer cursor after string, or NULL on invalid unicode. + */ +static_inline u8 *write_string(u8 *cur, bool esc, bool inv, + const u8 *str, usize str_len, + const char_enc_type *enc_table) { + + /* UTF-8 character mask and pattern, see `read_string()` for details. */ +#if YYJSON_ENDIAN == YYJSON_BIG_ENDIAN + const u16 b2_mask = 0xE0C0UL; + const u16 b2_patt = 0xC080UL; + const u16 b2_requ = 0x1E00UL; + const u32 b3_mask = 0xF0C0C000UL; + const u32 b3_patt = 0xE0808000UL; + const u32 b3_requ = 0x0F200000UL; + const u32 b3_erro = 0x0D200000UL; + const u32 b4_mask = 0xF8C0C0C0UL; + const u32 b4_patt = 0xF0808080UL; + const u32 b4_requ = 0x07300000UL; + const u32 b4_err0 = 0x04000000UL; + const u32 b4_err1 = 0x03300000UL; +#elif YYJSON_ENDIAN == YYJSON_LITTLE_ENDIAN + const u16 b2_mask = 0xC0E0UL; + const u16 b2_patt = 0x80C0UL; + const u16 b2_requ = 0x001EUL; + const u32 b3_mask = 0x00C0C0F0UL; + const u32 b3_patt = 0x008080E0UL; + const u32 b3_requ = 0x0000200FUL; + const u32 b3_erro = 0x0000200DUL; + const u32 b4_mask = 0xC0C0C0F8UL; + const u32 b4_patt = 0x808080F0UL; + const u32 b4_requ = 0x00003007UL; + const u32 b4_err0 = 0x00000004UL; + const u32 b4_err1 = 0x00003003UL; +#else + /* this should be evaluated at compile-time */ + v16_uni b2_mask_uni = {{ 0xE0, 0xC0 }}; + v16_uni b2_patt_uni = {{ 0xC0, 0x80 }}; + v16_uni b2_requ_uni = {{ 0x1E, 0x00 }}; + v32_uni b3_mask_uni = {{ 0xF0, 0xC0, 0xC0, 0x00 }}; + v32_uni b3_patt_uni = {{ 0xE0, 0x80, 0x80, 0x00 }}; + v32_uni b3_requ_uni = {{ 0x0F, 0x20, 0x00, 0x00 }}; + v32_uni b3_erro_uni = {{ 0x0D, 0x20, 0x00, 0x00 }}; + v32_uni b4_mask_uni = {{ 0xF8, 0xC0, 0xC0, 0xC0 }}; + v32_uni b4_patt_uni = {{ 0xF0, 0x80, 0x80, 0x80 }}; + v32_uni b4_requ_uni = {{ 0x07, 0x30, 0x00, 0x00 }}; + v32_uni b4_err0_uni = {{ 0x04, 0x00, 0x00, 0x00 }}; + v32_uni b4_err1_uni = {{ 0x03, 0x30, 0x00, 0x00 }}; + u16 b2_mask = b2_mask_uni.u; + u16 b2_patt = b2_patt_uni.u; + u16 b2_requ = b2_requ_uni.u; + u32 b3_mask = b3_mask_uni.u; + u32 b3_patt = b3_patt_uni.u; + u32 b3_requ = b3_requ_uni.u; + u32 b3_erro = b3_erro_uni.u; + u32 b4_mask = b4_mask_uni.u; + u32 b4_patt = b4_patt_uni.u; + u32 b4_requ = b4_requ_uni.u; + u32 b4_err0 = b4_err0_uni.u; + u32 b4_err1 = b4_err1_uni.u; +#endif + +#define is_valid_seq_2(uni) ( \ + ((uni & b2_mask) == b2_patt) && \ + ((uni & b2_requ)) \ +) + +#define is_valid_seq_3(uni) ( \ + ((uni & b3_mask) == b3_patt) && \ + ((tmp = (uni & b3_requ))) && \ + ((tmp != b3_erro)) \ +) + +#define is_valid_seq_4(uni) ( \ + ((uni & b4_mask) == b4_patt) && \ + ((tmp = (uni & b4_requ))) && \ + ((tmp & b4_err0) == 0 || (tmp & b4_err1) == 0) \ +) + + /* The replacement character U+FFFD, used to indicate invalid character. */ + const v32 rep = {{ 'F', 'F', 'F', 'D' }}; + const v32 pre = {{ '\\', 'u', '0', '0' }}; + + const u8 *src = str; + const u8 *end = str + str_len; + *cur++ = '"'; + +copy_ascii: + /* + Copy continuous ASCII, loop unrolling, same as the following code: + + while (end > src) ( + if (unlikely(enc_table[*src])) break; + *cur++ = *src++; + ); + */ +#define expr_jump(i) \ + if (unlikely(enc_table[src[i]])) goto stop_char_##i; + +#define expr_stop(i) \ + stop_char_##i: \ + memcpy(cur, src, i); \ + cur += i; src += i; goto copy_utf8; + + while (end - src >= 16) { + repeat16_incr(expr_jump) + byte_copy_16(cur, src); + cur += 16; src += 16; + } + + while (end - src >= 4) { + repeat4_incr(expr_jump) + byte_copy_4(cur, src); + cur += 4; src += 4; + } + + while (end > src) { + expr_jump(0) + *cur++ = *src++; + } + + *cur++ = '"'; + return cur; + + repeat16_incr(expr_stop) + +#undef expr_jump +#undef expr_stop + +copy_utf8: + if (unlikely(src + 4 > end)) { + if (end == src) goto copy_end; + if (end - src < enc_table[*src] / 2) goto err_one; + } + switch (enc_table[*src]) { + case CHAR_ENC_CPY_1: { + *cur++ = *src++; + goto copy_ascii; + } + case CHAR_ENC_CPY_2: { + u16 v; +#if YYJSON_DISABLE_UTF8_VALIDATION + byte_copy_2(cur, src); +#else + v = byte_load_2(src); + if (unlikely(!is_valid_seq_2(v))) goto err_cpy; + byte_copy_2(cur, src); +#endif + cur += 2; + src += 2; + goto copy_utf8; + } + case CHAR_ENC_CPY_3: { + u32 v, tmp; +#if YYJSON_DISABLE_UTF8_VALIDATION + if (likely(src + 4 <= end)) { + byte_copy_4(cur, src); + } else { + byte_copy_2(cur, src); + cur[2] = src[2]; + } +#else + if (likely(src + 4 <= end)) { + v = byte_load_4(src); + if (unlikely(!is_valid_seq_3(v))) goto err_cpy; + byte_copy_4(cur, src); + } else { + v = byte_load_3(src); + if (unlikely(!is_valid_seq_3(v))) goto err_cpy; + byte_copy_4(cur, &v); + } +#endif + cur += 3; + src += 3; + goto copy_utf8; + } + case CHAR_ENC_CPY_4: { + u32 v, tmp; +#if YYJSON_DISABLE_UTF8_VALIDATION + byte_copy_4(cur, src); +#else + v = byte_load_4(src); + if (unlikely(!is_valid_seq_4(v))) goto err_cpy; + byte_copy_4(cur, src); +#endif + cur += 4; + src += 4; + goto copy_utf8; + } + case CHAR_ENC_ESC_A: { + byte_copy_2(cur, &esc_single_char_table[*src * 2]); + cur += 2; + src += 1; + goto copy_utf8; + } + case CHAR_ENC_ESC_1: { + byte_copy_4(cur + 0, &pre); + byte_copy_2(cur + 4, &esc_hex_char_table[*src * 2]); + cur += 6; + src += 1; + goto copy_utf8; + } + case CHAR_ENC_ESC_2: { + u16 u, v; +#if !YYJSON_DISABLE_UTF8_VALIDATION + v = byte_load_2(src); + if (unlikely(!is_valid_seq_2(v))) goto err_esc; +#endif + u = (u16)(((u16)(src[0] & 0x1F) << 6) | + ((u16)(src[1] & 0x3F) << 0)); + byte_copy_2(cur + 0, &pre); + byte_copy_2(cur + 2, &esc_hex_char_table[(u >> 8) * 2]); + byte_copy_2(cur + 4, &esc_hex_char_table[(u & 0xFF) * 2]); + cur += 6; + src += 2; + goto copy_utf8; + } + case CHAR_ENC_ESC_3: { + u16 u; + u32 v, tmp; +#if !YYJSON_DISABLE_UTF8_VALIDATION + v = byte_load_3(src); + if (unlikely(!is_valid_seq_3(v))) goto err_esc; +#endif + u = (u16)(((u16)(src[0] & 0x0F) << 12) | + ((u16)(src[1] & 0x3F) << 6) | + ((u16)(src[2] & 0x3F) << 0)); + byte_copy_2(cur + 0, &pre); + byte_copy_2(cur + 2, &esc_hex_char_table[(u >> 8) * 2]); + byte_copy_2(cur + 4, &esc_hex_char_table[(u & 0xFF) * 2]); + cur += 6; + src += 3; + goto copy_utf8; + } + case CHAR_ENC_ESC_4: { + u32 hi, lo, u, v, tmp; +#if !YYJSON_DISABLE_UTF8_VALIDATION + v = byte_load_4(src); + if (unlikely(!is_valid_seq_4(v))) goto err_esc; +#endif + u = ((u32)(src[0] & 0x07) << 18) | + ((u32)(src[1] & 0x3F) << 12) | + ((u32)(src[2] & 0x3F) << 6) | + ((u32)(src[3] & 0x3F) << 0); + u -= 0x10000; + hi = (u >> 10) + 0xD800; + lo = (u & 0x3FF) + 0xDC00; + byte_copy_2(cur + 0, &pre); + byte_copy_2(cur + 2, &esc_hex_char_table[(hi >> 8) * 2]); + byte_copy_2(cur + 4, &esc_hex_char_table[(hi & 0xFF) * 2]); + byte_copy_2(cur + 6, &pre); + byte_copy_2(cur + 8, &esc_hex_char_table[(lo >> 8) * 2]); + byte_copy_2(cur + 10, &esc_hex_char_table[(lo & 0xFF) * 2]); + cur += 12; + src += 4; + goto copy_utf8; + } + case CHAR_ENC_ERR_1: { + goto err_one; + } + default: break; + } + +copy_end: + *cur++ = '"'; + return cur; + +err_one: + if (esc) goto err_esc; + else goto err_cpy; + +err_cpy: + if (!inv) return NULL; + *cur++ = *src++; + goto copy_utf8; + +err_esc: + if (!inv) return NULL; + byte_copy_2(cur + 0, &pre); + byte_copy_4(cur + 2, &rep); + cur += 6; + src += 1; + goto copy_utf8; + +#undef is_valid_seq_2 +#undef is_valid_seq_3 +#undef is_valid_seq_4 +} + + + +/*============================================================================== + * Writer Utilities + *============================================================================*/ + +/** Write null (requires 8 bytes buffer). */ +static_inline u8 *write_null(u8 *cur) { + v64 v = {{ 'n', 'u', 'l', 'l', ',', '\n', 0, 0 }}; + byte_copy_8(cur, &v); + return cur + 4; +} + +/** Write bool (requires 8 bytes buffer). */ +static_inline u8 *write_bool(u8 *cur, bool val) { + v64 v0 = {{ 'f', 'a', 'l', 's', 'e', ',', '\n', 0 }}; + v64 v1 = {{ 't', 'r', 'u', 'e', ',', '\n', 0, 0 }}; + if (val) { + byte_copy_8(cur, &v1); + } else { + byte_copy_8(cur, &v0); + } + return cur + 5 - val; +} + +/** Write indent (requires level x 4 bytes buffer). + Param spaces should not larger than 4. */ +static_inline u8 *write_indent(u8 *cur, usize level, usize spaces) { + while (level-- > 0) { + byte_copy_4(cur, " "); + cur += spaces; + } + return cur; +} + +/** Write data to file pointer. */ +static bool write_dat_to_fp(FILE *fp, u8 *dat, usize len, + yyjson_write_err *err) { + if (fwrite(dat, len, 1, fp) != 1) { + err->msg = "file writing failed"; + err->code = YYJSON_WRITE_ERROR_FILE_WRITE; + return false; + } + return true; +} + +/** Write data to file. */ +static bool write_dat_to_file(const char *path, u8 *dat, usize len, + yyjson_write_err *err) { + +#define return_err(_code, _msg) do { \ + err->msg = _msg; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + if (file) fclose(file); \ + return false; \ +} while (false) + + FILE *file = fopen_writeonly(path); + if (file == NULL) { + return_err(FILE_OPEN, "file opening failed"); + } + if (fwrite(dat, len, 1, file) != 1) { + return_err(FILE_WRITE, "file writing failed"); + } + if (fclose(file) != 0) { + file = NULL; + return_err(FILE_WRITE, "file closing failed"); + } + return true; + +#undef return_err +} + + + +/*============================================================================== + * JSON Writer Implementation + *============================================================================*/ + +typedef struct yyjson_write_ctx { + usize tag; +} yyjson_write_ctx; + +static_inline void yyjson_write_ctx_set(yyjson_write_ctx *ctx, + usize size, bool is_obj) { + ctx->tag = (size << 1) | (usize)is_obj; +} + +static_inline void yyjson_write_ctx_get(yyjson_write_ctx *ctx, + usize *size, bool *is_obj) { + usize tag = ctx->tag; + *size = tag >> 1; + *is_obj = (bool)(tag & 1); +} + +/** Write single JSON value. */ +static_inline u8 *yyjson_write_single(yyjson_val *val, + yyjson_write_flag flg, + yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { + +#define return_err(_code, _msg) do { \ + if (hdr) alc.free(alc.ctx, (void *)hdr); \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + hdr = (u8 *)alc.malloc(alc.ctx, _len); \ + if (!hdr) goto fail_alloc; \ + cur = hdr; \ +} while (false) + +#define check_str_len(_len) do { \ + if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + u8 *hdr = NULL, *cur; + usize str_len; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool cpy = (enc_table == enc_table_cpy); + bool esc = has_write_flag(ESCAPE_UNICODE) != 0; + bool inv = has_write_flag(ALLOW_INVALID_UNICODE) != 0; + + switch (unsafe_yyjson_get_type(val)) { + case YYJSON_TYPE_RAW: + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 1); + cur = write_raw(cur, str_ptr, str_len); + break; + + case YYJSON_TYPE_STR: + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 4); + if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { + cur = write_string_noesc(cur, str_ptr, str_len); + } else { + cur = write_string(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + } + break; + + case YYJSON_TYPE_NUM: + incr_len(32); + cur = write_number(cur, val, flg); + if (unlikely(!cur)) goto fail_num; + break; + + case YYJSON_TYPE_BOOL: + incr_len(8); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + break; + + case YYJSON_TYPE_NULL: + incr_len(8); + cur = write_null(cur); + break; + + case YYJSON_TYPE_ARR: + incr_len(4); + byte_copy_2(cur, "[]"); + cur += 2; + break; + + case YYJSON_TYPE_OBJ: + incr_len(4); + byte_copy_2(cur, "{}"); + cur += 2; + break; + + default: + goto fail_type; + } + + *cur = '\0'; + *dat_len = (usize)(cur - hdr); + memset(err, 0, sizeof(yyjson_write_err)); + return hdr; + +fail_alloc: + return_err(MEMORY_ALLOCATION, "memory allocation failed"); +fail_type: + return_err(INVALID_VALUE_TYPE, "invalid JSON value type"); +fail_num: + return_err(NAN_OR_INF, "nan or inf number is not allowed"); +fail_str: + return_err(INVALID_STRING, "invalid utf-8 encoding in string"); + +#undef return_err +#undef check_str_len +#undef incr_len +} + +/** Write JSON document minify. + The root of this document should be a non-empty container. */ +static_inline u8 *yyjson_write_minify(const yyjson_val *root, + const yyjson_write_flag flg, + const yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { + +#define return_err(_code, _msg) do { \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + if (hdr) alc.free(alc.ctx, hdr); \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + ext_len = (usize)(_len); \ + if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ + alc_inc = yyjson_max(alc_len / 2, ext_len); \ + alc_inc = size_align_up(alc_inc, sizeof(yyjson_write_ctx)); \ + if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \ + goto fail_alloc; \ + alc_len += alc_inc; \ + tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ + if (unlikely(!tmp)) goto fail_alloc; \ + ctx_len = (usize)(end - (u8 *)ctx); \ + ctx_tmp = (yyjson_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ + memmove((void *)ctx_tmp, (void *)(tmp + ((u8 *)ctx - hdr)), ctx_len); \ + ctx = ctx_tmp; \ + cur = tmp + (cur - hdr); \ + end = tmp + alc_len; \ + hdr = tmp; \ + } \ +} while (false) + +#define check_str_len(_len) do { \ + if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + yyjson_val *val; + yyjson_type val_type; + usize ctn_len, ctn_len_tmp; + bool ctn_obj, ctn_obj_tmp, is_key; + u8 *hdr, *cur, *end, *tmp; + yyjson_write_ctx *ctx, *ctx_tmp; + usize alc_len, alc_inc, ctx_len, ext_len, str_len; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool cpy = (enc_table == enc_table_cpy); + bool esc = has_write_flag(ESCAPE_UNICODE) != 0; + bool inv = has_write_flag(ALLOW_INVALID_UNICODE) != 0; + + alc_len = root->uni.ofs / sizeof(yyjson_val); + alc_len = alc_len * YYJSON_WRITER_ESTIMATED_MINIFY_RATIO + 64; + alc_len = size_align_up(alc_len, sizeof(yyjson_write_ctx)); + hdr = (u8 *)alc.malloc(alc.ctx, alc_len); + if (!hdr) goto fail_alloc; + cur = hdr; + end = hdr + alc_len; + ctx = (yyjson_write_ctx *)(void *)end; + +doc_begin: + val = constcast(yyjson_val *)root; + val_type = unsafe_yyjson_get_type(val); + ctn_obj = (val_type == YYJSON_TYPE_OBJ); + ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + val++; + +val_begin: + val_type = unsafe_yyjson_get_type(val); + if (val_type == YYJSON_TYPE_STR) { + is_key = ((u8)ctn_obj & (u8)~ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 16); + if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { + cur = write_string_noesc(cur, str_ptr, str_len); + } else { + cur = write_string(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + } + *cur++ = is_key ? ':' : ','; + goto val_end; + } + if (val_type == YYJSON_TYPE_NUM) { + incr_len(32); + cur = write_number(cur, val, flg); + if (unlikely(!cur)) goto fail_num; + *cur++ = ','; + goto val_end; + } + if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == + (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { + ctn_len_tmp = unsafe_yyjson_get_len(val); + ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); + incr_len(16); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); + *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); + *cur++ = ','; + goto val_end; + } else { + /* push context, setup new container */ + yyjson_write_ctx_set(--ctx, ctn_len, ctn_obj); + ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; + ctn_obj = ctn_obj_tmp; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + val++; + goto val_begin; + } + } + if (val_type == YYJSON_TYPE_BOOL) { + incr_len(16); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + cur++; + goto val_end; + } + if (val_type == YYJSON_TYPE_NULL) { + incr_len(16); + cur = write_null(cur); + cur++; + goto val_end; + } + if (val_type == YYJSON_TYPE_RAW) { + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 2); + cur = write_raw(cur, str_ptr, str_len); + *cur++ = ','; + goto val_end; + } + goto fail_type; + +val_end: + val++; + ctn_len--; + if (unlikely(ctn_len == 0)) goto ctn_end; + goto val_begin; + +ctn_end: + cur--; + *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); + *cur++ = ','; + if (unlikely((u8 *)ctx >= end)) goto doc_end; + yyjson_write_ctx_get(ctx++, &ctn_len, &ctn_obj); + ctn_len--; + if (likely(ctn_len > 0)) { + goto val_begin; + } else { + goto ctn_end; + } + +doc_end: + *--cur = '\0'; + *dat_len = (usize)(cur - hdr); + memset(err, 0, sizeof(yyjson_write_err)); + return hdr; + +fail_alloc: + return_err(MEMORY_ALLOCATION, "memory allocation failed"); +fail_type: + return_err(INVALID_VALUE_TYPE, "invalid JSON value type"); +fail_num: + return_err(NAN_OR_INF, "nan or inf number is not allowed"); +fail_str: + return_err(INVALID_STRING, "invalid utf-8 encoding in string"); + +#undef return_err +#undef incr_len +#undef check_str_len +} + +/** Write JSON document pretty. + The root of this document should be a non-empty container. */ +static_inline u8 *yyjson_write_pretty(const yyjson_val *root, + const yyjson_write_flag flg, + const yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { + +#define return_err(_code, _msg) do { \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + if (hdr) alc.free(alc.ctx, hdr); \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + ext_len = (usize)(_len); \ + if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ + alc_inc = yyjson_max(alc_len / 2, ext_len); \ + alc_inc = size_align_up(alc_inc, sizeof(yyjson_write_ctx)); \ + if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \ + goto fail_alloc; \ + alc_len += alc_inc; \ + tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ + if (unlikely(!tmp)) goto fail_alloc; \ + ctx_len = (usize)(end - (u8 *)ctx); \ + ctx_tmp = (yyjson_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ + memmove((void *)ctx_tmp, (void *)(tmp + ((u8 *)ctx - hdr)), ctx_len); \ + ctx = ctx_tmp; \ + cur = tmp + (cur - hdr); \ + end = tmp + alc_len; \ + hdr = tmp; \ + } \ +} while (false) + +#define check_str_len(_len) do { \ + if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + yyjson_val *val; + yyjson_type val_type; + usize ctn_len, ctn_len_tmp; + bool ctn_obj, ctn_obj_tmp, is_key, no_indent; + u8 *hdr, *cur, *end, *tmp; + yyjson_write_ctx *ctx, *ctx_tmp; + usize alc_len, alc_inc, ctx_len, ext_len, str_len, level; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool cpy = (enc_table == enc_table_cpy); + bool esc = has_write_flag(ESCAPE_UNICODE) != 0; + bool inv = has_write_flag(ALLOW_INVALID_UNICODE) != 0; + usize spaces = has_write_flag(PRETTY_TWO_SPACES) ? 2 : 4; + + alc_len = root->uni.ofs / sizeof(yyjson_val); + alc_len = alc_len * YYJSON_WRITER_ESTIMATED_PRETTY_RATIO + 64; + alc_len = size_align_up(alc_len, sizeof(yyjson_write_ctx)); + hdr = (u8 *)alc.malloc(alc.ctx, alc_len); + if (!hdr) goto fail_alloc; + cur = hdr; + end = hdr + alc_len; + ctx = (yyjson_write_ctx *)(void *)end; + +doc_begin: + val = constcast(yyjson_val *)root; + val_type = unsafe_yyjson_get_type(val); + ctn_obj = (val_type == YYJSON_TYPE_OBJ); + ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + *cur++ = '\n'; + val++; + level = 1; + +val_begin: + val_type = unsafe_yyjson_get_type(val); + if (val_type == YYJSON_TYPE_STR) { + is_key = (bool)((u8)ctn_obj & (u8)~ctn_len); + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { + cur = write_string_noesc(cur, str_ptr, str_len); + } else { + cur = write_string(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + } + *cur++ = is_key ? ':' : ','; + *cur++ = is_key ? ' ' : '\n'; + goto val_end; + } + if (val_type == YYJSON_TYPE_NUM) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(32 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_number(cur, val, flg); + if (unlikely(!cur)) goto fail_num; + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } + if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == + (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + ctn_len_tmp = unsafe_yyjson_get_len(val); + ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); + *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } else { + /* push context, setup new container */ + incr_len(32 + (no_indent ? 0 : level * 4)); + yyjson_write_ctx_set(--ctx, ctn_len, ctn_obj); + ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; + ctn_obj = ctn_obj_tmp; + cur = write_indent(cur, no_indent ? 0 : level, spaces); + level++; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + *cur++ = '\n'; + val++; + goto val_begin; + } + } + if (val_type == YYJSON_TYPE_BOOL) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + cur += 2; + goto val_end; + } + if (val_type == YYJSON_TYPE_NULL) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_null(cur); + cur += 2; + goto val_end; + } + if (val_type == YYJSON_TYPE_RAW) { + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 3); + cur = write_raw(cur, str_ptr, str_len); + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } + goto fail_type; + +val_end: + val++; + ctn_len--; + if (unlikely(ctn_len == 0)) goto ctn_end; + goto val_begin; + +ctn_end: + cur -= 2; + *cur++ = '\n'; + incr_len(level * 4); + cur = write_indent(cur, --level, spaces); + *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); + if (unlikely((u8 *)ctx >= end)) goto doc_end; + yyjson_write_ctx_get(ctx++, &ctn_len, &ctn_obj); + ctn_len--; + *cur++ = ','; + *cur++ = '\n'; + if (likely(ctn_len > 0)) { + goto val_begin; + } else { + goto ctn_end; + } + +doc_end: + *cur = '\0'; + *dat_len = (usize)(cur - hdr); + memset(err, 0, sizeof(yyjson_write_err)); + return hdr; + +fail_alloc: + return_err(MEMORY_ALLOCATION, "memory allocation failed"); +fail_type: + return_err(INVALID_VALUE_TYPE, "invalid JSON value type"); +fail_num: + return_err(NAN_OR_INF, "nan or inf number is not allowed"); +fail_str: + return_err(INVALID_STRING, "invalid utf-8 encoding in string"); + +#undef return_err +#undef incr_len +#undef check_str_len +} + +char *yyjson_val_write_opts(const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + yyjson_write_err dummy_err; + usize dummy_dat_len; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + yyjson_val *root = constcast(yyjson_val *)val; + + err = err ? err : &dummy_err; + dat_len = dat_len ? dat_len : &dummy_dat_len; + + if (unlikely(!root)) { + *dat_len = 0; + err->msg = "input JSON is NULL"; + err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; + return NULL; + } + + if (!unsafe_yyjson_is_ctn(root) || unsafe_yyjson_get_len(root) == 0) { + return (char *)yyjson_write_single(root, flg, alc, dat_len, err); + } else if (flg & (YYJSON_WRITE_PRETTY | YYJSON_WRITE_PRETTY_TWO_SPACES)) { + return (char *)yyjson_write_pretty(root, flg, alc, dat_len, err); + } else { + return (char *)yyjson_write_minify(root, flg, alc, dat_len, err); + } +} + +char *yyjson_write_opts(const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + yyjson_val *root = doc ? doc->root : NULL; + return yyjson_val_write_opts(root, flg, alc_ptr, dat_len, err); +} + +bool yyjson_val_write_file(const char *path, + const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_write_err dummy_err; + u8 *dat; + usize dat_len = 0; + yyjson_val *root = constcast(yyjson_val *)val; + bool suc; + + alc_ptr = alc_ptr ? alc_ptr : &YYJSON_DEFAULT_ALC; + err = err ? err : &dummy_err; + if (unlikely(!path || !*path)) { + err->msg = "input path is invalid"; + err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; + return false; + } + + dat = (u8 *)yyjson_val_write_opts(root, flg, alc_ptr, &dat_len, err); + if (unlikely(!dat)) return false; + suc = write_dat_to_file(path, dat, dat_len, err); + alc_ptr->free(alc_ptr->ctx, dat); + return suc; +} + +bool yyjson_val_write_fp(FILE *fp, + const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_write_err dummy_err; + u8 *dat; + usize dat_len = 0; + yyjson_val *root = constcast(yyjson_val *)val; + bool suc; + + alc_ptr = alc_ptr ? alc_ptr : &YYJSON_DEFAULT_ALC; + err = err ? err : &dummy_err; + if (unlikely(!fp)) { + err->msg = "input fp is invalid"; + err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; + return false; + } + + dat = (u8 *)yyjson_val_write_opts(root, flg, alc_ptr, &dat_len, err); + if (unlikely(!dat)) return false; + suc = write_dat_to_fp(fp, dat, dat_len, err); + alc_ptr->free(alc_ptr->ctx, dat); + return suc; +} + +bool yyjson_write_file(const char *path, + const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_val *root = doc ? doc->root : NULL; + return yyjson_val_write_file(path, root, flg, alc_ptr, err); +} + +bool yyjson_write_fp(FILE *fp, + const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_val *root = doc ? doc->root : NULL; + return yyjson_val_write_fp(fp, root, flg, alc_ptr, err); +} + + + +/*============================================================================== + * Mutable JSON Writer Implementation + *============================================================================*/ + +typedef struct yyjson_mut_write_ctx { + usize tag; + yyjson_mut_val *ctn; +} yyjson_mut_write_ctx; + +static_inline void yyjson_mut_write_ctx_set(yyjson_mut_write_ctx *ctx, + yyjson_mut_val *ctn, + usize size, bool is_obj) { + ctx->tag = (size << 1) | (usize)is_obj; + ctx->ctn = ctn; +} + +static_inline void yyjson_mut_write_ctx_get(yyjson_mut_write_ctx *ctx, + yyjson_mut_val **ctn, + usize *size, bool *is_obj) { + usize tag = ctx->tag; + *size = tag >> 1; + *is_obj = (bool)(tag & 1); + *ctn = ctx->ctn; +} + +/** Get the estimated number of values for the mutable JSON document. */ +static_inline usize yyjson_mut_doc_estimated_val_num( + const yyjson_mut_doc *doc) { + usize sum = 0; + yyjson_val_chunk *chunk = doc->val_pool.chunks; + while (chunk) { + sum += chunk->chunk_size / sizeof(yyjson_mut_val) - 1; + if (chunk == doc->val_pool.chunks) { + sum -= (usize)(doc->val_pool.end - doc->val_pool.cur); + } + chunk = chunk->next; + } + return sum; +} + +/** Write single JSON value. */ +static_inline u8 *yyjson_mut_write_single(yyjson_mut_val *val, + yyjson_write_flag flg, + yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { + return yyjson_write_single((yyjson_val *)val, flg, alc, dat_len, err); +} + +/** Write JSON document minify. + The root of this document should be a non-empty container. */ +static_inline u8 *yyjson_mut_write_minify(const yyjson_mut_val *root, + usize estimated_val_num, + yyjson_write_flag flg, + yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { + +#define return_err(_code, _msg) do { \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + if (hdr) alc.free(alc.ctx, hdr); \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + ext_len = (usize)(_len); \ + if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ + alc_inc = yyjson_max(alc_len / 2, ext_len); \ + alc_inc = size_align_up(alc_inc, sizeof(yyjson_mut_write_ctx)); \ + if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \ + goto fail_alloc; \ + alc_len += alc_inc; \ + tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ + if (unlikely(!tmp)) goto fail_alloc; \ + ctx_len = (usize)(end - (u8 *)ctx); \ + ctx_tmp = (yyjson_mut_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ + memmove((void *)ctx_tmp, (void *)(tmp + ((u8 *)ctx - hdr)), ctx_len); \ + ctx = ctx_tmp; \ + cur = tmp + (cur - hdr); \ + end = tmp + alc_len; \ + hdr = tmp; \ + } \ +} while (false) + +#define check_str_len(_len) do { \ + if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + yyjson_mut_val *val, *ctn; + yyjson_type val_type; + usize ctn_len, ctn_len_tmp; + bool ctn_obj, ctn_obj_tmp, is_key; + u8 *hdr, *cur, *end, *tmp; + yyjson_mut_write_ctx *ctx, *ctx_tmp; + usize alc_len, alc_inc, ctx_len, ext_len, str_len; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool cpy = (enc_table == enc_table_cpy); + bool esc = has_write_flag(ESCAPE_UNICODE) != 0; + bool inv = has_write_flag(ALLOW_INVALID_UNICODE) != 0; + + alc_len = estimated_val_num * YYJSON_WRITER_ESTIMATED_MINIFY_RATIO + 64; + alc_len = size_align_up(alc_len, sizeof(yyjson_mut_write_ctx)); + hdr = (u8 *)alc.malloc(alc.ctx, alc_len); + if (!hdr) goto fail_alloc; + cur = hdr; + end = hdr + alc_len; + ctx = (yyjson_mut_write_ctx *)(void *)end; + +doc_begin: + val = constcast(yyjson_mut_val *)root; + val_type = unsafe_yyjson_get_type(val); + ctn_obj = (val_type == YYJSON_TYPE_OBJ); + ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + ctn = val; + val = (yyjson_mut_val *)val->uni.ptr; /* tail */ + val = ctn_obj ? val->next->next : val->next; + +val_begin: + val_type = unsafe_yyjson_get_type(val); + if (val_type == YYJSON_TYPE_STR) { + is_key = ((u8)ctn_obj & (u8)~ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 16); + if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { + cur = write_string_noesc(cur, str_ptr, str_len); + } else { + cur = write_string(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + } + *cur++ = is_key ? ':' : ','; + goto val_end; + } + if (val_type == YYJSON_TYPE_NUM) { + incr_len(32); + cur = write_number(cur, (yyjson_val *)val, flg); + if (unlikely(!cur)) goto fail_num; + *cur++ = ','; + goto val_end; + } + if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == + (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { + ctn_len_tmp = unsafe_yyjson_get_len(val); + ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); + incr_len(16); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); + *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); + *cur++ = ','; + goto val_end; + } else { + /* push context, setup new container */ + yyjson_mut_write_ctx_set(--ctx, ctn, ctn_len, ctn_obj); + ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; + ctn_obj = ctn_obj_tmp; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + ctn = val; + val = (yyjson_mut_val *)ctn->uni.ptr; /* tail */ + val = ctn_obj ? val->next->next : val->next; + goto val_begin; + } + } + if (val_type == YYJSON_TYPE_BOOL) { + incr_len(16); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + cur++; + goto val_end; + } + if (val_type == YYJSON_TYPE_NULL) { + incr_len(16); + cur = write_null(cur); + cur++; + goto val_end; + } + if (val_type == YYJSON_TYPE_RAW) { + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 2); + cur = write_raw(cur, str_ptr, str_len); + *cur++ = ','; + goto val_end; + } + goto fail_type; + +val_end: + ctn_len--; + if (unlikely(ctn_len == 0)) goto ctn_end; + val = val->next; + goto val_begin; + +ctn_end: + cur--; + *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); + *cur++ = ','; + if (unlikely((u8 *)ctx >= end)) goto doc_end; + val = ctn->next; + yyjson_mut_write_ctx_get(ctx++, &ctn, &ctn_len, &ctn_obj); + ctn_len--; + if (likely(ctn_len > 0)) { + goto val_begin; + } else { + goto ctn_end; + } + +doc_end: + *--cur = '\0'; + *dat_len = (usize)(cur - hdr); + err->code = YYJSON_WRITE_SUCCESS; + err->msg = "success"; + return hdr; + +fail_alloc: + return_err(MEMORY_ALLOCATION, "memory allocation failed"); +fail_type: + return_err(INVALID_VALUE_TYPE, "invalid JSON value type"); +fail_num: + return_err(NAN_OR_INF, "nan or inf number is not allowed"); +fail_str: + return_err(INVALID_STRING, "invalid utf-8 encoding in string"); + +#undef return_err +#undef incr_len +#undef check_str_len +} + +/** Write JSON document pretty. + The root of this document should be a non-empty container. */ +static_inline u8 *yyjson_mut_write_pretty(const yyjson_mut_val *root, + usize estimated_val_num, + yyjson_write_flag flg, + yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { + +#define return_err(_code, _msg) do { \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + if (hdr) alc.free(alc.ctx, hdr); \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + ext_len = (usize)(_len); \ + if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ + alc_inc = yyjson_max(alc_len / 2, ext_len); \ + alc_inc = size_align_up(alc_inc, sizeof(yyjson_mut_write_ctx)); \ + if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \ + goto fail_alloc; \ + alc_len += alc_inc; \ + tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ + if (unlikely(!tmp)) goto fail_alloc; \ + ctx_len = (usize)(end - (u8 *)ctx); \ + ctx_tmp = (yyjson_mut_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ + memmove((void *)ctx_tmp, (void *)(tmp + ((u8 *)ctx - hdr)), ctx_len); \ + ctx = ctx_tmp; \ + cur = tmp + (cur - hdr); \ + end = tmp + alc_len; \ + hdr = tmp; \ + } \ +} while (false) + +#define check_str_len(_len) do { \ + if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + yyjson_mut_val *val, *ctn; + yyjson_type val_type; + usize ctn_len, ctn_len_tmp; + bool ctn_obj, ctn_obj_tmp, is_key, no_indent; + u8 *hdr, *cur, *end, *tmp; + yyjson_mut_write_ctx *ctx, *ctx_tmp; + usize alc_len, alc_inc, ctx_len, ext_len, str_len, level; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool cpy = (enc_table == enc_table_cpy); + bool esc = has_write_flag(ESCAPE_UNICODE) != 0; + bool inv = has_write_flag(ALLOW_INVALID_UNICODE) != 0; + usize spaces = has_write_flag(PRETTY_TWO_SPACES) ? 2 : 4; + + alc_len = estimated_val_num * YYJSON_WRITER_ESTIMATED_PRETTY_RATIO + 64; + alc_len = size_align_up(alc_len, sizeof(yyjson_mut_write_ctx)); + hdr = (u8 *)alc.malloc(alc.ctx, alc_len); + if (!hdr) goto fail_alloc; + cur = hdr; + end = hdr + alc_len; + ctx = (yyjson_mut_write_ctx *)(void *)end; + +doc_begin: + val = constcast(yyjson_mut_val *)root; + val_type = unsafe_yyjson_get_type(val); + ctn_obj = (val_type == YYJSON_TYPE_OBJ); + ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + *cur++ = '\n'; + ctn = val; + val = (yyjson_mut_val *)val->uni.ptr; /* tail */ + val = ctn_obj ? val->next->next : val->next; + level = 1; + +val_begin: + val_type = unsafe_yyjson_get_type(val); + if (val_type == YYJSON_TYPE_STR) { + is_key = (bool)((u8)ctn_obj & (u8)~ctn_len); + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { + cur = write_string_noesc(cur, str_ptr, str_len); + } else { + cur = write_string(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + } + *cur++ = is_key ? ':' : ','; + *cur++ = is_key ? ' ' : '\n'; + goto val_end; + } + if (val_type == YYJSON_TYPE_NUM) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(32 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_number(cur, (yyjson_val *)val, flg); + if (unlikely(!cur)) goto fail_num; + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } + if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == + (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + ctn_len_tmp = unsafe_yyjson_get_len(val); + ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); + *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } else { + /* push context, setup new container */ + incr_len(32 + (no_indent ? 0 : level * 4)); + yyjson_mut_write_ctx_set(--ctx, ctn, ctn_len, ctn_obj); + ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; + ctn_obj = ctn_obj_tmp; + cur = write_indent(cur, no_indent ? 0 : level, spaces); + level++; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + *cur++ = '\n'; + ctn = val; + val = (yyjson_mut_val *)ctn->uni.ptr; /* tail */ + val = ctn_obj ? val->next->next : val->next; + goto val_begin; + } + } + if (val_type == YYJSON_TYPE_BOOL) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + cur += 2; + goto val_end; + } + if (val_type == YYJSON_TYPE_NULL) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_null(cur); + cur += 2; + goto val_end; + } + if (val_type == YYJSON_TYPE_RAW) { + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 3); + cur = write_raw(cur, str_ptr, str_len); + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } + goto fail_type; + +val_end: + ctn_len--; + if (unlikely(ctn_len == 0)) goto ctn_end; + val = val->next; + goto val_begin; + +ctn_end: + cur -= 2; + *cur++ = '\n'; + incr_len(level * 4); + cur = write_indent(cur, --level, spaces); + *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); + if (unlikely((u8 *)ctx >= end)) goto doc_end; + val = ctn->next; + yyjson_mut_write_ctx_get(ctx++, &ctn, &ctn_len, &ctn_obj); + ctn_len--; + *cur++ = ','; + *cur++ = '\n'; + if (likely(ctn_len > 0)) { + goto val_begin; + } else { + goto ctn_end; + } + +doc_end: + *cur = '\0'; + *dat_len = (usize)(cur - hdr); + err->code = YYJSON_WRITE_SUCCESS; + err->msg = "success"; + return hdr; + +fail_alloc: + return_err(MEMORY_ALLOCATION, "memory allocation failed"); +fail_type: + return_err(INVALID_VALUE_TYPE, "invalid JSON value type"); +fail_num: + return_err(NAN_OR_INF, "nan or inf number is not allowed"); +fail_str: + return_err(INVALID_STRING, "invalid utf-8 encoding in string"); + +#undef return_err +#undef incr_len +#undef check_str_len +} + +static char *yyjson_mut_write_opts_impl(const yyjson_mut_val *val, + usize estimated_val_num, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + yyjson_write_err dummy_err; + usize dummy_dat_len; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + yyjson_mut_val *root = constcast(yyjson_mut_val *)val; + + err = err ? err : &dummy_err; + dat_len = dat_len ? dat_len : &dummy_dat_len; + + if (unlikely(!root)) { + *dat_len = 0; + err->msg = "input JSON is NULL"; + err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; + return NULL; + } + + if (!unsafe_yyjson_is_ctn(root) || unsafe_yyjson_get_len(root) == 0) { + return (char *)yyjson_mut_write_single(root, flg, alc, dat_len, err); + } else if (flg & (YYJSON_WRITE_PRETTY | YYJSON_WRITE_PRETTY_TWO_SPACES)) { + return (char *)yyjson_mut_write_pretty(root, estimated_val_num, + flg, alc, dat_len, err); + } else { + return (char *)yyjson_mut_write_minify(root, estimated_val_num, + flg, alc, dat_len, err); + } +} + +char *yyjson_mut_val_write_opts(const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + return yyjson_mut_write_opts_impl(val, 0, flg, alc_ptr, dat_len, err); +} + +char *yyjson_mut_write_opts(const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + yyjson_mut_val *root; + usize estimated_val_num; + if (likely(doc)) { + root = doc->root; + estimated_val_num = yyjson_mut_doc_estimated_val_num(doc); + } else { + root = NULL; + estimated_val_num = 0; + } + return yyjson_mut_write_opts_impl(root, estimated_val_num, + flg, alc_ptr, dat_len, err); +} + +bool yyjson_mut_val_write_file(const char *path, + const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_write_err dummy_err; + u8 *dat; + usize dat_len = 0; + yyjson_mut_val *root = constcast(yyjson_mut_val *)val; + bool suc; + + alc_ptr = alc_ptr ? alc_ptr : &YYJSON_DEFAULT_ALC; + err = err ? err : &dummy_err; + if (unlikely(!path || !*path)) { + err->msg = "input path is invalid"; + err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; + return false; + } + + dat = (u8 *)yyjson_mut_val_write_opts(root, flg, alc_ptr, &dat_len, err); + if (unlikely(!dat)) return false; + suc = write_dat_to_file(path, dat, dat_len, err); + alc_ptr->free(alc_ptr->ctx, dat); + return suc; +} + +bool yyjson_mut_val_write_fp(FILE *fp, + const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_write_err dummy_err; + u8 *dat; + usize dat_len = 0; + yyjson_mut_val *root = constcast(yyjson_mut_val *)val; + bool suc; + + alc_ptr = alc_ptr ? alc_ptr : &YYJSON_DEFAULT_ALC; + err = err ? err : &dummy_err; + if (unlikely(!fp)) { + err->msg = "input fp is invalid"; + err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; + return false; + } + + dat = (u8 *)yyjson_mut_val_write_opts(root, flg, alc_ptr, &dat_len, err); + if (unlikely(!dat)) return false; + suc = write_dat_to_fp(fp, dat, dat_len, err); + alc_ptr->free(alc_ptr->ctx, dat); + return suc; +} + +bool yyjson_mut_write_file(const char *path, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_mut_val *root = doc ? doc->root : NULL; + return yyjson_mut_val_write_file(path, root, flg, alc_ptr, err); +} + +bool yyjson_mut_write_fp(FILE *fp, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_mut_val *root = doc ? doc->root : NULL; + return yyjson_mut_val_write_fp(fp, root, flg, alc_ptr, err); +} + +#endif /* YYJSON_DISABLE_WRITER */ diff --git a/deps/yyjson/yyjson.h b/deps/yyjson/yyjson.h new file mode 100644 index 0000000..7204597 --- /dev/null +++ b/deps/yyjson/yyjson.h @@ -0,0 +1,7814 @@ +/*============================================================================== + Copyright (c) 2020 YaoYuan + + 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. + *============================================================================*/ + +/** + @file yyjson.h + @date 2019-03-09 + @author YaoYuan + */ + +#ifndef YYJSON_H +#define YYJSON_H + + + +/*============================================================================== + * Header Files + *============================================================================*/ + +#include +#include +#include +#include +#include +#include + + + +/*============================================================================== + * Compile-time Options + *============================================================================*/ + +/* + Define as 1 to disable JSON reader if JSON parsing is not required. + + This will disable these functions at compile-time: + - yyjson_read() + - yyjson_read_opts() + - yyjson_read_file() + - yyjson_read_number() + - yyjson_mut_read_number() + + This will reduce the binary size by about 60%. + */ +#ifndef YYJSON_DISABLE_READER +#endif + +/* + Define as 1 to disable JSON writer if JSON serialization is not required. + + This will disable these functions at compile-time: + - yyjson_write() + - yyjson_write_file() + - yyjson_write_opts() + - yyjson_val_write() + - yyjson_val_write_file() + - yyjson_val_write_opts() + - yyjson_mut_write() + - yyjson_mut_write_file() + - yyjson_mut_write_opts() + - yyjson_mut_val_write() + - yyjson_mut_val_write_file() + - yyjson_mut_val_write_opts() + + This will reduce the binary size by about 30%. + */ +#ifndef YYJSON_DISABLE_WRITER +#endif + +/* + Define as 1 to disable JSON Pointer, JSON Patch and JSON Merge Patch supports. + + This will disable these functions at compile-time: + - yyjson_ptr_xxx() + - yyjson_mut_ptr_xxx() + - yyjson_doc_ptr_xxx() + - yyjson_mut_doc_ptr_xxx() + - yyjson_patch() + - yyjson_mut_patch() + - yyjson_merge_patch() + - yyjson_mut_merge_patch() + */ +#ifndef YYJSON_DISABLE_UTILS +#endif + +/* + Define as 1 to disable the fast floating-point number conversion in yyjson, + and use libc's `strtod/snprintf` instead. + + This will reduce the binary size by about 30%, but significantly slow down the + floating-point read/write speed. + */ +#ifndef YYJSON_DISABLE_FAST_FP_CONV +#endif + +/* + Define as 1 to disable non-standard JSON support at compile-time: + - Reading and writing inf/nan literal, such as `NaN`, `-Infinity`. + - Single line and multiple line comments. + - Single trailing comma at the end of an object or array. + - Invalid unicode in string value. + + This will also invalidate these run-time options: + - YYJSON_READ_ALLOW_INF_AND_NAN + - YYJSON_READ_ALLOW_COMMENTS + - YYJSON_READ_ALLOW_TRAILING_COMMAS + - YYJSON_READ_ALLOW_INVALID_UNICODE + - YYJSON_WRITE_ALLOW_INF_AND_NAN + - YYJSON_WRITE_ALLOW_INVALID_UNICODE + + This will reduce the binary size by about 10%, and speed up the reading and + writing speed by about 2% to 6%. + */ +#ifndef YYJSON_DISABLE_NON_STANDARD +#endif + +/* + Define as 1 to disable UTF-8 validation at compile time. + + If all input strings are guaranteed to be valid UTF-8 encoding (for example, + some language's String object has already validated the encoding), using this + flag can avoid redundant UTF-8 validation in yyjson. + + This flag can speed up the reading and writing speed of non-ASCII encoded + strings by about 3% to 7%. + + Note: If this flag is used while passing in illegal UTF-8 strings, the + following errors may occur: + - Escaped characters may be ignored when parsing JSON strings. + - Ending quotes may be ignored when parsing JSON strings, causing the string + to be concatenated to the next value. + - When accessing `yyjson_mut_val` for serialization, the string ending may be + accessed out of bounds, causing a segmentation fault. + */ +#ifndef YYJSON_DISABLE_UTF8_VALIDATION +#endif + +/* + Define as 1 to indicate that the target architecture does not support unaligned + memory access. Please refer to the comments in the C file for details. + */ +#ifndef YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS +#endif + +/* Define as 1 to export symbols when building this library as Windows DLL. */ +#ifndef YYJSON_EXPORTS +#endif + +/* Define as 1 to import symbols when using this library as Windows DLL. */ +#ifndef YYJSON_IMPORTS +#endif + +/* Define as 1 to include for compiler which doesn't support C99. */ +#ifndef YYJSON_HAS_STDINT_H +#endif + +/* Define as 1 to include for compiler which doesn't support C99. */ +#ifndef YYJSON_HAS_STDBOOL_H +#endif + + + +/*============================================================================== + * Compiler Macros + *============================================================================*/ + +/** compiler version (MSVC) */ +#ifdef _MSC_VER +# define YYJSON_MSC_VER _MSC_VER +#else +# define YYJSON_MSC_VER 0 +#endif + +/** compiler version (GCC) */ +#ifdef __GNUC__ +# define YYJSON_GCC_VER __GNUC__ +# if defined(__GNUC_PATCHLEVEL__) +# define yyjson_gcc_available(major, minor, patch) \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) \ + >= (major * 10000 + minor * 100 + patch)) +# else +# define yyjson_gcc_available(major, minor, patch) \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100) \ + >= (major * 10000 + minor * 100 + patch)) +# endif +#else +# define YYJSON_GCC_VER 0 +# define yyjson_gcc_available(major, minor, patch) 0 +#endif + +/** real gcc check */ +#if !defined(__clang__) && !defined(__INTEL_COMPILER) && !defined(__ICC) && \ + defined(__GNUC__) +# define YYJSON_IS_REAL_GCC 1 +#else +# define YYJSON_IS_REAL_GCC 0 +#endif + +/** C version (STDC) */ +#if defined(__STDC__) && (__STDC__ >= 1) && defined(__STDC_VERSION__) +# define YYJSON_STDC_VER __STDC_VERSION__ +#else +# define YYJSON_STDC_VER 0 +#endif + +/** C++ version */ +#if defined(__cplusplus) +# define YYJSON_CPP_VER __cplusplus +#else +# define YYJSON_CPP_VER 0 +#endif + +/** compiler builtin check (since gcc 10.0, clang 2.6, icc 2021) */ +#ifndef yyjson_has_builtin +# ifdef __has_builtin +# define yyjson_has_builtin(x) __has_builtin(x) +# else +# define yyjson_has_builtin(x) 0 +# endif +#endif + +/** compiler attribute check (since gcc 5.0, clang 2.9, icc 17) */ +#ifndef yyjson_has_attribute +# ifdef __has_attribute +# define yyjson_has_attribute(x) __has_attribute(x) +# else +# define yyjson_has_attribute(x) 0 +# endif +#endif + +/** compiler feature check (since clang 2.6, icc 17) */ +#ifndef yyjson_has_feature +# ifdef __has_feature +# define yyjson_has_feature(x) __has_feature(x) +# else +# define yyjson_has_feature(x) 0 +# endif +#endif + +/** include check (since gcc 5.0, clang 2.7, icc 16, msvc 2017 15.3) */ +#ifndef yyjson_has_include +# ifdef __has_include +# define yyjson_has_include(x) __has_include(x) +# else +# define yyjson_has_include(x) 0 +# endif +#endif + +/** inline for compiler */ +#ifndef yyjson_inline +# if YYJSON_MSC_VER >= 1200 +# define yyjson_inline __forceinline +# elif defined(_MSC_VER) +# define yyjson_inline __inline +# elif yyjson_has_attribute(always_inline) || YYJSON_GCC_VER >= 4 +# define yyjson_inline __inline__ __attribute__((always_inline)) +# elif defined(__clang__) || defined(__GNUC__) +# define yyjson_inline __inline__ +# elif defined(__cplusplus) || YYJSON_STDC_VER >= 199901L +# define yyjson_inline inline +# else +# define yyjson_inline +# endif +#endif + +/** noinline for compiler */ +#ifndef yyjson_noinline +# if YYJSON_MSC_VER >= 1400 +# define yyjson_noinline __declspec(noinline) +# elif yyjson_has_attribute(noinline) || YYJSON_GCC_VER >= 4 +# define yyjson_noinline __attribute__((noinline)) +# else +# define yyjson_noinline +# endif +#endif + +/** align for compiler */ +#ifndef yyjson_align +# if YYJSON_MSC_VER >= 1300 +# define yyjson_align(x) __declspec(align(x)) +# elif yyjson_has_attribute(aligned) || defined(__GNUC__) +# define yyjson_align(x) __attribute__((aligned(x))) +# elif YYJSON_CPP_VER >= 201103L +# define yyjson_align(x) alignas(x) +# else +# define yyjson_align(x) +# endif +#endif + +/** likely for compiler */ +#ifndef yyjson_likely +# if yyjson_has_builtin(__builtin_expect) || \ + (YYJSON_GCC_VER >= 4 && YYJSON_GCC_VER != 5) +# define yyjson_likely(expr) __builtin_expect(!!(expr), 1) +# else +# define yyjson_likely(expr) (expr) +# endif +#endif + +/** unlikely for compiler */ +#ifndef yyjson_unlikely +# if yyjson_has_builtin(__builtin_expect) || \ + (YYJSON_GCC_VER >= 4 && YYJSON_GCC_VER != 5) +# define yyjson_unlikely(expr) __builtin_expect(!!(expr), 0) +# else +# define yyjson_unlikely(expr) (expr) +# endif +#endif + +/** compile-time constant check for compiler */ +#ifndef yyjson_constant_p +# if yyjson_has_builtin(__builtin_constant_p) || (YYJSON_GCC_VER >= 3) +# define YYJSON_HAS_CONSTANT_P 1 +# define yyjson_constant_p(value) __builtin_constant_p(value) +# else +# define YYJSON_HAS_CONSTANT_P 0 +# define yyjson_constant_p(value) 0 +# endif +#endif + +/** deprecate warning */ +#ifndef yyjson_deprecated +# if YYJSON_MSC_VER >= 1400 +# define yyjson_deprecated(msg) __declspec(deprecated(msg)) +# elif yyjson_has_feature(attribute_deprecated_with_message) || \ + (YYJSON_GCC_VER > 4 || (YYJSON_GCC_VER == 4 && __GNUC_MINOR__ >= 5)) +# define yyjson_deprecated(msg) __attribute__((deprecated(msg))) +# elif YYJSON_GCC_VER >= 3 +# define yyjson_deprecated(msg) __attribute__((deprecated)) +# else +# define yyjson_deprecated(msg) +# endif +#endif + +/** function export */ +#ifndef yyjson_api +# if defined(_WIN32) +# if defined(YYJSON_EXPORTS) && YYJSON_EXPORTS +# define yyjson_api __declspec(dllexport) +# elif defined(YYJSON_IMPORTS) && YYJSON_IMPORTS +# define yyjson_api __declspec(dllimport) +# else +# define yyjson_api +# endif +# elif yyjson_has_attribute(visibility) || YYJSON_GCC_VER >= 4 +# define yyjson_api __attribute__((visibility("default"))) +# else +# define yyjson_api +# endif +#endif + +/** inline function export */ +#ifndef yyjson_api_inline +# define yyjson_api_inline static yyjson_inline +#endif + +#include +#include + +/** char bit check */ +#if defined(CHAR_BIT) +# if CHAR_BIT != 8 +# error non 8-bit char is not supported +# endif +#endif + +/** + Microsoft Visual C++ 6.0 doesn't support converting number from u64 to f64: + error C2520: conversion from unsigned __int64 to double not implemented. + */ +#ifndef YYJSON_U64_TO_F64_NO_IMPL +# if (0 < YYJSON_MSC_VER) && (YYJSON_MSC_VER <= 1200) +# define YYJSON_U64_TO_F64_NO_IMPL 1 +# else +# define YYJSON_U64_TO_F64_NO_IMPL 0 +# endif +#endif + + + +/*============================================================================== + * Compile Hint Begin + *============================================================================*/ + +/* extern "C" begin */ +#ifdef __cplusplus +extern "C" { +#endif + +/* warning suppress begin */ +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-function" +# pragma clang diagnostic ignored "-Wunused-parameter" +#elif defined(__GNUC__) +# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic push +# endif +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wunused-parameter" +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable:4800) /* 'int': forcing value to 'true' or 'false' */ +#endif + + + +/*============================================================================== + * Version + *============================================================================*/ + +/** The major version of yyjson. */ +#define YYJSON_VERSION_MAJOR 0 + +/** The minor version of yyjson. */ +#define YYJSON_VERSION_MINOR 8 + +/** The patch version of yyjson. */ +#define YYJSON_VERSION_PATCH 0 + +/** The version of yyjson in hex: `(major << 16) | (minor << 8) | (patch)`. */ +#define YYJSON_VERSION_HEX 0x000800 + +/** The version string of yyjson. */ +#define YYJSON_VERSION_STRING "0.8.0" + +/** The version of yyjson in hex, same as `YYJSON_VERSION_HEX`. */ +yyjson_api uint32_t yyjson_version(void); + + + +/*============================================================================== + * JSON Types + *============================================================================*/ + +/** Type of a JSON value (3 bit). */ +typedef uint8_t yyjson_type; +/** No type, invalid. */ +#define YYJSON_TYPE_NONE ((uint8_t)0) /* _____000 */ +/** Raw string type, no subtype. */ +#define YYJSON_TYPE_RAW ((uint8_t)1) /* _____001 */ +/** Null type: `null` literal, no subtype. */ +#define YYJSON_TYPE_NULL ((uint8_t)2) /* _____010 */ +/** Boolean type, subtype: TRUE, FALSE. */ +#define YYJSON_TYPE_BOOL ((uint8_t)3) /* _____011 */ +/** Number type, subtype: UINT, SINT, REAL. */ +#define YYJSON_TYPE_NUM ((uint8_t)4) /* _____100 */ +/** String type, subtype: NONE, NOESC. */ +#define YYJSON_TYPE_STR ((uint8_t)5) /* _____101 */ +/** Array type, no subtype. */ +#define YYJSON_TYPE_ARR ((uint8_t)6) /* _____110 */ +/** Object type, no subtype. */ +#define YYJSON_TYPE_OBJ ((uint8_t)7) /* _____111 */ + +/** Subtype of a JSON value (2 bit). */ +typedef uint8_t yyjson_subtype; +/** No subtype. */ +#define YYJSON_SUBTYPE_NONE ((uint8_t)(0 << 3)) /* ___00___ */ +/** False subtype: `false` literal. */ +#define YYJSON_SUBTYPE_FALSE ((uint8_t)(0 << 3)) /* ___00___ */ +/** True subtype: `true` literal. */ +#define YYJSON_SUBTYPE_TRUE ((uint8_t)(1 << 3)) /* ___01___ */ +/** Unsigned integer subtype: `uint64_t`. */ +#define YYJSON_SUBTYPE_UINT ((uint8_t)(0 << 3)) /* ___00___ */ +/** Signed integer subtype: `int64_t`. */ +#define YYJSON_SUBTYPE_SINT ((uint8_t)(1 << 3)) /* ___01___ */ +/** Real number subtype: `double`. */ +#define YYJSON_SUBTYPE_REAL ((uint8_t)(2 << 3)) /* ___10___ */ +/** String that do not need to be escaped for writing (internal use). */ +#define YYJSON_SUBTYPE_NOESC ((uint8_t)(1 << 3)) /* ___01___ */ + +/** The mask used to extract the type of a JSON value. */ +#define YYJSON_TYPE_MASK ((uint8_t)0x07) /* _____111 */ +/** The number of bits used by the type. */ +#define YYJSON_TYPE_BIT ((uint8_t)3) +/** The mask used to extract the subtype of a JSON value. */ +#define YYJSON_SUBTYPE_MASK ((uint8_t)0x18) /* ___11___ */ +/** The number of bits used by the subtype. */ +#define YYJSON_SUBTYPE_BIT ((uint8_t)2) +/** The mask used to extract the reserved bits of a JSON value. */ +#define YYJSON_RESERVED_MASK ((uint8_t)0xE0) /* 111_____ */ +/** The number of reserved bits. */ +#define YYJSON_RESERVED_BIT ((uint8_t)3) +/** The mask used to extract the tag of a JSON value. */ +#define YYJSON_TAG_MASK ((uint8_t)0xFF) /* 11111111 */ +/** The number of bits used by the tag. */ +#define YYJSON_TAG_BIT ((uint8_t)8) + +/** Padding size for JSON reader. */ +#define YYJSON_PADDING_SIZE 4 + + + +/*============================================================================== + * Allocator + *============================================================================*/ + +/** + A memory allocator. + + Typically you don't need to use it, unless you want to customize your own + memory allocator. + */ +typedef struct yyjson_alc { + /** Same as libc's malloc(size), should not be NULL. */ + void *(*malloc)(void *ctx, size_t size); + /** Same as libc's realloc(ptr, size), should not be NULL. */ + void *(*realloc)(void *ctx, void *ptr, size_t old_size, size_t size); + /** Same as libc's free(ptr), should not be NULL. */ + void (*free)(void *ctx, void *ptr); + /** A context for malloc/realloc/free, can be NULL. */ + void *ctx; +} yyjson_alc; + +/** + A pool allocator uses fixed length pre-allocated memory. + + This allocator may be used to avoid malloc/realloc calls. The pre-allocated + memory should be held by the caller. The maximum amount of memory required to + read a JSON can be calculated using the `yyjson_read_max_memory_usage()` + function, but the amount of memory required to write a JSON cannot be directly + calculated. + + This is not a general-purpose allocator. It is designed to handle a single JSON + data at a time. If it is used for overly complex memory tasks, such as parsing + multiple JSON documents using the same allocator but releasing only a few of + them, it may cause memory fragmentation, resulting in performance degradation + and memory waste. + + @param alc The allocator to be initialized. + If this parameter is NULL, the function will fail and return false. + If `buf` or `size` is invalid, this will be set to an empty allocator. + @param buf The buffer memory for this allocator. + If this parameter is NULL, the function will fail and return false. + @param size The size of `buf`, in bytes. + If this parameter is less than 8 words (32/64 bytes on 32/64-bit OS), the + function will fail and return false. + @return true if the `alc` has been successfully initialized. + + @par Example + @code + // parse JSON with stack memory + char buf[1024]; + yyjson_alc alc; + yyjson_alc_pool_init(&alc, buf, 1024); + + const char *json = "{\"name\":\"Helvetica\",\"size\":16}" + yyjson_doc *doc = yyjson_read_opts(json, strlen(json), 0, &alc, NULL); + // the memory of `doc` is on the stack + @endcode + + @warning This Allocator is not thread-safe. + */ +yyjson_api bool yyjson_alc_pool_init(yyjson_alc *alc, void *buf, size_t size); + +/** + A dynamic allocator. + + This allocator has a similar usage to the pool allocator above. However, when + there is not enough memory, this allocator will dynamically request more memory + using libc's `malloc` function, and frees it all at once when it is destroyed. + + @return A new dynamic allocator, or NULL if memory allocation failed. + @note The returned value should be freed with `yyjson_alc_dyn_free()`. + + @warning This Allocator is not thread-safe. + */ +yyjson_api yyjson_alc *yyjson_alc_dyn_new(void); + +/** + Free a dynamic allocator which is created by `yyjson_alc_dyn_new()`. + @param alc The dynamic allocator to be destroyed. + */ +yyjson_api void yyjson_alc_dyn_free(yyjson_alc *alc); + + + +/*============================================================================== + * JSON Structure + *============================================================================*/ + +/** + An immutable document for reading JSON. + This document holds memory for all its JSON values and strings. When it is no + longer used, the user should call `yyjson_doc_free()` to free its memory. + */ +typedef struct yyjson_doc yyjson_doc; + +/** + An immutable value for reading JSON. + A JSON Value has the same lifetime as its document. The memory is held by its + document and and cannot be freed alone. + */ +typedef struct yyjson_val yyjson_val; + +/** + A mutable document for building JSON. + This document holds memory for all its JSON values and strings. When it is no + longer used, the user should call `yyjson_mut_doc_free()` to free its memory. + */ +typedef struct yyjson_mut_doc yyjson_mut_doc; + +/** + A mutable value for building JSON. + A JSON Value has the same lifetime as its document. The memory is held by its + document and and cannot be freed alone. + */ +typedef struct yyjson_mut_val yyjson_mut_val; + + + +/*============================================================================== + * JSON Reader API + *============================================================================*/ + +/** Run-time options for JSON reader. */ +typedef uint32_t yyjson_read_flag; + +/** Default option (RFC 8259 compliant): + - Read positive integer as uint64_t. + - Read negative integer as int64_t. + - Read floating-point number as double with round-to-nearest mode. + - Read integer which cannot fit in uint64_t or int64_t as double. + - Report error if double number is infinity. + - Report error if string contains invalid UTF-8 character or BOM. + - Report error on trailing commas, comments, inf and nan literals. */ +static const yyjson_read_flag YYJSON_READ_NOFLAG = 0; + +/** Read the input data in-situ. + This option allows the reader to modify and use input data to store string + values, which can increase reading speed slightly. + The caller should hold the input data before free the document. + The input data must be padded by at least `YYJSON_PADDING_SIZE` bytes. + For example: `[1,2]` should be `[1,2]\0\0\0\0`, input length should be 5. */ +static const yyjson_read_flag YYJSON_READ_INSITU = 1 << 0; + +/** Stop when done instead of issuing an error if there's additional content + after a JSON document. This option may be used to parse small pieces of JSON + in larger data, such as `NDJSON`. */ +static const yyjson_read_flag YYJSON_READ_STOP_WHEN_DONE = 1 << 1; + +/** Allow single trailing comma at the end of an object or array, + such as `[1,2,3,]`, `{"a":1,"b":2,}` (non-standard). */ +static const yyjson_read_flag YYJSON_READ_ALLOW_TRAILING_COMMAS = 1 << 2; + +/** Allow C-style single line and multiple line comments (non-standard). */ +static const yyjson_read_flag YYJSON_READ_ALLOW_COMMENTS = 1 << 3; + +/** Allow inf/nan number and literal, case-insensitive, + such as 1e999, NaN, inf, -Infinity (non-standard). */ +static const yyjson_read_flag YYJSON_READ_ALLOW_INF_AND_NAN = 1 << 4; + +/** Read all numbers as raw strings (value with `YYJSON_TYPE_RAW` type), + inf/nan literal is also read as raw with `ALLOW_INF_AND_NAN` flag. */ +static const yyjson_read_flag YYJSON_READ_NUMBER_AS_RAW = 1 << 5; + +/** Allow reading invalid unicode when parsing string values (non-standard). + Invalid characters will be allowed to appear in the string values, but + invalid escape sequences will still be reported as errors. + This flag does not affect the performance of correctly encoded strings. + + @warning Strings in JSON values may contain incorrect encoding when this + option is used, you need to handle these strings carefully to avoid security + risks. */ +static const yyjson_read_flag YYJSON_READ_ALLOW_INVALID_UNICODE = 1 << 6; + +/** Read big numbers as raw strings. These big numbers include integers that + cannot be represented by `int64_t` and `uint64_t`, and floating-point + numbers that cannot be represented by finite `double`. + The flag will be overridden by `YYJSON_READ_NUMBER_AS_RAW` flag. */ +static const yyjson_read_flag YYJSON_READ_BIGNUM_AS_RAW = 1 << 7; + + + +/** Result code for JSON reader. */ +typedef uint32_t yyjson_read_code; + +/** Success, no error. */ +static const yyjson_read_code YYJSON_READ_SUCCESS = 0; + +/** Invalid parameter, such as NULL input string or 0 input length. */ +static const yyjson_read_code YYJSON_READ_ERROR_INVALID_PARAMETER = 1; + +/** Memory allocation failure occurs. */ +static const yyjson_read_code YYJSON_READ_ERROR_MEMORY_ALLOCATION = 2; + +/** Input JSON string is empty. */ +static const yyjson_read_code YYJSON_READ_ERROR_EMPTY_CONTENT = 3; + +/** Unexpected content after document, such as `[123]abc`. */ +static const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_CONTENT = 4; + +/** Unexpected ending, such as `[123`. */ +static const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_END = 5; + +/** Unexpected character inside the document, such as `[abc]`. */ +static const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_CHARACTER = 6; + +/** Invalid JSON structure, such as `[1,]`. */ +static const yyjson_read_code YYJSON_READ_ERROR_JSON_STRUCTURE = 7; + +/** Invalid comment, such as unclosed multi-line comment. */ +static const yyjson_read_code YYJSON_READ_ERROR_INVALID_COMMENT = 8; + +/** Invalid number, such as `123.e12`, `000`. */ +static const yyjson_read_code YYJSON_READ_ERROR_INVALID_NUMBER = 9; + +/** Invalid string, such as invalid escaped character inside a string. */ +static const yyjson_read_code YYJSON_READ_ERROR_INVALID_STRING = 10; + +/** Invalid JSON literal, such as `truu`. */ +static const yyjson_read_code YYJSON_READ_ERROR_LITERAL = 11; + +/** Failed to open a file. */ +static const yyjson_read_code YYJSON_READ_ERROR_FILE_OPEN = 12; + +/** Failed to read a file. */ +static const yyjson_read_code YYJSON_READ_ERROR_FILE_READ = 13; + +/** Error information for JSON reader. */ +typedef struct yyjson_read_err { + /** Error code, see `yyjson_read_code` for all possible values. */ + yyjson_read_code code; + /** Error message, constant, no need to free (NULL if success). */ + const char *msg; + /** Error byte position for input data (0 if success). */ + size_t pos; +} yyjson_read_err; + + + +/** + Read JSON with options. + + This function is thread-safe when: + 1. The `dat` is not modified by other threads. + 2. The `alc` is thread-safe or NULL. + + @param dat The JSON data (UTF-8 without BOM), null-terminator is not required. + If this parameter is NULL, the function will fail and return NULL. + The `dat` will not be modified without the flag `YYJSON_READ_INSITU`, so you + can pass a `const char *` string and case it to `char *` if you don't use + the `YYJSON_READ_INSITU` flag. + @param len The length of JSON data in bytes. + If this parameter is 0, the function will fail and return NULL. + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON reader. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON document, or NULL if an error occurs. + When it's no longer needed, it should be freed with `yyjson_doc_free()`. + */ +yyjson_api yyjson_doc *yyjson_read_opts(char *dat, + size_t len, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err); + +/** + Read a JSON file. + + This function is thread-safe when: + 1. The file is not modified by other threads. + 2. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + If this path is NULL or invalid, the function will fail and return NULL. + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON reader. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON document, or NULL if an error occurs. + When it's no longer needed, it should be freed with `yyjson_doc_free()`. + + @warning On 32-bit operating system, files larger than 2GB may fail to read. + */ +yyjson_api yyjson_doc *yyjson_read_file(const char *path, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err); + +/** + Read JSON from a file pointer. + + @param fp The file pointer. + The data will be read from the current position of the FILE to the end. + If this fp is NULL or invalid, the function will fail and return NULL. + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON reader. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON document, or NULL if an error occurs. + When it's no longer needed, it should be freed with `yyjson_doc_free()`. + + @warning On 32-bit operating system, files larger than 2GB may fail to read. + */ +yyjson_api yyjson_doc *yyjson_read_fp(FILE *fp, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err); + +/** + Read a JSON string. + + This function is thread-safe. + + @param dat The JSON data (UTF-8 without BOM), null-terminator is not required. + If this parameter is NULL, the function will fail and return NULL. + @param len The length of JSON data in bytes. + If this parameter is 0, the function will fail and return NULL. + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + @return A new JSON document, or NULL if an error occurs. + When it's no longer needed, it should be freed with `yyjson_doc_free()`. + */ +yyjson_api_inline yyjson_doc *yyjson_read(const char *dat, + size_t len, + yyjson_read_flag flg) { + flg &= ~YYJSON_READ_INSITU; /* const string cannot be modified */ + return yyjson_read_opts((char *)(void *)(size_t)(const void *)dat, + len, flg, NULL, NULL); +} + +/** + Returns the size of maximum memory usage to read a JSON data. + + You may use this value to avoid malloc() or calloc() call inside the reader + to get better performance, or read multiple JSON with one piece of memory. + + @param len The length of JSON data in bytes. + @param flg The JSON read options. + @return The maximum memory size to read this JSON, or 0 if overflow. + + @par Example + @code + // read multiple JSON with same pre-allocated memory + + char *dat1, *dat2, *dat3; // JSON data + size_t len1, len2, len3; // JSON length + size_t max_len = MAX(len1, MAX(len2, len3)); + yyjson_doc *doc; + + // use one allocator for multiple JSON + size_t size = yyjson_read_max_memory_usage(max_len, 0); + void *buf = malloc(size); + yyjson_alc alc; + yyjson_alc_pool_init(&alc, buf, size); + + // no more alloc() or realloc() call during reading + doc = yyjson_read_opts(dat1, len1, 0, &alc, NULL); + yyjson_doc_free(doc); + doc = yyjson_read_opts(dat2, len2, 0, &alc, NULL); + yyjson_doc_free(doc); + doc = yyjson_read_opts(dat3, len3, 0, &alc, NULL); + yyjson_doc_free(doc); + + free(buf); + @endcode + @see yyjson_alc_pool_init() + */ +yyjson_api_inline size_t yyjson_read_max_memory_usage(size_t len, + yyjson_read_flag flg) { + /* + 1. The max value count is (json_size / 2 + 1), + for example: "[1,2,3,4]" size is 9, value count is 5. + 2. Some broken JSON may cost more memory during reading, but fail at end, + for example: "[[[[[[[[". + 3. yyjson use 16 bytes per value, see struct yyjson_val. + 4. yyjson use dynamic memory with a growth factor of 1.5. + + The max memory size is (json_size / 2 * 16 * 1.5 + padding). + */ + size_t mul = (size_t)12 + !(flg & YYJSON_READ_INSITU); + size_t pad = 256; + size_t max = (size_t)(~(size_t)0); + if (flg & YYJSON_READ_STOP_WHEN_DONE) len = len < 256 ? 256 : len; + if (len >= (max - pad - mul) / mul) return 0; + return len * mul + pad; +} + +/** + Read a JSON number. + + This function is thread-safe when data is not modified by other threads. + + @param dat The JSON data (UTF-8 without BOM), null-terminator is required. + If this parameter is NULL, the function will fail and return NULL. + @param val The output value where result is stored. + If this parameter is NULL, the function will fail and return NULL. + The value will hold either UINT or SINT or REAL number; + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + Supports `YYJSON_READ_NUMBER_AS_RAW` and `YYJSON_READ_ALLOW_INF_AND_NAN`. + @param alc The memory allocator used for long number. + It is only used when the built-in floating point reader is disabled. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return If successful, a pointer to the character after the last character + used in the conversion, NULL if an error occurs. + */ +yyjson_api const char *yyjson_read_number(const char *dat, + yyjson_val *val, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err); + +/** + Read a JSON number. + + This function is thread-safe when data is not modified by other threads. + + @param dat The JSON data (UTF-8 without BOM), null-terminator is required. + If this parameter is NULL, the function will fail and return NULL. + @param val The output value where result is stored. + If this parameter is NULL, the function will fail and return NULL. + The value will hold either UINT or SINT or REAL number; + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + Supports `YYJSON_READ_NUMBER_AS_RAW` and `YYJSON_READ_ALLOW_INF_AND_NAN`. + @param alc The memory allocator used for long number. + It is only used when the built-in floating point reader is disabled. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return If successful, a pointer to the character after the last character + used in the conversion, NULL if an error occurs. + */ +yyjson_api_inline const char *yyjson_mut_read_number(const char *dat, + yyjson_mut_val *val, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err) { + return yyjson_read_number(dat, (yyjson_val *)val, flg, alc, err); +} + + +/*============================================================================== + * JSON Writer API + *============================================================================*/ + +/** Run-time options for JSON writer. */ +typedef uint32_t yyjson_write_flag; + +/** Default option: + - Write JSON minify. + - Report error on inf or nan number. + - Report error on invalid UTF-8 string. + - Do not escape unicode or slash. */ +static const yyjson_write_flag YYJSON_WRITE_NOFLAG = 0; + +/** Write JSON pretty with 4 space indent. */ +static const yyjson_write_flag YYJSON_WRITE_PRETTY = 1 << 0; + +/** Escape unicode as `uXXXX`, make the output ASCII only. */ +static const yyjson_write_flag YYJSON_WRITE_ESCAPE_UNICODE = 1 << 1; + +/** Escape '/' as '\/'. */ +static const yyjson_write_flag YYJSON_WRITE_ESCAPE_SLASHES = 1 << 2; + +/** Write inf and nan number as 'Infinity' and 'NaN' literal (non-standard). */ +static const yyjson_write_flag YYJSON_WRITE_ALLOW_INF_AND_NAN = 1 << 3; + +/** Write inf and nan number as null literal. + This flag will override `YYJSON_WRITE_ALLOW_INF_AND_NAN` flag. */ +static const yyjson_write_flag YYJSON_WRITE_INF_AND_NAN_AS_NULL = 1 << 4; + +/** Allow invalid unicode when encoding string values (non-standard). + Invalid characters in string value will be copied byte by byte. + If `YYJSON_WRITE_ESCAPE_UNICODE` flag is also set, invalid character will be + escaped as `U+FFFD` (replacement character). + This flag does not affect the performance of correctly encoded strings. */ +static const yyjson_write_flag YYJSON_WRITE_ALLOW_INVALID_UNICODE = 1 << 5; + +/** Write JSON pretty with 2 space indent. + This flag will override `YYJSON_WRITE_PRETTY` flag. */ +static const yyjson_write_flag YYJSON_WRITE_PRETTY_TWO_SPACES = 1 << 6; + + + +/** Result code for JSON writer */ +typedef uint32_t yyjson_write_code; + +/** Success, no error. */ +static const yyjson_write_code YYJSON_WRITE_SUCCESS = 0; + +/** Invalid parameter, such as NULL document. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_PARAMETER = 1; + +/** Memory allocation failure occurs. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_MEMORY_ALLOCATION = 2; + +/** Invalid value type in JSON document. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_VALUE_TYPE = 3; + +/** NaN or Infinity number occurs. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_NAN_OR_INF = 4; + +/** Failed to open a file. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_FILE_OPEN = 5; + +/** Failed to write a file. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_FILE_WRITE = 6; + +/** Invalid unicode in string. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_STRING = 7; + +/** Error information for JSON writer. */ +typedef struct yyjson_write_err { + /** Error code, see `yyjson_write_code` for all possible values. */ + yyjson_write_code code; + /** Error message, constant, no need to free (NULL if success). */ + const char *msg; +} yyjson_write_err; + + + +/*============================================================================== + * JSON Document Writer API + *============================================================================*/ + +/** + Write a document to JSON string with options. + + This function is thread-safe when: + The `alc` is thread-safe or NULL. + + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free() or alc->free(). + */ +yyjson_api char *yyjson_write_opts(const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + size_t *len, + yyjson_write_err *err); + +/** + Write a document to JSON file with options. + + This function is thread-safe when: + 1. The file is not accessed by other threads. + 2. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + If this path is NULL or invalid, the function will fail and return false. + If this file is not empty, the content will be discarded. + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_write_file(const char *path, + const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a document to file pointer with options. + + @param fp The file pointer. + The data will be written to the current position of the file. + If this fp is NULL or invalid, the function will fail and return false. + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_write_fp(FILE *fp, + const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a document to JSON string. + + This function is thread-safe. + + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free(). + */ +yyjson_api_inline char *yyjson_write(const yyjson_doc *doc, + yyjson_write_flag flg, + size_t *len) { + return yyjson_write_opts(doc, flg, NULL, len, NULL); +} + + + +/** + Write a document to JSON string with options. + + This function is thread-safe when: + 1. The `doc` is not modified by other threads. + 2. The `alc` is thread-safe or NULL. + + @param doc The mutable JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free() or alc->free(). + */ +yyjson_api char *yyjson_mut_write_opts(const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + size_t *len, + yyjson_write_err *err); + +/** + Write a document to JSON file with options. + + This function is thread-safe when: + 1. The file is not accessed by other threads. + 2. The `doc` is not modified by other threads. + 3. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + If this path is NULL or invalid, the function will fail and return false. + If this file is not empty, the content will be discarded. + @param doc The mutable JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_mut_write_file(const char *path, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a document to file pointer with options. + + @param fp The file pointer. + The data will be written to the current position of the file. + If this fp is NULL or invalid, the function will fail and return false. + @param doc The mutable JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_mut_write_fp(FILE *fp, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a document to JSON string. + + This function is thread-safe when: + The `doc` is not modified by other threads. + + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free(). + */ +yyjson_api_inline char *yyjson_mut_write(const yyjson_mut_doc *doc, + yyjson_write_flag flg, + size_t *len) { + return yyjson_mut_write_opts(doc, flg, NULL, len, NULL); +} + + + +/*============================================================================== + * JSON Value Writer API + *============================================================================*/ + +/** + Write a value to JSON string with options. + + This function is thread-safe when: + The `alc` is thread-safe or NULL. + + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free() or alc->free(). + */ +yyjson_api char *yyjson_val_write_opts(const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + size_t *len, + yyjson_write_err *err); + +/** + Write a value to JSON file with options. + + This function is thread-safe when: + 1. The file is not accessed by other threads. + 2. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + If this path is NULL or invalid, the function will fail and return false. + If this file is not empty, the content will be discarded. + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_val_write_file(const char *path, + const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a value to file pointer with options. + + @param fp The file pointer. + The data will be written to the current position of the file. + If this path is NULL or invalid, the function will fail and return false. + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_val_write_fp(FILE *fp, + const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a value to JSON string. + + This function is thread-safe. + + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free(). + */ +yyjson_api_inline char *yyjson_val_write(const yyjson_val *val, + yyjson_write_flag flg, + size_t *len) { + return yyjson_val_write_opts(val, flg, NULL, len, NULL); +} + +/** + Write a value to JSON string with options. + + This function is thread-safe when: + 1. The `val` is not modified by other threads. + 2. The `alc` is thread-safe or NULL. + + @param val The mutable JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free() or alc->free(). + */ +yyjson_api char *yyjson_mut_val_write_opts(const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + size_t *len, + yyjson_write_err *err); + +/** + Write a value to JSON file with options. + + This function is thread-safe when: + 1. The file is not accessed by other threads. + 2. The `val` is not modified by other threads. + 3. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + If this path is NULL or invalid, the function will fail and return false. + If this file is not empty, the content will be discarded. + @param val The mutable JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_mut_val_write_file(const char *path, + const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a value to JSON file with options. + + @param fp The file pointer. + The data will be written to the current position of the file. + If this path is NULL or invalid, the function will fail and return false. + @param val The mutable JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_mut_val_write_fp(FILE *fp, + const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a value to JSON string. + + This function is thread-safe when: + The `val` is not modified by other threads. + + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free(). + */ +yyjson_api_inline char *yyjson_mut_val_write(const yyjson_mut_val *val, + yyjson_write_flag flg, + size_t *len) { + return yyjson_mut_val_write_opts(val, flg, NULL, len, NULL); +} + + + +/*============================================================================== + * JSON Document API + *============================================================================*/ + +/** Returns the root value of this JSON document. + Returns NULL if `doc` is NULL. */ +yyjson_api_inline yyjson_val *yyjson_doc_get_root(yyjson_doc *doc); + +/** Returns read size of input JSON data. + Returns 0 if `doc` is NULL. + For example: the read size of `[1,2,3]` is 7 bytes. */ +yyjson_api_inline size_t yyjson_doc_get_read_size(yyjson_doc *doc); + +/** Returns total value count in this JSON document. + Returns 0 if `doc` is NULL. + For example: the value count of `[1,2,3]` is 4. */ +yyjson_api_inline size_t yyjson_doc_get_val_count(yyjson_doc *doc); + +/** Release the JSON document and free the memory. + After calling this function, the `doc` and all values from the `doc` are no + longer available. This function will do nothing if the `doc` is NULL. */ +yyjson_api_inline void yyjson_doc_free(yyjson_doc *doc); + + + +/*============================================================================== + * JSON Value Type API + *============================================================================*/ + +/** Returns whether the JSON value is raw. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_raw(yyjson_val *val); + +/** Returns whether the JSON value is `null`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_null(yyjson_val *val); + +/** Returns whether the JSON value is `true`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_true(yyjson_val *val); + +/** Returns whether the JSON value is `false`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_false(yyjson_val *val); + +/** Returns whether the JSON value is bool (true/false). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_bool(yyjson_val *val); + +/** Returns whether the JSON value is unsigned integer (uint64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_uint(yyjson_val *val); + +/** Returns whether the JSON value is signed integer (int64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_sint(yyjson_val *val); + +/** Returns whether the JSON value is integer (uint64_t/int64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_int(yyjson_val *val); + +/** Returns whether the JSON value is real number (double). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_real(yyjson_val *val); + +/** Returns whether the JSON value is number (uint64_t/int64_t/double). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_num(yyjson_val *val); + +/** Returns whether the JSON value is string. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_str(yyjson_val *val); + +/** Returns whether the JSON value is array. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_arr(yyjson_val *val); + +/** Returns whether the JSON value is object. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_obj(yyjson_val *val); + +/** Returns whether the JSON value is container (array/object). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_ctn(yyjson_val *val); + + + +/*============================================================================== + * JSON Value Content API + *============================================================================*/ + +/** Returns the JSON value's type. + Returns YYJSON_TYPE_NONE if `val` is NULL. */ +yyjson_api_inline yyjson_type yyjson_get_type(yyjson_val *val); + +/** Returns the JSON value's subtype. + Returns YYJSON_SUBTYPE_NONE if `val` is NULL. */ +yyjson_api_inline yyjson_subtype yyjson_get_subtype(yyjson_val *val); + +/** Returns the JSON value's tag. + Returns 0 if `val` is NULL. */ +yyjson_api_inline uint8_t yyjson_get_tag(yyjson_val *val); + +/** Returns the JSON value's type description. + The return value should be one of these strings: "raw", "null", "string", + "array", "object", "true", "false", "uint", "sint", "real", "unknown". */ +yyjson_api_inline const char *yyjson_get_type_desc(yyjson_val *val); + +/** Returns the content if the value is raw. + Returns NULL if `val` is NULL or type is not raw. */ +yyjson_api_inline const char *yyjson_get_raw(yyjson_val *val); + +/** Returns the content if the value is bool. + Returns NULL if `val` is NULL or type is not bool. */ +yyjson_api_inline bool yyjson_get_bool(yyjson_val *val); + +/** Returns the content and cast to uint64_t. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline uint64_t yyjson_get_uint(yyjson_val *val); + +/** Returns the content and cast to int64_t. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline int64_t yyjson_get_sint(yyjson_val *val); + +/** Returns the content and cast to int. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline int yyjson_get_int(yyjson_val *val); + +/** Returns the content if the value is real number, or 0.0 on error. + Returns 0.0 if `val` is NULL or type is not real(double). */ +yyjson_api_inline double yyjson_get_real(yyjson_val *val); + +/** Returns the content and typecast to `double` if the value is number. + Returns 0.0 if `val` is NULL or type is not number(uint/sint/real). */ +yyjson_api_inline double yyjson_get_num(yyjson_val *val); + +/** Returns the content if the value is string. + Returns NULL if `val` is NULL or type is not string. */ +yyjson_api_inline const char *yyjson_get_str(yyjson_val *val); + +/** Returns the content length (string length, array size, object size. + Returns 0 if `val` is NULL or type is not string/array/object. */ +yyjson_api_inline size_t yyjson_get_len(yyjson_val *val); + +/** Returns whether the JSON value is equals to a string. + Returns false if input is NULL or type is not string. */ +yyjson_api_inline bool yyjson_equals_str(yyjson_val *val, const char *str); + +/** Returns whether the JSON value is equals to a string. + The `str` should be a UTF-8 string, null-terminator is not required. + Returns false if input is NULL or type is not string. */ +yyjson_api_inline bool yyjson_equals_strn(yyjson_val *val, const char *str, + size_t len); + +/** Returns whether two JSON values are equal (deep compare). + Returns false if input is NULL. + @note the result may be inaccurate if object has duplicate keys. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api_inline bool yyjson_equals(yyjson_val *lhs, yyjson_val *rhs); + +/** Set the value to raw. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_raw(yyjson_val *val, + const char *raw, size_t len); + +/** Set the value to null. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_null(yyjson_val *val); + +/** Set the value to bool. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_bool(yyjson_val *val, bool num); + +/** Set the value to uint. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_uint(yyjson_val *val, uint64_t num); + +/** Set the value to sint. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_sint(yyjson_val *val, int64_t num); + +/** Set the value to int. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_int(yyjson_val *val, int num); + +/** Set the value to real. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_real(yyjson_val *val, double num); + +/** Set the value to string (null-terminated). + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_str(yyjson_val *val, const char *str); + +/** Set the value to string (with length). + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_strn(yyjson_val *val, + const char *str, size_t len); + + + +/*============================================================================== + * JSON Array API + *============================================================================*/ + +/** Returns the number of elements in this array. + Returns 0 if `arr` is NULL or type is not array. */ +yyjson_api_inline size_t yyjson_arr_size(yyjson_val *arr); + +/** Returns the element at the specified position in this array. + Returns NULL if array is NULL/empty or the index is out of bounds. + @warning This function takes a linear search time if array is not flat. + For example: `[1,{},3]` is flat, `[1,[2],3]` is not flat. */ +yyjson_api_inline yyjson_val *yyjson_arr_get(yyjson_val *arr, size_t idx); + +/** Returns the first element of this array. + Returns NULL if `arr` is NULL/empty or type is not array. */ +yyjson_api_inline yyjson_val *yyjson_arr_get_first(yyjson_val *arr); + +/** Returns the last element of this array. + Returns NULL if `arr` is NULL/empty or type is not array. + @warning This function takes a linear search time if array is not flat. + For example: `[1,{},3]` is flat, `[1,[2],3]` is not flat.*/ +yyjson_api_inline yyjson_val *yyjson_arr_get_last(yyjson_val *arr); + + + +/*============================================================================== + * JSON Array Iterator API + *============================================================================*/ + +/** + A JSON array iterator. + + @par Example + @code + yyjson_val *val; + yyjson_arr_iter iter = yyjson_arr_iter_with(arr); + while ((val = yyjson_arr_iter_next(&iter))) { + your_func(val); + } + @endcode + */ +typedef struct yyjson_arr_iter { + size_t idx; /**< next value's index */ + size_t max; /**< maximum index (arr.size) */ + yyjson_val *cur; /**< next value */ +} yyjson_arr_iter; + +/** + Initialize an iterator for this array. + + @param arr The array to be iterated over. + If this parameter is NULL or not an array, `iter` will be set to empty. + @param iter The iterator to be initialized. + If this parameter is NULL, the function will fail and return false. + @return true if the `iter` has been successfully initialized. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline bool yyjson_arr_iter_init(yyjson_val *arr, + yyjson_arr_iter *iter); + +/** + Create an iterator with an array , same as `yyjson_arr_iter_init()`. + + @param arr The array to be iterated over. + If this parameter is NULL or not an array, an empty iterator will returned. + @return A new iterator for the array. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline yyjson_arr_iter yyjson_arr_iter_with(yyjson_val *arr); + +/** + Returns whether the iteration has more elements. + If `iter` is NULL, this function will return false. + */ +yyjson_api_inline bool yyjson_arr_iter_has_next(yyjson_arr_iter *iter); + +/** + Returns the next element in the iteration, or NULL on end. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_val *yyjson_arr_iter_next(yyjson_arr_iter *iter); + +/** + Macro for iterating over an array. + It works like iterator, but with a more intuitive API. + + @par Example + @code + size_t idx, max; + yyjson_val *val; + yyjson_arr_foreach(arr, idx, max, val) { + your_func(idx, val); + } + @endcode + */ +#define yyjson_arr_foreach(arr, idx, max, val) \ + for ((idx) = 0, \ + (max) = yyjson_arr_size(arr), \ + (val) = yyjson_arr_get_first(arr); \ + (idx) < (max); \ + (idx)++, \ + (val) = unsafe_yyjson_get_next(val)) + + + +/*============================================================================== + * JSON Object API + *============================================================================*/ + +/** Returns the number of key-value pairs in this object. + Returns 0 if `obj` is NULL or type is not object. */ +yyjson_api_inline size_t yyjson_obj_size(yyjson_val *obj); + +/** Returns the value to which the specified key is mapped. + Returns NULL if this object contains no mapping for the key. + Returns NULL if `obj/key` is NULL, or type is not object. + + The `key` should be a null-terminated UTF-8 string. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_val *yyjson_obj_get(yyjson_val *obj, const char *key); + +/** Returns the value to which the specified key is mapped. + Returns NULL if this object contains no mapping for the key. + Returns NULL if `obj/key` is NULL, or type is not object. + + The `key` should be a UTF-8 string, null-terminator is not required. + The `key_len` should be the length of the key, in bytes. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_val *yyjson_obj_getn(yyjson_val *obj, const char *key, + size_t key_len); + + + +/*============================================================================== + * JSON Object Iterator API + *============================================================================*/ + +/** + A JSON object iterator. + + @par Example + @code + yyjson_val *key, *val; + yyjson_obj_iter iter = yyjson_obj_iter_with(obj); + while ((key = yyjson_obj_iter_next(&iter))) { + val = yyjson_obj_iter_get_val(key); + your_func(key, val); + } + @endcode + + If the ordering of the keys is known at compile-time, you can use this method + to speed up value lookups: + @code + // {"k1":1, "k2": 3, "k3": 3} + yyjson_val *key, *val; + yyjson_obj_iter iter = yyjson_obj_iter_with(obj); + yyjson_val *v1 = yyjson_obj_iter_get(&iter, "k1"); + yyjson_val *v3 = yyjson_obj_iter_get(&iter, "k3"); + @endcode + @see yyjson_obj_iter_get() and yyjson_obj_iter_getn() + */ +typedef struct yyjson_obj_iter { + size_t idx; /**< next key's index */ + size_t max; /**< maximum key index (obj.size) */ + yyjson_val *cur; /**< next key */ + yyjson_val *obj; /**< the object being iterated */ +} yyjson_obj_iter; + +/** + Initialize an iterator for this object. + + @param obj The object to be iterated over. + If this parameter is NULL or not an object, `iter` will be set to empty. + @param iter The iterator to be initialized. + If this parameter is NULL, the function will fail and return false. + @return true if the `iter` has been successfully initialized. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline bool yyjson_obj_iter_init(yyjson_val *obj, + yyjson_obj_iter *iter); + +/** + Create an iterator with an object, same as `yyjson_obj_iter_init()`. + + @param obj The object to be iterated over. + If this parameter is NULL or not an object, an empty iterator will returned. + @return A new iterator for the object. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline yyjson_obj_iter yyjson_obj_iter_with(yyjson_val *obj); + +/** + Returns whether the iteration has more elements. + If `iter` is NULL, this function will return false. + */ +yyjson_api_inline bool yyjson_obj_iter_has_next(yyjson_obj_iter *iter); + +/** + Returns the next key in the iteration, or NULL on end. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_val *yyjson_obj_iter_next(yyjson_obj_iter *iter); + +/** + Returns the value for key inside the iteration. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_val *yyjson_obj_iter_get_val(yyjson_val *key); + +/** + Iterates to a specified key and returns the value. + + This function does the same thing as `yyjson_obj_get()`, but is much faster + if the ordering of the keys is known at compile-time and you are using the same + order to look up the values. If the key exists in this object, then the + iterator will stop at the next key, otherwise the iterator will not change and + NULL is returned. + + @param iter The object iterator, should not be NULL. + @param key The key, should be a UTF-8 string with null-terminator. + @return The value to which the specified key is mapped. + NULL if this object contains no mapping for the key or input is invalid. + + @warning This function takes a linear search time if the key is not nearby. + */ +yyjson_api_inline yyjson_val *yyjson_obj_iter_get(yyjson_obj_iter *iter, + const char *key); + +/** + Iterates to a specified key and returns the value. + + This function does the same thing as `yyjson_obj_getn()`, but is much faster + if the ordering of the keys is known at compile-time and you are using the same + order to look up the values. If the key exists in this object, then the + iterator will stop at the next key, otherwise the iterator will not change and + NULL is returned. + + @param iter The object iterator, should not be NULL. + @param key The key, should be a UTF-8 string, null-terminator is not required. + @param key_len The the length of `key`, in bytes. + @return The value to which the specified key is mapped. + NULL if this object contains no mapping for the key or input is invalid. + + @warning This function takes a linear search time if the key is not nearby. + */ +yyjson_api_inline yyjson_val *yyjson_obj_iter_getn(yyjson_obj_iter *iter, + const char *key, + size_t key_len); + +/** + Macro for iterating over an object. + It works like iterator, but with a more intuitive API. + + @par Example + @code + size_t idx, max; + yyjson_val *key, *val; + yyjson_obj_foreach(obj, idx, max, key, val) { + your_func(key, val); + } + @endcode + */ +#define yyjson_obj_foreach(obj, idx, max, key, val) \ + for ((idx) = 0, \ + (max) = yyjson_obj_size(obj), \ + (key) = (obj) ? unsafe_yyjson_get_first(obj) : NULL, \ + (val) = (key) + 1; \ + (idx) < (max); \ + (idx)++, \ + (key) = unsafe_yyjson_get_next(val), \ + (val) = (key) + 1) + + + +/*============================================================================== + * Mutable JSON Document API + *============================================================================*/ + +/** Returns the root value of this JSON document. + Returns NULL if `doc` is NULL. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_root(yyjson_mut_doc *doc); + +/** Sets the root value of this JSON document. + Pass NULL to clear root value of the document. */ +yyjson_api_inline void yyjson_mut_doc_set_root(yyjson_mut_doc *doc, + yyjson_mut_val *root); + +/** + Set the string pool size for a mutable document. + This function does not allocate memory immediately, but uses the size when + the next memory allocation is needed. + + If the caller knows the approximate bytes of strings that the document needs to + store (e.g. copy string with `yyjson_mut_strcpy` function), setting a larger + size can avoid multiple memory allocations and improve performance. + + @param doc The mutable document. + @param len The desired string pool size in bytes (total string length). + @return true if successful, false if size is 0 or overflow. + */ +yyjson_api bool yyjson_mut_doc_set_str_pool_size(yyjson_mut_doc *doc, + size_t len); + +/** + Set the value pool size for a mutable document. + This function does not allocate memory immediately, but uses the size when + the next memory allocation is needed. + + If the caller knows the approximate number of values that the document needs to + store (e.g. create new value with `yyjson_mut_xxx` functions), setting a larger + size can avoid multiple memory allocations and improve performance. + + @param doc The mutable document. + @param count The desired value pool size (number of `yyjson_mut_val`). + @return true if successful, false if size is 0 or overflow. + */ +yyjson_api bool yyjson_mut_doc_set_val_pool_size(yyjson_mut_doc *doc, + size_t count); + +/** Release the JSON document and free the memory. + After calling this function, the `doc` and all values from the `doc` are no + longer available. This function will do nothing if the `doc` is NULL. */ +yyjson_api void yyjson_mut_doc_free(yyjson_mut_doc *doc); + +/** Creates and returns a new mutable JSON document, returns NULL on error. + If allocator is NULL, the default allocator will be used. */ +yyjson_api yyjson_mut_doc *yyjson_mut_doc_new(const yyjson_alc *alc); + +/** Copies and returns a new mutable document from input, returns NULL on error. + This makes a `deep-copy` on the immutable document. + If allocator is NULL, the default allocator will be used. + @note `imut_doc` -> `mut_doc`. */ +yyjson_api yyjson_mut_doc *yyjson_doc_mut_copy(yyjson_doc *doc, + const yyjson_alc *alc); + +/** Copies and returns a new mutable document from input, returns NULL on error. + This makes a `deep-copy` on the mutable document. + If allocator is NULL, the default allocator will be used. + @note `mut_doc` -> `mut_doc`. */ +yyjson_api yyjson_mut_doc *yyjson_mut_doc_mut_copy(yyjson_mut_doc *doc, + const yyjson_alc *alc); + +/** Copies and returns a new mutable value from input, returns NULL on error. + This makes a `deep-copy` on the immutable value. + The memory was managed by mutable document. + @note `imut_val` -> `mut_val`. */ +yyjson_api yyjson_mut_val *yyjson_val_mut_copy(yyjson_mut_doc *doc, + yyjson_val *val); + +/** Copies and returns a new mutable value from input, returns NULL on error. + This makes a `deep-copy` on the mutable value. + The memory was managed by mutable document. + @note `mut_val` -> `mut_val`. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api yyjson_mut_val *yyjson_mut_val_mut_copy(yyjson_mut_doc *doc, + yyjson_mut_val *val); + +/** Copies and returns a new immutable document from input, + returns NULL on error. This makes a `deep-copy` on the mutable document. + The returned document should be freed with `yyjson_doc_free()`. + @note `mut_doc` -> `imut_doc`. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api yyjson_doc *yyjson_mut_doc_imut_copy(yyjson_mut_doc *doc, + const yyjson_alc *alc); + +/** Copies and returns a new immutable document from input, + returns NULL on error. This makes a `deep-copy` on the mutable value. + The returned document should be freed with `yyjson_doc_free()`. + @note `mut_val` -> `imut_doc`. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api yyjson_doc *yyjson_mut_val_imut_copy(yyjson_mut_val *val, + const yyjson_alc *alc); + + + +/*============================================================================== + * Mutable JSON Value Type API + *============================================================================*/ + +/** Returns whether the JSON value is raw. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_raw(yyjson_mut_val *val); + +/** Returns whether the JSON value is `null`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_null(yyjson_mut_val *val); + +/** Returns whether the JSON value is `true`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_true(yyjson_mut_val *val); + +/** Returns whether the JSON value is `false`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_false(yyjson_mut_val *val); + +/** Returns whether the JSON value is bool (true/false). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_bool(yyjson_mut_val *val); + +/** Returns whether the JSON value is unsigned integer (uint64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_uint(yyjson_mut_val *val); + +/** Returns whether the JSON value is signed integer (int64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_sint(yyjson_mut_val *val); + +/** Returns whether the JSON value is integer (uint64_t/int64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_int(yyjson_mut_val *val); + +/** Returns whether the JSON value is real number (double). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_real(yyjson_mut_val *val); + +/** Returns whether the JSON value is number (uint/sint/real). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_num(yyjson_mut_val *val); + +/** Returns whether the JSON value is string. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_str(yyjson_mut_val *val); + +/** Returns whether the JSON value is array. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_arr(yyjson_mut_val *val); + +/** Returns whether the JSON value is object. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_obj(yyjson_mut_val *val); + +/** Returns whether the JSON value is container (array/object). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_ctn(yyjson_mut_val *val); + + + +/*============================================================================== + * Mutable JSON Value Content API + *============================================================================*/ + +/** Returns the JSON value's type. + Returns `YYJSON_TYPE_NONE` if `val` is NULL. */ +yyjson_api_inline yyjson_type yyjson_mut_get_type(yyjson_mut_val *val); + +/** Returns the JSON value's subtype. + Returns `YYJSON_SUBTYPE_NONE` if `val` is NULL. */ +yyjson_api_inline yyjson_subtype yyjson_mut_get_subtype(yyjson_mut_val *val); + +/** Returns the JSON value's tag. + Returns 0 if `val` is NULL. */ +yyjson_api_inline uint8_t yyjson_mut_get_tag(yyjson_mut_val *val); + +/** Returns the JSON value's type description. + The return value should be one of these strings: "raw", "null", "string", + "array", "object", "true", "false", "uint", "sint", "real", "unknown". */ +yyjson_api_inline const char *yyjson_mut_get_type_desc(yyjson_mut_val *val); + +/** Returns the content if the value is raw. + Returns NULL if `val` is NULL or type is not raw. */ +yyjson_api_inline const char *yyjson_mut_get_raw(yyjson_mut_val *val); + +/** Returns the content if the value is bool. + Returns NULL if `val` is NULL or type is not bool. */ +yyjson_api_inline bool yyjson_mut_get_bool(yyjson_mut_val *val); + +/** Returns the content and cast to uint64_t. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline uint64_t yyjson_mut_get_uint(yyjson_mut_val *val); + +/** Returns the content and cast to int64_t. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline int64_t yyjson_mut_get_sint(yyjson_mut_val *val); + +/** Returns the content and cast to int. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline int yyjson_mut_get_int(yyjson_mut_val *val); + +/** Returns the content if the value is real number. + Returns 0.0 if `val` is NULL or type is not real(double). */ +yyjson_api_inline double yyjson_mut_get_real(yyjson_mut_val *val); + +/** Returns the content and typecast to `double` if the value is number. + Returns 0.0 if `val` is NULL or type is not number(uint/sint/real). */ +yyjson_api_inline double yyjson_mut_get_num(yyjson_mut_val *val); + +/** Returns the content if the value is string. + Returns NULL if `val` is NULL or type is not string. */ +yyjson_api_inline const char *yyjson_mut_get_str(yyjson_mut_val *val); + +/** Returns the content length (string length, array size, object size. + Returns 0 if `val` is NULL or type is not string/array/object. */ +yyjson_api_inline size_t yyjson_mut_get_len(yyjson_mut_val *val); + +/** Returns whether the JSON value is equals to a string. + The `str` should be a null-terminated UTF-8 string. + Returns false if input is NULL or type is not string. */ +yyjson_api_inline bool yyjson_mut_equals_str(yyjson_mut_val *val, + const char *str); + +/** Returns whether the JSON value is equals to a string. + The `str` should be a UTF-8 string, null-terminator is not required. + Returns false if input is NULL or type is not string. */ +yyjson_api_inline bool yyjson_mut_equals_strn(yyjson_mut_val *val, + const char *str, size_t len); + +/** Returns whether two JSON values are equal (deep compare). + Returns false if input is NULL. + @note the result may be inaccurate if object has duplicate keys. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api_inline bool yyjson_mut_equals(yyjson_mut_val *lhs, + yyjson_mut_val *rhs); + +/** Set the value to raw. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_raw(yyjson_mut_val *val, + const char *raw, size_t len); + +/** Set the value to null. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_null(yyjson_mut_val *val); + +/** Set the value to bool. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_bool(yyjson_mut_val *val, bool num); + +/** Set the value to uint. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_uint(yyjson_mut_val *val, uint64_t num); + +/** Set the value to sint. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_sint(yyjson_mut_val *val, int64_t num); + +/** Set the value to int. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_int(yyjson_mut_val *val, int num); + +/** Set the value to real. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_real(yyjson_mut_val *val, double num); + +/** Set the value to string (null-terminated). + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_str(yyjson_mut_val *val, const char *str); + +/** Set the value to string (with length). + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_strn(yyjson_mut_val *val, + const char *str, size_t len); + +/** Set the value to array. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_arr(yyjson_mut_val *val); + +/** Set the value to array. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_obj(yyjson_mut_val *val); + + + +/*============================================================================== + * Mutable JSON Value Creation API + *============================================================================*/ + +/** Creates and returns a raw value, returns NULL on error. + The `str` should be a null-terminated UTF-8 string. + + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_raw(yyjson_mut_doc *doc, + const char *str); + +/** Creates and returns a raw value, returns NULL on error. + The `str` should be a UTF-8 string, null-terminator is not required. + + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawn(yyjson_mut_doc *doc, + const char *str, + size_t len); + +/** Creates and returns a raw value, returns NULL on error. + The `str` should be a null-terminated UTF-8 string. + The input string is copied and held by the document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawcpy(yyjson_mut_doc *doc, + const char *str); + +/** Creates and returns a raw value, returns NULL on error. + The `str` should be a UTF-8 string, null-terminator is not required. + The input string is copied and held by the document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawncpy(yyjson_mut_doc *doc, + const char *str, + size_t len); + +/** Creates and returns a null value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_null(yyjson_mut_doc *doc); + +/** Creates and returns a true value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_true(yyjson_mut_doc *doc); + +/** Creates and returns a false value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_false(yyjson_mut_doc *doc); + +/** Creates and returns a bool value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_bool(yyjson_mut_doc *doc, + bool val); + +/** Creates and returns an unsigned integer value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_uint(yyjson_mut_doc *doc, + uint64_t num); + +/** Creates and returns a signed integer value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_sint(yyjson_mut_doc *doc, + int64_t num); + +/** Creates and returns a signed integer value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_int(yyjson_mut_doc *doc, + int64_t num); + +/** Creates and returns an real number value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_real(yyjson_mut_doc *doc, + double num); + +/** Creates and returns a string value, returns NULL on error. + The `str` should be a null-terminated UTF-8 string. + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_str(yyjson_mut_doc *doc, + const char *str); + +/** Creates and returns a string value, returns NULL on error. + The `str` should be a UTF-8 string, null-terminator is not required. + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_strn(yyjson_mut_doc *doc, + const char *str, + size_t len); + +/** Creates and returns a string value, returns NULL on error. + The `str` should be a null-terminated UTF-8 string. + The input string is copied and held by the document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_strcpy(yyjson_mut_doc *doc, + const char *str); + +/** Creates and returns a string value, returns NULL on error. + The `str` should be a UTF-8 string, null-terminator is not required. + The input string is copied and held by the document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_strncpy(yyjson_mut_doc *doc, + const char *str, + size_t len); + + + +/*============================================================================== + * Mutable JSON Array API + *============================================================================*/ + +/** Returns the number of elements in this array. + Returns 0 if `arr` is NULL or type is not array. */ +yyjson_api_inline size_t yyjson_mut_arr_size(yyjson_mut_val *arr); + +/** Returns the element at the specified position in this array. + Returns NULL if array is NULL/empty or the index is out of bounds. + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get(yyjson_mut_val *arr, + size_t idx); + +/** Returns the first element of this array. + Returns NULL if `arr` is NULL/empty or type is not array. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_first(yyjson_mut_val *arr); + +/** Returns the last element of this array. + Returns NULL if `arr` is NULL/empty or type is not array. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_last(yyjson_mut_val *arr); + + + +/*============================================================================== + * Mutable JSON Array Iterator API + *============================================================================*/ + +/** + A mutable JSON array iterator. + + @warning You should not modify the array while iterating over it, but you can + use `yyjson_mut_arr_iter_remove()` to remove current value. + + @par Example + @code + yyjson_mut_val *val; + yyjson_mut_arr_iter iter = yyjson_mut_arr_iter_with(arr); + while ((val = yyjson_mut_arr_iter_next(&iter))) { + your_func(val); + if (your_val_is_unused(val)) { + yyjson_mut_arr_iter_remove(&iter); + } + } + @endcode + */ +typedef struct yyjson_mut_arr_iter { + size_t idx; /**< next value's index */ + size_t max; /**< maximum index (arr.size) */ + yyjson_mut_val *cur; /**< current value */ + yyjson_mut_val *pre; /**< previous value */ + yyjson_mut_val *arr; /**< the array being iterated */ +} yyjson_mut_arr_iter; + +/** + Initialize an iterator for this array. + + @param arr The array to be iterated over. + If this parameter is NULL or not an array, `iter` will be set to empty. + @param iter The iterator to be initialized. + If this parameter is NULL, the function will fail and return false. + @return true if the `iter` has been successfully initialized. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline bool yyjson_mut_arr_iter_init(yyjson_mut_val *arr, + yyjson_mut_arr_iter *iter); + +/** + Create an iterator with an array , same as `yyjson_mut_arr_iter_init()`. + + @param arr The array to be iterated over. + If this parameter is NULL or not an array, an empty iterator will returned. + @return A new iterator for the array. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline yyjson_mut_arr_iter yyjson_mut_arr_iter_with( + yyjson_mut_val *arr); + +/** + Returns whether the iteration has more elements. + If `iter` is NULL, this function will return false. + */ +yyjson_api_inline bool yyjson_mut_arr_iter_has_next( + yyjson_mut_arr_iter *iter); + +/** + Returns the next element in the iteration, or NULL on end. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_next( + yyjson_mut_arr_iter *iter); + +/** + Removes and returns current element in the iteration. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_remove( + yyjson_mut_arr_iter *iter); + +/** + Macro for iterating over an array. + It works like iterator, but with a more intuitive API. + + @warning You should not modify the array while iterating over it. + + @par Example + @code + size_t idx, max; + yyjson_mut_val *val; + yyjson_mut_arr_foreach(arr, idx, max, val) { + your_func(idx, val); + } + @endcode + */ +#define yyjson_mut_arr_foreach(arr, idx, max, val) \ + for ((idx) = 0, \ + (max) = yyjson_mut_arr_size(arr), \ + (val) = yyjson_mut_arr_get_first(arr); \ + (idx) < (max); \ + (idx)++, \ + (val) = (val)->next) + + + +/*============================================================================== + * Mutable JSON Array Creation API + *============================================================================*/ + +/** + Creates and returns an empty mutable array. + @param doc A mutable document, used for memory allocation only. + @return The new array. NULL if input is NULL or memory allocation failed. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr(yyjson_mut_doc *doc); + +/** + Creates and returns a new mutable array with the given boolean values. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of boolean values. + @param count The value count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const bool vals[3] = { true, false, true }; + yyjson_mut_val *arr = yyjson_mut_arr_with_bool(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_bool( + yyjson_mut_doc *doc, const bool *vals, size_t count); + +/** + Creates and returns a new mutable array with the given sint numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of sint numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const int64_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint64(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint( + yyjson_mut_doc *doc, const int64_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const uint64_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint( + yyjson_mut_doc *doc, const uint64_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given real numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of real numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const double vals[3] = { 0.1, 0.2, 0.3 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_real(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_real( + yyjson_mut_doc *doc, const double *vals, size_t count); + +/** + Creates and returns a new mutable array with the given int8 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of int8 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const int8_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint8(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint8( + yyjson_mut_doc *doc, const int8_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given int16 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of int16 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const int16_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint16(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint16( + yyjson_mut_doc *doc, const int16_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given int32 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of int32 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const int32_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint32(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint32( + yyjson_mut_doc *doc, const int32_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given int64 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of int64 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const int64_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint64(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint64( + yyjson_mut_doc *doc, const int64_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint8 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint8 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const uint8_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint8(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint8( + yyjson_mut_doc *doc, const uint8_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint16 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint16 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const uint16_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint16(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint16( + yyjson_mut_doc *doc, const uint16_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint32 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint32 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const uint32_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint32(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint32( + yyjson_mut_doc *doc, const uint32_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint64 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint64 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const uint64_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint64(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint64( + yyjson_mut_doc *doc, const uint64_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given float numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of float numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const float vals[3] = { -1.0f, 0.0f, 1.0f }; + yyjson_mut_val *arr = yyjson_mut_arr_with_float(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_float( + yyjson_mut_doc *doc, const float *vals, size_t count); + +/** + Creates and returns a new mutable array with the given double numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of double numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const double vals[3] = { -1.0, 0.0, 1.0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_double(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_double( + yyjson_mut_doc *doc, const double *vals, size_t count); + +/** + Creates and returns a new mutable array with the given strings, these strings + will not be copied. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of UTF-8 null-terminator strings. + If this array contains NULL, the function will fail and return NULL. + @param count The number of values in `vals`. + If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @warning The input strings are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. If these strings will be + modified, you should use `yyjson_mut_arr_with_strcpy()` instead. + + @par Example + @code + const char *vals[3] = { "a", "b", "c" }; + yyjson_mut_val *arr = yyjson_mut_arr_with_str(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_str( + yyjson_mut_doc *doc, const char **vals, size_t count); + +/** + Creates and returns a new mutable array with the given strings and string + lengths, these strings will not be copied. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of UTF-8 strings, null-terminator is not required. + If this array contains NULL, the function will fail and return NULL. + @param lens A C array of string lengths, in bytes. + @param count The number of strings in `vals`. + If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @warning The input strings are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. If these strings will be + modified, you should use `yyjson_mut_arr_with_strncpy()` instead. + + @par Example + @code + const char *vals[3] = { "a", "bb", "c" }; + const size_t lens[3] = { 1, 2, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_strn(doc, vals, lens, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strn( + yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count); + +/** + Creates and returns a new mutable array with the given strings, these strings + will be copied. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of UTF-8 null-terminator strings. + If this array contains NULL, the function will fail and return NULL. + @param count The number of values in `vals`. + If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const char *vals[3] = { "a", "b", "c" }; + yyjson_mut_val *arr = yyjson_mut_arr_with_strcpy(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strcpy( + yyjson_mut_doc *doc, const char **vals, size_t count); + +/** + Creates and returns a new mutable array with the given strings and string + lengths, these strings will be copied. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of UTF-8 strings, null-terminator is not required. + If this array contains NULL, the function will fail and return NULL. + @param lens A C array of string lengths, in bytes. + @param count The number of strings in `vals`. + If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const char *vals[3] = { "a", "bb", "c" }; + const size_t lens[3] = { 1, 2, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_strn(doc, vals, lens, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strncpy( + yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count); + + + +/*============================================================================== + * Mutable JSON Array Modification API + *============================================================================*/ + +/** + Inserts a value into an array at a given index. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The value to be inserted. Returns false if it is NULL. + @param idx The index to which to insert the new value. + Returns false if the index is out of range. + @return Whether successful. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_arr_insert(yyjson_mut_val *arr, + yyjson_mut_val *val, size_t idx); + +/** + Inserts a value at the end of the array. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The value to be inserted. Returns false if it is NULL. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_append(yyjson_mut_val *arr, + yyjson_mut_val *val); + +/** + Inserts a value at the head of the array. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The value to be inserted. Returns false if it is NULL. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_prepend(yyjson_mut_val *arr, + yyjson_mut_val *val); + +/** + Replaces a value at index and returns old value. + @param arr The array to which the value is to be replaced. + Returns false if it is NULL or not an array. + @param idx The index to which to replace the value. + Returns false if the index is out of range. + @param val The new value to replace. Returns false if it is NULL. + @return Old value, or NULL on error. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_replace(yyjson_mut_val *arr, + size_t idx, + yyjson_mut_val *val); + +/** + Removes and returns a value at index. + @param arr The array from which the value is to be removed. + Returns false if it is NULL or not an array. + @param idx The index from which to remove the value. + Returns false if the index is out of range. + @return Old value, or NULL on error. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove(yyjson_mut_val *arr, + size_t idx); + +/** + Removes and returns the first value in this array. + @param arr The array from which the value is to be removed. + Returns false if it is NULL or not an array. + @return The first value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_first( + yyjson_mut_val *arr); + +/** + Removes and returns the last value in this array. + @param arr The array from which the value is to be removed. + Returns false if it is NULL or not an array. + @return The last value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_last( + yyjson_mut_val *arr); + +/** + Removes all values within a specified range in the array. + @param arr The array from which the value is to be removed. + Returns false if it is NULL or not an array. + @param idx The start index of the range (0 is the first). + @param len The number of items in the range (can be 0). + @return Whether successful. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_arr_remove_range(yyjson_mut_val *arr, + size_t idx, size_t len); + +/** + Removes all values in this array. + @param arr The array from which all of the values are to be removed. + Returns false if it is NULL or not an array. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_clear(yyjson_mut_val *arr); + +/** + Rotates values in this array for the given number of times. + For example: `[1,2,3,4,5]` rotate 2 is `[3,4,5,1,2]`. + @param arr The array to be rotated. + @param idx Index (or times) to rotate. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_arr_rotate(yyjson_mut_val *arr, + size_t idx); + + + +/*============================================================================== + * Mutable JSON Array Modification Convenience API + *============================================================================*/ + +/** + Adds a value at the end of the array. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The value to be inserted. Returns false if it is NULL. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_val(yyjson_mut_val *arr, + yyjson_mut_val *val); + +/** + Adds a `null` value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_null(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + +/** + Adds a `true` value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_true(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + +/** + Adds a `false` value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_false(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + +/** + Adds a bool value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The bool value to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_bool(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + bool val); + +/** + Adds an unsigned integer value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_uint(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + uint64_t num); + +/** + Adds a signed integer value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_sint(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + int64_t num); + +/** + Adds a integer value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_int(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + int64_t num); + +/** + Adds a double value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_real(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + double num); + +/** + Adds a string value at the end of the array (no copy). + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param str A null-terminated UTF-8 string. + @return Whether successful. + @warning The input string is not copied, you should keep this string unmodified + for the lifetime of this JSON document. + */ +yyjson_api_inline bool yyjson_mut_arr_add_str(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str); + +/** + Adds a string value at the end of the array (no copy). + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param str A UTF-8 string, null-terminator is not required. + @param len The length of the string, in bytes. + @return Whether successful. + @warning The input string is not copied, you should keep this string unmodified + for the lifetime of this JSON document. + */ +yyjson_api_inline bool yyjson_mut_arr_add_strn(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str, + size_t len); + +/** + Adds a string value at the end of the array (copied). + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param str A null-terminated UTF-8 string. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_strcpy(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str); + +/** + Adds a string value at the end of the array (copied). + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param str A UTF-8 string, null-terminator is not required. + @param len The length of the string, in bytes. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_strncpy(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str, + size_t len); + +/** + Creates and adds a new array at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return The new array, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_arr(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + +/** + Creates and adds a new object at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return The new object, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_obj(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + + + +/*============================================================================== + * Mutable JSON Object API + *============================================================================*/ + +/** Returns the number of key-value pairs in this object. + Returns 0 if `obj` is NULL or type is not object. */ +yyjson_api_inline size_t yyjson_mut_obj_size(yyjson_mut_val *obj); + +/** Returns the value to which the specified key is mapped. + Returns NULL if this object contains no mapping for the key. + Returns NULL if `obj/key` is NULL, or type is not object. + + The `key` should be a null-terminated UTF-8 string. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_get(yyjson_mut_val *obj, + const char *key); + +/** Returns the value to which the specified key is mapped. + Returns NULL if this object contains no mapping for the key. + Returns NULL if `obj/key` is NULL, or type is not object. + + The `key` should be a UTF-8 string, null-terminator is not required. + The `key_len` should be the length of the key, in bytes. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_getn(yyjson_mut_val *obj, + const char *key, + size_t key_len); + + + +/*============================================================================== + * Mutable JSON Object Iterator API + *============================================================================*/ + +/** + A mutable JSON object iterator. + + @warning You should not modify the object while iterating over it, but you can + use `yyjson_mut_obj_iter_remove()` to remove current value. + + @par Example + @code + yyjson_mut_val *key, *val; + yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(obj); + while ((key = yyjson_mut_obj_iter_next(&iter))) { + val = yyjson_mut_obj_iter_get_val(key); + your_func(key, val); + if (your_val_is_unused(key, val)) { + yyjson_mut_obj_iter_remove(&iter); + } + } + @endcode + + If the ordering of the keys is known at compile-time, you can use this method + to speed up value lookups: + @code + // {"k1":1, "k2": 3, "k3": 3} + yyjson_mut_val *key, *val; + yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(obj); + yyjson_mut_val *v1 = yyjson_mut_obj_iter_get(&iter, "k1"); + yyjson_mut_val *v3 = yyjson_mut_obj_iter_get(&iter, "k3"); + @endcode + @see `yyjson_mut_obj_iter_get()` and `yyjson_mut_obj_iter_getn()` + */ +typedef struct yyjson_mut_obj_iter { + size_t idx; /**< next key's index */ + size_t max; /**< maximum key index (obj.size) */ + yyjson_mut_val *cur; /**< current key */ + yyjson_mut_val *pre; /**< previous key */ + yyjson_mut_val *obj; /**< the object being iterated */ +} yyjson_mut_obj_iter; + +/** + Initialize an iterator for this object. + + @param obj The object to be iterated over. + If this parameter is NULL or not an array, `iter` will be set to empty. + @param iter The iterator to be initialized. + If this parameter is NULL, the function will fail and return false. + @return true if the `iter` has been successfully initialized. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline bool yyjson_mut_obj_iter_init(yyjson_mut_val *obj, + yyjson_mut_obj_iter *iter); + +/** + Create an iterator with an object, same as `yyjson_obj_iter_init()`. + + @param obj The object to be iterated over. + If this parameter is NULL or not an object, an empty iterator will returned. + @return A new iterator for the object. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline yyjson_mut_obj_iter yyjson_mut_obj_iter_with( + yyjson_mut_val *obj); + +/** + Returns whether the iteration has more elements. + If `iter` is NULL, this function will return false. + */ +yyjson_api_inline bool yyjson_mut_obj_iter_has_next( + yyjson_mut_obj_iter *iter); + +/** + Returns the next key in the iteration, or NULL on end. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_next( + yyjson_mut_obj_iter *iter); + +/** + Returns the value for key inside the iteration. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get_val( + yyjson_mut_val *key); + +/** + Removes current key-value pair in the iteration, returns the removed value. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_remove( + yyjson_mut_obj_iter *iter); + +/** + Iterates to a specified key and returns the value. + + This function does the same thing as `yyjson_mut_obj_get()`, but is much faster + if the ordering of the keys is known at compile-time and you are using the same + order to look up the values. If the key exists in this object, then the + iterator will stop at the next key, otherwise the iterator will not change and + NULL is returned. + + @param iter The object iterator, should not be NULL. + @param key The key, should be a UTF-8 string with null-terminator. + @return The value to which the specified key is mapped. + NULL if this object contains no mapping for the key or input is invalid. + + @warning This function takes a linear search time if the key is not nearby. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get( + yyjson_mut_obj_iter *iter, const char *key); + +/** + Iterates to a specified key and returns the value. + + This function does the same thing as `yyjson_mut_obj_getn()` but is much faster + if the ordering of the keys is known at compile-time and you are using the same + order to look up the values. If the key exists in this object, then the + iterator will stop at the next key, otherwise the iterator will not change and + NULL is returned. + + @param iter The object iterator, should not be NULL. + @param key The key, should be a UTF-8 string, null-terminator is not required. + @param key_len The the length of `key`, in bytes. + @return The value to which the specified key is mapped. + NULL if this object contains no mapping for the key or input is invalid. + + @warning This function takes a linear search time if the key is not nearby. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_getn( + yyjson_mut_obj_iter *iter, const char *key, size_t key_len); + +/** + Macro for iterating over an object. + It works like iterator, but with a more intuitive API. + + @warning You should not modify the object while iterating over it. + + @par Example + @code + size_t idx, max; + yyjson_val *key, *val; + yyjson_obj_foreach(obj, idx, max, key, val) { + your_func(key, val); + } + @endcode + */ +#define yyjson_mut_obj_foreach(obj, idx, max, key, val) \ + for ((idx) = 0, \ + (max) = yyjson_mut_obj_size(obj), \ + (key) = (max) ? ((yyjson_mut_val *)(obj)->uni.ptr)->next->next : NULL, \ + (val) = (key) ? (key)->next : NULL; \ + (idx) < (max); \ + (idx)++, \ + (key) = (val)->next, \ + (val) = (key)->next) + + + +/*============================================================================== + * Mutable JSON Object Creation API + *============================================================================*/ + +/** Creates and returns a mutable object, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj(yyjson_mut_doc *doc); + +/** + Creates and returns a mutable object with keys and values, returns NULL on + error. The keys and values are not copied. The strings should be a + null-terminated UTF-8 string. + + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. + + @par Example + @code + const char *keys[2] = { "id", "name" }; + const char *vals[2] = { "01", "Harry" }; + yyjson_mut_val *obj = yyjson_mut_obj_with_str(doc, keys, vals, 2); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_str(yyjson_mut_doc *doc, + const char **keys, + const char **vals, + size_t count); + +/** + Creates and returns a mutable object with key-value pairs and pair count, + returns NULL on error. The keys and values are not copied. The strings should + be a null-terminated UTF-8 string. + + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. + + @par Example + @code + const char *kv_pairs[4] = { "id", "01", "name", "Harry" }; + yyjson_mut_val *obj = yyjson_mut_obj_with_kv(doc, kv_pairs, 2); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_kv(yyjson_mut_doc *doc, + const char **kv_pairs, + size_t pair_count); + + + +/*============================================================================== + * Mutable JSON Object Modification API + *============================================================================*/ + +/** + Adds a key-value pair at the end of the object. + This function allows duplicated key in one object. + @param obj The object to which the new key-value pair is to be added. + @param key The key, should be a string which is created by `yyjson_mut_str()`, + `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`. + @param val The value to add to the object. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_obj_add(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val); +/** + Sets a key-value pair at the end of the object. + This function may remove all key-value pairs for the given key before add. + @param obj The object to which the new key-value pair is to be added. + @param key The key, should be a string which is created by `yyjson_mut_str()`, + `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`. + @param val The value to add to the object. If this value is null, the behavior + is same as `yyjson_mut_obj_remove()`. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_obj_put(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val); + +/** + Inserts a key-value pair to the object at the given position. + This function allows duplicated key in one object. + @param obj The object to which the new key-value pair is to be added. + @param key The key, should be a string which is created by `yyjson_mut_str()`, + `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`. + @param val The value to add to the object. + @param idx The index to which to insert the new pair. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_obj_insert(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val, + size_t idx); + +/** + Removes all key-value pair from the object with given key. + @param obj The object from which the key-value pair is to be removed. + @param key The key, should be a string value. + @return The first matched value, or NULL if no matched value. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove(yyjson_mut_val *obj, + yyjson_mut_val *key); + +/** + Removes all key-value pair from the object with given key. + @param obj The object from which the key-value pair is to be removed. + @param key The key, should be a UTF-8 string with null-terminator. + @return The first matched value, or NULL if no matched value. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_key( + yyjson_mut_val *obj, const char *key); + +/** + Removes all key-value pair from the object with given key. + @param obj The object from which the key-value pair is to be removed. + @param key The key, should be a UTF-8 string, null-terminator is not required. + @param key_len The length of the key. + @return The first matched value, or NULL if no matched value. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_keyn( + yyjson_mut_val *obj, const char *key, size_t key_len); + +/** + Removes all key-value pairs in this object. + @param obj The object from which all of the values are to be removed. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_obj_clear(yyjson_mut_val *obj); + +/** + Replaces value from the object with given key. + If the key is not exist, or the value is NULL, it will fail. + @param obj The object to which the value is to be replaced. + @param key The key, should be a string value. + @param val The value to replace into the object. + @return Whether successful. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_obj_replace(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val); + +/** + Rotates key-value pairs in the object for the given number of times. + For example: `{"a":1,"b":2,"c":3,"d":4}` rotate 1 is + `{"b":2,"c":3,"d":4,"a":1}`. + @param obj The object to be rotated. + @param idx Index (or times) to rotate. + @return Whether successful. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_obj_rotate(yyjson_mut_val *obj, + size_t idx); + + + +/*============================================================================== + * Mutable JSON Object Modification Convenience API + *============================================================================*/ + +/** Adds a `null` value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_null(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key); + +/** Adds a `true` value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_true(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key); + +/** Adds a `false` value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_false(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key); + +/** Adds a bool value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_bool(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, bool val); + +/** Adds an unsigned integer value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_uint(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, uint64_t val); + +/** Adds a signed integer value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_sint(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, int64_t val); + +/** Adds an int value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_int(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, int64_t val); + +/** Adds a double value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_real(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, double val); + +/** Adds a string value at the end of the object. + The `key` and `val` should be null-terminated UTF-8 strings. + This function allows duplicated key in one object. + + @warning The key/value strings are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_str(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, const char *val); + +/** Adds a string value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + The `val` should be a UTF-8 string, null-terminator is not required. + The `len` should be the length of the `val`, in bytes. + This function allows duplicated key in one object. + + @warning The key/value strings are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_strn(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *val, size_t len); + +/** Adds a string value at the end of the object. + The `key` and `val` should be null-terminated UTF-8 strings. + The value string is copied. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_strcpy(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *val); + +/** Adds a string value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + The `val` should be a UTF-8 string, null-terminator is not required. + The `len` should be the length of the `val`, in bytes. + This function allows duplicated key in one object. + + @warning The key/value strings are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_strncpy(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *val, size_t len); + +/** + Creates and adds a new array to the target object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep these strings + unmodified for the lifetime of this JSON document. + @return The new array, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_arr(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key); + +/** + Creates and adds a new object to the target object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep these strings + unmodified for the lifetime of this JSON document. + @return The new object, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_obj(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key); + +/** Adds a JSON value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_val(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + yyjson_mut_val *val); + +/** Removes all key-value pairs for the given key. + Returns the first value to which the specified key is mapped or NULL if this + object contains no mapping for the key. + The `key` should be a null-terminated UTF-8 string. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_str( + yyjson_mut_val *obj, const char *key); + +/** Removes all key-value pairs for the given key. + Returns the first value to which the specified key is mapped or NULL if this + object contains no mapping for the key. + The `key` should be a UTF-8 string, null-terminator is not required. + The `len` should be the length of the key, in bytes. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_strn( + yyjson_mut_val *obj, const char *key, size_t len); + +/** Replaces all matching keys with the new key. + Returns true if at least one key was renamed. + The `key` and `new_key` should be a null-terminated UTF-8 string. + The `new_key` is copied and held by doc. + + @warning This function takes a linear search time. + If `new_key` already exists, it will cause duplicate keys. + */ +yyjson_api_inline bool yyjson_mut_obj_rename_key(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *new_key); + +/** Replaces all matching keys with the new key. + Returns true if at least one key was renamed. + The `key` and `new_key` should be a UTF-8 string, + null-terminator is not required. The `new_key` is copied and held by doc. + + @warning This function takes a linear search time. + If `new_key` already exists, it will cause duplicate keys. + */ +yyjson_api_inline bool yyjson_mut_obj_rename_keyn(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + size_t len, + const char *new_key, + size_t new_len); + + + +/*============================================================================== + * JSON Pointer API (RFC 6901) + * https://tools.ietf.org/html/rfc6901 + *============================================================================*/ + +/** JSON Pointer error code. */ +typedef uint32_t yyjson_ptr_code; + +/** No JSON pointer error. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_NONE = 0; + +/** Invalid input parameter, such as NULL input. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_PARAMETER = 1; + +/** JSON pointer syntax error, such as invalid escape, token no prefix. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_SYNTAX = 2; + +/** JSON pointer resolve failed, such as index out of range, key not found. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_RESOLVE = 3; + +/** Document's root is NULL, but it is required for the function call. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_NULL_ROOT = 4; + +/** Cannot set root as the target is not a document. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_SET_ROOT = 5; + +/** The memory allocation failed and a new value could not be created. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_MEMORY_ALLOCATION = 6; + +/** Error information for JSON pointer. */ +typedef struct yyjson_ptr_err { + /** Error code, see `yyjson_ptr_code` for all possible values. */ + yyjson_ptr_code code; + /** Error message, constant, no need to free (NULL if no error). */ + const char *msg; + /** Error byte position for input JSON pointer (0 if no error). */ + size_t pos; +} yyjson_ptr_err; + +/** + A context for JSON pointer operation. + + This struct stores the context of JSON Pointer operation result. The struct + can be used with three helper functions: `ctx_append()`, `ctx_replace()`, and + `ctx_remove()`, which perform the corresponding operations on the container + without re-parsing the JSON Pointer. + + For example: + @code + // doc before: {"a":[0,1,null]} + // ptr: "/a/2" + val = yyjson_mut_doc_ptr_getx(doc, ptr, strlen(ptr), &ctx, &err); + if (yyjson_is_null(val)) { + yyjson_ptr_ctx_remove(&ctx); + } + // doc after: {"a":[0,1]} + @endcode + */ +typedef struct yyjson_ptr_ctx { + /** + The container (parent) of the target value. It can be either an array or + an object. If the target location has no value, but all its parent + containers exist, and the target location can be used to insert a new + value, then `ctn` is the parent container of the target location. + Otherwise, `ctn` is NULL. + */ + yyjson_mut_val *ctn; + /** + The previous sibling of the target value. It can be either a value in an + array or a key in an object. As the container is a `circular linked list` + of elements, `pre` is the previous node of the target value. If the + operation is `add` or `set`, then `pre` is the previous node of the new + value, not the original target value. If the target value does not exist, + `pre` is NULL. + */ + yyjson_mut_val *pre; + /** + The removed value if the operation is `set`, `replace` or `remove`. It can + be used to restore the original state of the document if needed. + */ + yyjson_mut_val *old; +} yyjson_ptr_ctx; + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_doc_ptr_get(yyjson_doc *doc, + const char *ptr); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_doc_ptr_getn(yyjson_doc *doc, + const char *ptr, size_t len); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param err A pointer to store the error information, or NULL if not needed. + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_doc_ptr_getx(yyjson_doc *doc, + const char *ptr, size_t len, + yyjson_ptr_err *err); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_ptr_get(yyjson_val *val, + const char *ptr); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_ptr_getn(yyjson_val *val, + const char *ptr, size_t len); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param err A pointer to store the error information, or NULL if not needed. + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_ptr_getx(yyjson_val *val, + const char *ptr, size_t len, + yyjson_ptr_err *err); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_get(yyjson_mut_doc *doc, + const char *ptr); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getn(yyjson_mut_doc *doc, + const char *ptr, + size_t len); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getx(yyjson_mut_doc *doc, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_get(yyjson_mut_val *val, + const char *ptr); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getn(yyjson_mut_val *val, + const char *ptr, + size_t len); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getx(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Add (insert) value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The value to be added. + @return true if JSON pointer is valid and new value is added, false otherwise. + @note The parent nodes will be created if they do not exist. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_add(yyjson_mut_doc *doc, + const char *ptr, + yyjson_mut_val *new_val); + +/** + Add (insert) value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be added. + @return true if JSON pointer is valid and new value is added, false otherwise. + @note The parent nodes will be created if they do not exist. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_addn(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val); + +/** + Add (insert) value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be added. + @param create_parent Whether to create parent nodes if not exist. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return true if JSON pointer is valid and new value is added, false otherwise. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_addx(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Add (insert) value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param doc Only used to create new values when needed. + @param new_val The value to be added. + @return true if JSON pointer is valid and new value is added, false otherwise. + @note The parent nodes will be created if they do not exist. + */ +yyjson_api_inline bool yyjson_mut_ptr_add(yyjson_mut_val *val, + const char *ptr, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc); + +/** + Add (insert) value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param doc Only used to create new values when needed. + @param new_val The value to be added. + @return true if JSON pointer is valid and new value is added, false otherwise. + @note The parent nodes will be created if they do not exist. + */ +yyjson_api_inline bool yyjson_mut_ptr_addn(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc); + +/** + Add (insert) value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param doc Only used to create new values when needed. + @param new_val The value to be added. + @param create_parent Whether to create parent nodes if not exist. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return true if JSON pointer is valid and new value is added, false otherwise. + */ +yyjson_api_inline bool yyjson_mut_ptr_addx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Set value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The value to be set, pass NULL to remove. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note The parent nodes will be created if they do not exist. + If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_set(yyjson_mut_doc *doc, + const char *ptr, + yyjson_mut_val *new_val); + +/** + Set value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be set, pass NULL to remove. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note The parent nodes will be created if they do not exist. + If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_setn(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val); + +/** + Set value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be set, pass NULL to remove. + @param create_parent Whether to create parent nodes if not exist. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_setx(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Set value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The value to be set, pass NULL to remove. + @param doc Only used to create new values when needed. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note The parent nodes will be created if they do not exist. + If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_ptr_set(yyjson_mut_val *val, + const char *ptr, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc); + +/** + Set value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be set, pass NULL to remove. + @param doc Only used to create new values when needed. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note The parent nodes will be created if they do not exist. + If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_ptr_setn(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc); + +/** + Set value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be set, pass NULL to remove. + @param doc Only used to create new values when needed. + @param create_parent Whether to create parent nodes if not exist. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_ptr_setx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Replace value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The new value to replace the old one. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replace( + yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val); + +/** + Replace value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The new value to replace the old one. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacen( + yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val); + +/** + Replace value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The new value to replace the old one. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacex( + yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); + +/** + Replace value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The new value to replace the old one. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replace( + yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val); + +/** + Replace value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The new value to replace the old one. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacen( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val); + +/** + Replace value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The new value to replace the old one. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacex( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); + +/** + Remove value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_remove( + yyjson_mut_doc *doc, const char *ptr); + +/** + Remove value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removen( + yyjson_mut_doc *doc, const char *ptr, size_t len); + +/** + Remove value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removex( + yyjson_mut_doc *doc, const char *ptr, size_t len, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); + +/** + Remove value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_remove(yyjson_mut_val *val, + const char *ptr); + +/** + Remove value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removen(yyjson_mut_val *val, + const char *ptr, + size_t len); + +/** + Remove value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removex(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Append value by JSON pointer context. + @param ctx The context from the `yyjson_mut_ptr_xxx()` calls. + @param key New key if `ctx->ctn` is object, or NULL if `ctx->ctn` is array. + @param val New value to be added. + @return true on success or false on fail. + */ +yyjson_api_inline bool yyjson_ptr_ctx_append(yyjson_ptr_ctx *ctx, + yyjson_mut_val *key, + yyjson_mut_val *val); + +/** + Replace value by JSON pointer context. + @param ctx The context from the `yyjson_mut_ptr_xxx()` calls. + @param val New value to be replaced. + @return true on success or false on fail. + @note If success, the old value will be returned via `ctx->old`. + */ +yyjson_api_inline bool yyjson_ptr_ctx_replace(yyjson_ptr_ctx *ctx, + yyjson_mut_val *val); + +/** + Remove value by JSON pointer context. + @param ctx The context from the `yyjson_mut_ptr_xxx()` calls. + @return true on success or false on fail. + @note If success, the old value will be returned via `ctx->old`. + */ +yyjson_api_inline bool yyjson_ptr_ctx_remove(yyjson_ptr_ctx *ctx); + + + +/*============================================================================== + * JSON Patch API (RFC 6902) + * https://tools.ietf.org/html/rfc6902 + *============================================================================*/ + +/** Result code for JSON patch. */ +typedef uint32_t yyjson_patch_code; + +/** Success, no error. */ +static const yyjson_patch_code YYJSON_PATCH_SUCCESS = 0; + +/** Invalid parameter, such as NULL input or non-array patch. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_PARAMETER = 1; + +/** Memory allocation failure occurs. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_MEMORY_ALLOCATION = 2; + +/** JSON patch operation is not object type. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_OPERATION = 3; + +/** JSON patch operation is missing a required key. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_MISSING_KEY = 4; + +/** JSON patch operation member is invalid. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_MEMBER = 5; + +/** JSON patch operation `test` not equal. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_EQUAL = 6; + +/** JSON patch operation failed on JSON pointer. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_POINTER = 7; + +/** Error information for JSON patch. */ +typedef struct yyjson_patch_err { + /** Error code, see `yyjson_patch_code` for all possible values. */ + yyjson_patch_code code; + /** Index of the error operation (0 if no error). */ + size_t idx; + /** Error message, constant, no need to free (NULL if no error). */ + const char *msg; + /** JSON pointer error if `code == YYJSON_PATCH_ERROR_POINTER`. */ + yyjson_ptr_err ptr; +} yyjson_patch_err; + +/** + Creates and returns a patched JSON value (RFC 6902). + The memory of the returned value is allocated by the `doc`. + The `err` is used to receive error information, pass NULL if not needed. + Returns NULL if the patch could not be applied. + */ +yyjson_api yyjson_mut_val *yyjson_patch(yyjson_mut_doc *doc, + yyjson_val *orig, + yyjson_val *patch, + yyjson_patch_err *err); + +/** + Creates and returns a patched JSON value (RFC 6902). + The memory of the returned value is allocated by the `doc`. + The `err` is used to receive error information, pass NULL if not needed. + Returns NULL if the patch could not be applied. + */ +yyjson_api yyjson_mut_val *yyjson_mut_patch(yyjson_mut_doc *doc, + yyjson_mut_val *orig, + yyjson_mut_val *patch, + yyjson_patch_err *err); + + + +/*============================================================================== + * JSON Merge-Patch API (RFC 7386) + * https://tools.ietf.org/html/rfc7386 + *============================================================================*/ + +/** + Creates and returns a merge-patched JSON value (RFC 7386). + The memory of the returned value is allocated by the `doc`. + Returns NULL if the patch could not be applied. + + @warning This function is recursive and may cause a stack overflow if the + object level is too deep. + */ +yyjson_api yyjson_mut_val *yyjson_merge_patch(yyjson_mut_doc *doc, + yyjson_val *orig, + yyjson_val *patch); + +/** + Creates and returns a merge-patched JSON value (RFC 7386). + The memory of the returned value is allocated by the `doc`. + Returns NULL if the patch could not be applied. + + @warning This function is recursive and may cause a stack overflow if the + object level is too deep. + */ +yyjson_api yyjson_mut_val *yyjson_mut_merge_patch(yyjson_mut_doc *doc, + yyjson_mut_val *orig, + yyjson_mut_val *patch); + + + +/*============================================================================== + * JSON Structure (Implementation) + *============================================================================*/ + +/** Payload of a JSON value (8 bytes). */ +typedef union yyjson_val_uni { + uint64_t u64; + int64_t i64; + double f64; + const char *str; + void *ptr; + size_t ofs; +} yyjson_val_uni; + +/** + Immutable JSON value, 16 bytes. + */ +struct yyjson_val { + uint64_t tag; /**< type, subtype and length */ + yyjson_val_uni uni; /**< payload */ +}; + +struct yyjson_doc { + /** Root value of the document (nonnull). */ + yyjson_val *root; + /** Allocator used by document (nonnull). */ + yyjson_alc alc; + /** The total number of bytes read when parsing JSON (nonzero). */ + size_t dat_read; + /** The total number of value read when parsing JSON (nonzero). */ + size_t val_read; + /** The string pool used by JSON values (nullable). */ + char *str_pool; +}; + + + +/*============================================================================== + * Unsafe JSON Value API (Implementation) + *============================================================================*/ + +/* + Whether the string does not need to be escaped for serialization. + This function is used to optimize the writing speed of small constant strings. + This function works only if the compiler can evaluate it at compile time. + + Clang supports it since v8.0, + earlier versions do not support constant_p(strlen) and return false. + GCC supports it since at least v4.4, + earlier versions may compile it as run-time instructions. + ICC supports it since at least v16, + earlier versions are uncertain. + + @param str The C string. + @param len The returnd value from strlen(str). + */ +yyjson_api_inline bool unsafe_yyjson_is_str_noesc(const char *str, size_t len) { +#if YYJSON_HAS_CONSTANT_P && \ + (!YYJSON_IS_REAL_GCC || yyjson_gcc_available(4, 4, 0)) + if (yyjson_constant_p(len) && len <= 32) { + /* + Same as the following loop: + + for (size_t i = 0; i < len; i++) { + char c = str[i]; + if (c < ' ' || c > '~' || c == '"' || c == '\\') return false; + } + + GCC evaluates it at compile time only if the string length is within 17 + and -O3 (which turns on the -fpeel-loops flag) is used. + So the loop is unrolled for GCC. + */ +# define yyjson_repeat32_incr(x) \ + x(0) x(1) x(2) x(3) x(4) x(5) x(6) x(7) \ + x(8) x(9) x(10) x(11) x(12) x(13) x(14) x(15) \ + x(16) x(17) x(18) x(19) x(20) x(21) x(22) x(23) \ + x(24) x(25) x(26) x(27) x(28) x(29) x(30) x(31) +# define yyjson_check_char_noesc(i) \ + if (i < len) { \ + char c = str[i]; \ + if (c < ' ' || c > '~' || c == '"' || c == '\\') return false; } + yyjson_repeat32_incr(yyjson_check_char_noesc) +# undef yyjson_repeat32_incr +# undef yyjson_check_char_noesc + return true; + } +#else + (void)str; + (void)len; +#endif + return false; +} + +yyjson_api_inline yyjson_type unsafe_yyjson_get_type(void *val) { + uint8_t tag = (uint8_t)((yyjson_val *)val)->tag; + return (yyjson_type)(tag & YYJSON_TYPE_MASK); +} + +yyjson_api_inline yyjson_subtype unsafe_yyjson_get_subtype(void *val) { + uint8_t tag = (uint8_t)((yyjson_val *)val)->tag; + return (yyjson_subtype)(tag & YYJSON_SUBTYPE_MASK); +} + +yyjson_api_inline uint8_t unsafe_yyjson_get_tag(void *val) { + uint8_t tag = (uint8_t)((yyjson_val *)val)->tag; + return (uint8_t)(tag & YYJSON_TAG_MASK); +} + +yyjson_api_inline bool unsafe_yyjson_is_raw(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_RAW; +} + +yyjson_api_inline bool unsafe_yyjson_is_null(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_NULL; +} + +yyjson_api_inline bool unsafe_yyjson_is_bool(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_BOOL; +} + +yyjson_api_inline bool unsafe_yyjson_is_num(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_NUM; +} + +yyjson_api_inline bool unsafe_yyjson_is_str(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_STR; +} + +yyjson_api_inline bool unsafe_yyjson_is_arr(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_ARR; +} + +yyjson_api_inline bool unsafe_yyjson_is_obj(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_OBJ; +} + +yyjson_api_inline bool unsafe_yyjson_is_ctn(void *val) { + uint8_t mask = YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ; + return (unsafe_yyjson_get_tag(val) & mask) == mask; +} + +yyjson_api_inline bool unsafe_yyjson_is_uint(void *val) { + const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_sint(void *val) { + const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_int(void *val) { + const uint8_t mask = YYJSON_TAG_MASK & (~YYJSON_SUBTYPE_SINT); + const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + return (unsafe_yyjson_get_tag(val) & mask) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_real(void *val) { + const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_true(void *val) { + const uint8_t patt = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_false(void *val) { + const uint8_t patt = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_arr_is_flat(yyjson_val *val) { + size_t ofs = val->uni.ofs; + size_t len = (size_t)(val->tag >> YYJSON_TAG_BIT); + return len * sizeof(yyjson_val) + sizeof(yyjson_val) == ofs; +} + +yyjson_api_inline const char *unsafe_yyjson_get_raw(void *val) { + return ((yyjson_val *)val)->uni.str; +} + +yyjson_api_inline bool unsafe_yyjson_get_bool(void *val) { + uint8_t tag = unsafe_yyjson_get_tag(val); + return (bool)((tag & YYJSON_SUBTYPE_MASK) >> YYJSON_TYPE_BIT); +} + +yyjson_api_inline uint64_t unsafe_yyjson_get_uint(void *val) { + return ((yyjson_val *)val)->uni.u64; +} + +yyjson_api_inline int64_t unsafe_yyjson_get_sint(void *val) { + return ((yyjson_val *)val)->uni.i64; +} + +yyjson_api_inline int unsafe_yyjson_get_int(void *val) { + return (int)((yyjson_val *)val)->uni.i64; +} + +yyjson_api_inline double unsafe_yyjson_get_real(void *val) { + return ((yyjson_val *)val)->uni.f64; +} + +yyjson_api_inline double unsafe_yyjson_get_num(void *val) { + uint8_t tag = unsafe_yyjson_get_tag(val); + if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL)) { + return ((yyjson_val *)val)->uni.f64; + } else if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT)) { + return (double)((yyjson_val *)val)->uni.i64; + } else if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT)) { +#if YYJSON_U64_TO_F64_NO_IMPL + uint64_t msb = ((uint64_t)1) << 63; + uint64_t num = ((yyjson_val *)val)->uni.u64; + if ((num & msb) == 0) { + return (double)(int64_t)num; + } else { + return ((double)(int64_t)((num >> 1) | (num & 1))) * (double)2.0; + } +#else + return (double)((yyjson_val *)val)->uni.u64; +#endif + } + return 0.0; +} + +yyjson_api_inline const char *unsafe_yyjson_get_str(void *val) { + return ((yyjson_val *)val)->uni.str; +} + +yyjson_api_inline size_t unsafe_yyjson_get_len(void *val) { + return (size_t)(((yyjson_val *)val)->tag >> YYJSON_TAG_BIT); +} + +yyjson_api_inline yyjson_val *unsafe_yyjson_get_first(yyjson_val *ctn) { + return ctn + 1; +} + +yyjson_api_inline yyjson_val *unsafe_yyjson_get_next(yyjson_val *val) { + bool is_ctn = unsafe_yyjson_is_ctn(val); + size_t ctn_ofs = val->uni.ofs; + size_t ofs = (is_ctn ? ctn_ofs : sizeof(yyjson_val)); + return (yyjson_val *)(void *)((uint8_t *)val + ofs); +} + +yyjson_api_inline bool unsafe_yyjson_equals_strn(void *val, const char *str, + size_t len) { + return unsafe_yyjson_get_len(val) == len && + memcmp(((yyjson_val *)val)->uni.str, str, len) == 0; +} + +yyjson_api_inline bool unsafe_yyjson_equals_str(void *val, const char *str) { + return unsafe_yyjson_equals_strn(val, str, strlen(str)); +} + +yyjson_api_inline void unsafe_yyjson_set_type(void *val, yyjson_type type, + yyjson_subtype subtype) { + uint8_t tag = (type | subtype); + uint64_t new_tag = ((yyjson_val *)val)->tag; + new_tag = (new_tag & (~(uint64_t)YYJSON_TAG_MASK)) | (uint64_t)tag; + ((yyjson_val *)val)->tag = new_tag; +} + +yyjson_api_inline void unsafe_yyjson_set_len(void *val, size_t len) { + uint64_t tag = ((yyjson_val *)val)->tag & YYJSON_TAG_MASK; + tag |= (uint64_t)len << YYJSON_TAG_BIT; + ((yyjson_val *)val)->tag = tag; +} + +yyjson_api_inline void unsafe_yyjson_inc_len(void *val) { + uint64_t tag = ((yyjson_val *)val)->tag; + tag += (uint64_t)(1 << YYJSON_TAG_BIT); + ((yyjson_val *)val)->tag = tag; +} + +yyjson_api_inline void unsafe_yyjson_set_raw(void *val, const char *raw, + size_t len) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_RAW, YYJSON_SUBTYPE_NONE); + unsafe_yyjson_set_len(val, len); + ((yyjson_val *)val)->uni.str = raw; +} + +yyjson_api_inline void unsafe_yyjson_set_null(void *val) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_NULL, YYJSON_SUBTYPE_NONE); + unsafe_yyjson_set_len(val, 0); +} + +yyjson_api_inline void unsafe_yyjson_set_bool(void *val, bool num) { + yyjson_subtype subtype = num ? YYJSON_SUBTYPE_TRUE : YYJSON_SUBTYPE_FALSE; + unsafe_yyjson_set_type(val, YYJSON_TYPE_BOOL, subtype); + unsafe_yyjson_set_len(val, 0); +} + +yyjson_api_inline void unsafe_yyjson_set_uint(void *val, uint64_t num) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_UINT); + unsafe_yyjson_set_len(val, 0); + ((yyjson_val *)val)->uni.u64 = num; +} + +yyjson_api_inline void unsafe_yyjson_set_sint(void *val, int64_t num) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_SINT); + unsafe_yyjson_set_len(val, 0); + ((yyjson_val *)val)->uni.i64 = num; +} + +yyjson_api_inline void unsafe_yyjson_set_real(void *val, double num) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL); + unsafe_yyjson_set_len(val, 0); + ((yyjson_val *)val)->uni.f64 = num; +} + +yyjson_api_inline void unsafe_yyjson_set_str(void *val, const char *str) { + size_t len = strlen(str); + bool noesc = unsafe_yyjson_is_str_noesc(str, len); + yyjson_subtype sub = noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; + unsafe_yyjson_set_type(val, YYJSON_TYPE_STR, sub); + unsafe_yyjson_set_len(val, len); + ((yyjson_val *)val)->uni.str = str; +} + +yyjson_api_inline void unsafe_yyjson_set_strn(void *val, const char *str, + size_t len) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_STR, YYJSON_SUBTYPE_NONE); + unsafe_yyjson_set_len(val, len); + ((yyjson_val *)val)->uni.str = str; +} + +yyjson_api_inline void unsafe_yyjson_set_arr(void *val, size_t size) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_ARR, YYJSON_SUBTYPE_NONE); + unsafe_yyjson_set_len(val, size); +} + +yyjson_api_inline void unsafe_yyjson_set_obj(void *val, size_t size) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_OBJ, YYJSON_SUBTYPE_NONE); + unsafe_yyjson_set_len(val, size); +} + + + +/*============================================================================== + * JSON Document API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_val *yyjson_doc_get_root(yyjson_doc *doc) { + return doc ? doc->root : NULL; +} + +yyjson_api_inline size_t yyjson_doc_get_read_size(yyjson_doc *doc) { + return doc ? doc->dat_read : 0; +} + +yyjson_api_inline size_t yyjson_doc_get_val_count(yyjson_doc *doc) { + return doc ? doc->val_read : 0; +} + +yyjson_api_inline void yyjson_doc_free(yyjson_doc *doc) { + if (doc) { + yyjson_alc alc = doc->alc; + if (doc->str_pool) alc.free(alc.ctx, doc->str_pool); + alc.free(alc.ctx, doc); + } +} + + + +/*============================================================================== + * JSON Value Type API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_is_raw(yyjson_val *val) { + return val ? unsafe_yyjson_is_raw(val) : false; +} + +yyjson_api_inline bool yyjson_is_null(yyjson_val *val) { + return val ? unsafe_yyjson_is_null(val) : false; +} + +yyjson_api_inline bool yyjson_is_true(yyjson_val *val) { + return val ? unsafe_yyjson_is_true(val) : false; +} + +yyjson_api_inline bool yyjson_is_false(yyjson_val *val) { + return val ? unsafe_yyjson_is_false(val) : false; +} + +yyjson_api_inline bool yyjson_is_bool(yyjson_val *val) { + return val ? unsafe_yyjson_is_bool(val) : false; +} + +yyjson_api_inline bool yyjson_is_uint(yyjson_val *val) { + return val ? unsafe_yyjson_is_uint(val) : false; +} + +yyjson_api_inline bool yyjson_is_sint(yyjson_val *val) { + return val ? unsafe_yyjson_is_sint(val) : false; +} + +yyjson_api_inline bool yyjson_is_int(yyjson_val *val) { + return val ? unsafe_yyjson_is_int(val) : false; +} + +yyjson_api_inline bool yyjson_is_real(yyjson_val *val) { + return val ? unsafe_yyjson_is_real(val) : false; +} + +yyjson_api_inline bool yyjson_is_num(yyjson_val *val) { + return val ? unsafe_yyjson_is_num(val) : false; +} + +yyjson_api_inline bool yyjson_is_str(yyjson_val *val) { + return val ? unsafe_yyjson_is_str(val) : false; +} + +yyjson_api_inline bool yyjson_is_arr(yyjson_val *val) { + return val ? unsafe_yyjson_is_arr(val) : false; +} + +yyjson_api_inline bool yyjson_is_obj(yyjson_val *val) { + return val ? unsafe_yyjson_is_obj(val) : false; +} + +yyjson_api_inline bool yyjson_is_ctn(yyjson_val *val) { + return val ? unsafe_yyjson_is_ctn(val) : false; +} + + + +/*============================================================================== + * JSON Value Content API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_type yyjson_get_type(yyjson_val *val) { + return val ? unsafe_yyjson_get_type(val) : YYJSON_TYPE_NONE; +} + +yyjson_api_inline yyjson_subtype yyjson_get_subtype(yyjson_val *val) { + return val ? unsafe_yyjson_get_subtype(val) : YYJSON_SUBTYPE_NONE; +} + +yyjson_api_inline uint8_t yyjson_get_tag(yyjson_val *val) { + return val ? unsafe_yyjson_get_tag(val) : 0; +} + +yyjson_api_inline const char *yyjson_get_type_desc(yyjson_val *val) { + switch (yyjson_get_tag(val)) { + case YYJSON_TYPE_RAW | YYJSON_SUBTYPE_NONE: return "raw"; + case YYJSON_TYPE_NULL | YYJSON_SUBTYPE_NONE: return "null"; + case YYJSON_TYPE_STR | YYJSON_SUBTYPE_NONE: return "string"; + case YYJSON_TYPE_STR | YYJSON_SUBTYPE_NOESC: return "string"; + case YYJSON_TYPE_ARR | YYJSON_SUBTYPE_NONE: return "array"; + case YYJSON_TYPE_OBJ | YYJSON_SUBTYPE_NONE: return "object"; + case YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE: return "true"; + case YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE: return "false"; + case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT: return "uint"; + case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT: return "sint"; + case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL: return "real"; + default: return "unknown"; + } +} + +yyjson_api_inline const char *yyjson_get_raw(yyjson_val *val) { + return yyjson_is_raw(val) ? unsafe_yyjson_get_raw(val) : NULL; +} + +yyjson_api_inline bool yyjson_get_bool(yyjson_val *val) { + return yyjson_is_bool(val) ? unsafe_yyjson_get_bool(val) : false; +} + +yyjson_api_inline uint64_t yyjson_get_uint(yyjson_val *val) { + return yyjson_is_int(val) ? unsafe_yyjson_get_uint(val) : 0; +} + +yyjson_api_inline int64_t yyjson_get_sint(yyjson_val *val) { + return yyjson_is_int(val) ? unsafe_yyjson_get_sint(val) : 0; +} + +yyjson_api_inline int yyjson_get_int(yyjson_val *val) { + return yyjson_is_int(val) ? unsafe_yyjson_get_int(val) : 0; +} + +yyjson_api_inline double yyjson_get_real(yyjson_val *val) { + return yyjson_is_real(val) ? unsafe_yyjson_get_real(val) : 0.0; +} + +yyjson_api_inline double yyjson_get_num(yyjson_val *val) { + return val ? unsafe_yyjson_get_num(val) : 0.0; +} + +yyjson_api_inline const char *yyjson_get_str(yyjson_val *val) { + return yyjson_is_str(val) ? unsafe_yyjson_get_str(val) : NULL; +} + +yyjson_api_inline size_t yyjson_get_len(yyjson_val *val) { + return val ? unsafe_yyjson_get_len(val) : 0; +} + +yyjson_api_inline bool yyjson_equals_str(yyjson_val *val, const char *str) { + if (yyjson_likely(val && str)) { + return unsafe_yyjson_is_str(val) && + unsafe_yyjson_equals_str(val, str); + } + return false; +} + +yyjson_api_inline bool yyjson_equals_strn(yyjson_val *val, const char *str, + size_t len) { + if (yyjson_likely(val && str)) { + return unsafe_yyjson_is_str(val) && + unsafe_yyjson_equals_strn(val, str, len); + } + return false; +} + +yyjson_api bool unsafe_yyjson_equals(yyjson_val *lhs, yyjson_val *rhs); + +yyjson_api_inline bool yyjson_equals(yyjson_val *lhs, yyjson_val *rhs) { + if (yyjson_unlikely(!lhs || !rhs)) return false; + return unsafe_yyjson_equals(lhs, rhs); +} + +yyjson_api_inline bool yyjson_set_raw(yyjson_val *val, + const char *raw, size_t len) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_raw(val, raw, len); + return true; +} + +yyjson_api_inline bool yyjson_set_null(yyjson_val *val) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_null(val); + return true; +} + +yyjson_api_inline bool yyjson_set_bool(yyjson_val *val, bool num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_bool(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_uint(yyjson_val *val, uint64_t num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_uint(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_sint(yyjson_val *val, int64_t num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_sint(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_int(yyjson_val *val, int num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_sint(val, (int64_t)num); + return true; +} + +yyjson_api_inline bool yyjson_set_real(yyjson_val *val, double num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_real(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_str(yyjson_val *val, const char *str) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + if (yyjson_unlikely(!str)) return false; + unsafe_yyjson_set_str(val, str); + return true; +} + +yyjson_api_inline bool yyjson_set_strn(yyjson_val *val, + const char *str, size_t len) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + if (yyjson_unlikely(!str)) return false; + unsafe_yyjson_set_strn(val, str, len); + return true; +} + + + +/*============================================================================== + * JSON Array API (Implementation) + *============================================================================*/ + +yyjson_api_inline size_t yyjson_arr_size(yyjson_val *arr) { + return yyjson_is_arr(arr) ? unsafe_yyjson_get_len(arr) : 0; +} + +yyjson_api_inline yyjson_val *yyjson_arr_get(yyjson_val *arr, size_t idx) { + if (yyjson_likely(yyjson_is_arr(arr))) { + if (yyjson_likely(unsafe_yyjson_get_len(arr) > idx)) { + yyjson_val *val = unsafe_yyjson_get_first(arr); + if (unsafe_yyjson_arr_is_flat(arr)) { + return val + idx; + } else { + while (idx-- > 0) val = unsafe_yyjson_get_next(val); + return val; + } + } + } + return NULL; +} + +yyjson_api_inline yyjson_val *yyjson_arr_get_first(yyjson_val *arr) { + if (yyjson_likely(yyjson_is_arr(arr))) { + if (yyjson_likely(unsafe_yyjson_get_len(arr) > 0)) { + return unsafe_yyjson_get_first(arr); + } + } + return NULL; +} + +yyjson_api_inline yyjson_val *yyjson_arr_get_last(yyjson_val *arr) { + if (yyjson_likely(yyjson_is_arr(arr))) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(len > 0)) { + yyjson_val *val = unsafe_yyjson_get_first(arr); + if (unsafe_yyjson_arr_is_flat(arr)) { + return val + (len - 1); + } else { + while (len-- > 1) val = unsafe_yyjson_get_next(val); + return val; + } + } + } + return NULL; +} + + + +/*============================================================================== + * JSON Array Iterator API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_arr_iter_init(yyjson_val *arr, + yyjson_arr_iter *iter) { + if (yyjson_likely(yyjson_is_arr(arr) && iter)) { + iter->idx = 0; + iter->max = unsafe_yyjson_get_len(arr); + iter->cur = unsafe_yyjson_get_first(arr); + return true; + } + if (iter) memset(iter, 0, sizeof(yyjson_arr_iter)); + return false; +} + +yyjson_api_inline yyjson_arr_iter yyjson_arr_iter_with(yyjson_val *arr) { + yyjson_arr_iter iter; + yyjson_arr_iter_init(arr, &iter); + return iter; +} + +yyjson_api_inline bool yyjson_arr_iter_has_next(yyjson_arr_iter *iter) { + return iter ? iter->idx < iter->max : false; +} + +yyjson_api_inline yyjson_val *yyjson_arr_iter_next(yyjson_arr_iter *iter) { + yyjson_val *val; + if (iter && iter->idx < iter->max) { + val = iter->cur; + iter->cur = unsafe_yyjson_get_next(val); + iter->idx++; + return val; + } + return NULL; +} + + + +/*============================================================================== + * JSON Object API (Implementation) + *============================================================================*/ + +yyjson_api_inline size_t yyjson_obj_size(yyjson_val *obj) { + return yyjson_is_obj(obj) ? unsafe_yyjson_get_len(obj) : 0; +} + +yyjson_api_inline yyjson_val *yyjson_obj_get(yyjson_val *obj, + const char *key) { + return yyjson_obj_getn(obj, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_val *yyjson_obj_getn(yyjson_val *obj, + const char *_key, + size_t key_len) { + if (yyjson_likely(yyjson_is_obj(obj) && _key)) { + size_t len = unsafe_yyjson_get_len(obj); + yyjson_val *key = unsafe_yyjson_get_first(obj); + while (len-- > 0) { + if (unsafe_yyjson_equals_strn(key, _key, key_len)) return key + 1; + key = unsafe_yyjson_get_next(key + 1); + } + } + return NULL; +} + + + +/*============================================================================== + * JSON Object Iterator API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_obj_iter_init(yyjson_val *obj, + yyjson_obj_iter *iter) { + if (yyjson_likely(yyjson_is_obj(obj) && iter)) { + iter->idx = 0; + iter->max = unsafe_yyjson_get_len(obj); + iter->cur = unsafe_yyjson_get_first(obj); + iter->obj = obj; + return true; + } + if (iter) memset(iter, 0, sizeof(yyjson_obj_iter)); + return false; +} + +yyjson_api_inline yyjson_obj_iter yyjson_obj_iter_with(yyjson_val *obj) { + yyjson_obj_iter iter; + yyjson_obj_iter_init(obj, &iter); + return iter; +} + +yyjson_api_inline bool yyjson_obj_iter_has_next(yyjson_obj_iter *iter) { + return iter ? iter->idx < iter->max : false; +} + +yyjson_api_inline yyjson_val *yyjson_obj_iter_next(yyjson_obj_iter *iter) { + if (iter && iter->idx < iter->max) { + yyjson_val *key = iter->cur; + iter->idx++; + iter->cur = unsafe_yyjson_get_next(key + 1); + return key; + } + return NULL; +} + +yyjson_api_inline yyjson_val *yyjson_obj_iter_get_val(yyjson_val *key) { + return key ? key + 1 : NULL; +} + +yyjson_api_inline yyjson_val *yyjson_obj_iter_get(yyjson_obj_iter *iter, + const char *key) { + return yyjson_obj_iter_getn(iter, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_val *yyjson_obj_iter_getn(yyjson_obj_iter *iter, + const char *key, + size_t key_len) { + if (iter && key) { + size_t idx = iter->idx; + size_t max = iter->max; + yyjson_val *cur = iter->cur; + if (yyjson_unlikely(idx == max)) { + idx = 0; + cur = unsafe_yyjson_get_first(iter->obj); + } + while (idx++ < max) { + yyjson_val *next = unsafe_yyjson_get_next(cur + 1); + if (unsafe_yyjson_equals_strn(cur, key, key_len)) { + iter->idx = idx; + iter->cur = next; + return cur + 1; + } + cur = next; + if (idx == iter->max && iter->idx < iter->max) { + idx = 0; + max = iter->idx; + cur = unsafe_yyjson_get_first(iter->obj); + } + } + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Structure (Implementation) + *============================================================================*/ + +/** + Mutable JSON value, 24 bytes. + The 'tag' and 'uni' field is same as immutable value. + The 'next' field links all elements inside the container to be a cycle. + */ +struct yyjson_mut_val { + uint64_t tag; /**< type, subtype and length */ + yyjson_val_uni uni; /**< payload */ + yyjson_mut_val *next; /**< the next value in circular linked list */ +}; + +/** + A memory chunk in string memory pool. + */ +typedef struct yyjson_str_chunk { + struct yyjson_str_chunk *next; /* next chunk linked list */ + size_t chunk_size; /* chunk size in bytes */ + /* char str[]; flexible array member */ +} yyjson_str_chunk; + +/** + A memory pool to hold all strings in a mutable document. + */ +typedef struct yyjson_str_pool { + char *cur; /* cursor inside current chunk */ + char *end; /* the end of current chunk */ + size_t chunk_size; /* chunk size in bytes while creating new chunk */ + size_t chunk_size_max; /* maximum chunk size in bytes */ + yyjson_str_chunk *chunks; /* a linked list of chunks, nullable */ +} yyjson_str_pool; + +/** + A memory chunk in value memory pool. + `sizeof(yyjson_val_chunk)` should not larger than `sizeof(yyjson_mut_val)`. + */ +typedef struct yyjson_val_chunk { + struct yyjson_val_chunk *next; /* next chunk linked list */ + size_t chunk_size; /* chunk size in bytes */ + /* char pad[sizeof(yyjson_mut_val) - sizeof(yyjson_val_chunk)]; padding */ + /* yyjson_mut_val vals[]; flexible array member */ +} yyjson_val_chunk; + +/** + A memory pool to hold all values in a mutable document. + */ +typedef struct yyjson_val_pool { + yyjson_mut_val *cur; /* cursor inside current chunk */ + yyjson_mut_val *end; /* the end of current chunk */ + size_t chunk_size; /* chunk size in bytes while creating new chunk */ + size_t chunk_size_max; /* maximum chunk size in bytes */ + yyjson_val_chunk *chunks; /* a linked list of chunks, nullable */ +} yyjson_val_pool; + +struct yyjson_mut_doc { + yyjson_mut_val *root; /**< root value of the JSON document, nullable */ + yyjson_alc alc; /**< a valid allocator, nonnull */ + yyjson_str_pool str_pool; /**< string memory pool */ + yyjson_val_pool val_pool; /**< value memory pool */ +}; + +/* Ensures the capacity to at least equal to the specified byte length. */ +yyjson_api bool unsafe_yyjson_str_pool_grow(yyjson_str_pool *pool, + const yyjson_alc *alc, + size_t len); + +/* Ensures the capacity to at least equal to the specified value count. */ +yyjson_api bool unsafe_yyjson_val_pool_grow(yyjson_val_pool *pool, + const yyjson_alc *alc, + size_t count); + +/* Allocate memory for string. */ +yyjson_api_inline char *unsafe_yyjson_mut_str_alc(yyjson_mut_doc *doc, + size_t len) { + char *mem; + const yyjson_alc *alc = &doc->alc; + yyjson_str_pool *pool = &doc->str_pool; + if (yyjson_unlikely((size_t)(pool->end - pool->cur) <= len)) { + if (yyjson_unlikely(!unsafe_yyjson_str_pool_grow(pool, alc, len + 1))) { + return NULL; + } + } + mem = pool->cur; + pool->cur = mem + len + 1; + return mem; +} + +yyjson_api_inline char *unsafe_yyjson_mut_strncpy(yyjson_mut_doc *doc, + const char *str, size_t len) { + char *mem = unsafe_yyjson_mut_str_alc(doc, len); + if (yyjson_unlikely(!mem)) return NULL; + memcpy((void *)mem, (const void *)str, len); + mem[len] = '\0'; + return mem; +} + +yyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_val(yyjson_mut_doc *doc, + size_t count) { + yyjson_mut_val *val; + yyjson_alc *alc = &doc->alc; + yyjson_val_pool *pool = &doc->val_pool; + if (yyjson_unlikely((size_t)(pool->end - pool->cur) < count)) { + if (yyjson_unlikely(!unsafe_yyjson_val_pool_grow(pool, alc, count))) { + return NULL; + } + } + val = pool->cur; + pool->cur += count; + return val; +} + + + +/*============================================================================== + * Mutable JSON Document API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_root(yyjson_mut_doc *doc) { + return doc ? doc->root : NULL; +} + +yyjson_api_inline void yyjson_mut_doc_set_root(yyjson_mut_doc *doc, + yyjson_mut_val *root) { + if (doc) doc->root = root; +} + + + +/*============================================================================== + * Mutable JSON Value Type API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_is_raw(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_raw(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_null(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_null(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_true(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_true(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_false(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_false(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_bool(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_bool(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_uint(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_uint(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_sint(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_sint(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_int(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_int(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_real(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_real(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_num(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_num(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_str(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_str(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_arr(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_arr(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_obj(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_obj(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_ctn(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_ctn(val) : false; +} + + + +/*============================================================================== + * Mutable JSON Value Content API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_type yyjson_mut_get_type(yyjson_mut_val *val) { + return yyjson_get_type((yyjson_val *)val); +} + +yyjson_api_inline yyjson_subtype yyjson_mut_get_subtype(yyjson_mut_val *val) { + return yyjson_get_subtype((yyjson_val *)val); +} + +yyjson_api_inline uint8_t yyjson_mut_get_tag(yyjson_mut_val *val) { + return yyjson_get_tag((yyjson_val *)val); +} + +yyjson_api_inline const char *yyjson_mut_get_type_desc(yyjson_mut_val *val) { + return yyjson_get_type_desc((yyjson_val *)val); +} + +yyjson_api_inline const char *yyjson_mut_get_raw(yyjson_mut_val *val) { + return yyjson_get_raw((yyjson_val *)val); +} + +yyjson_api_inline bool yyjson_mut_get_bool(yyjson_mut_val *val) { + return yyjson_get_bool((yyjson_val *)val); +} + +yyjson_api_inline uint64_t yyjson_mut_get_uint(yyjson_mut_val *val) { + return yyjson_get_uint((yyjson_val *)val); +} + +yyjson_api_inline int64_t yyjson_mut_get_sint(yyjson_mut_val *val) { + return yyjson_get_sint((yyjson_val *)val); +} + +yyjson_api_inline int yyjson_mut_get_int(yyjson_mut_val *val) { + return yyjson_get_int((yyjson_val *)val); +} + +yyjson_api_inline double yyjson_mut_get_real(yyjson_mut_val *val) { + return yyjson_get_real((yyjson_val *)val); +} + +yyjson_api_inline double yyjson_mut_get_num(yyjson_mut_val *val) { + return yyjson_get_num((yyjson_val *)val); +} + +yyjson_api_inline const char *yyjson_mut_get_str(yyjson_mut_val *val) { + return yyjson_get_str((yyjson_val *)val); +} + +yyjson_api_inline size_t yyjson_mut_get_len(yyjson_mut_val *val) { + return yyjson_get_len((yyjson_val *)val); +} + +yyjson_api_inline bool yyjson_mut_equals_str(yyjson_mut_val *val, + const char *str) { + return yyjson_equals_str((yyjson_val *)val, str); +} + +yyjson_api_inline bool yyjson_mut_equals_strn(yyjson_mut_val *val, + const char *str, size_t len) { + return yyjson_equals_strn((yyjson_val *)val, str, len); +} + +yyjson_api bool unsafe_yyjson_mut_equals(yyjson_mut_val *lhs, + yyjson_mut_val *rhs); + +yyjson_api_inline bool yyjson_mut_equals(yyjson_mut_val *lhs, + yyjson_mut_val *rhs) { + if (yyjson_unlikely(!lhs || !rhs)) return false; + return unsafe_yyjson_mut_equals(lhs, rhs); +} + +yyjson_api_inline bool yyjson_mut_set_raw(yyjson_mut_val *val, + const char *raw, size_t len) { + if (yyjson_unlikely(!val || !raw)) return false; + unsafe_yyjson_set_raw(val, raw, len); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_null(yyjson_mut_val *val) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_null(val); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_bool(yyjson_mut_val *val, bool num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_bool(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_uint(yyjson_mut_val *val, uint64_t num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_uint(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_sint(yyjson_mut_val *val, int64_t num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_sint(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_int(yyjson_mut_val *val, int num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_sint(val, (int64_t)num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_real(yyjson_mut_val *val, double num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_real(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_str(yyjson_mut_val *val, + const char *str) { + if (yyjson_unlikely(!val || !str)) return false; + unsafe_yyjson_set_str(val, str); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_strn(yyjson_mut_val *val, + const char *str, size_t len) { + if (yyjson_unlikely(!val || !str)) return false; + unsafe_yyjson_set_strn(val, str, len); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_arr(yyjson_mut_val *val) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_arr(val, 0); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_obj(yyjson_mut_val *val) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_obj(val, 0); + return true; +} + + + +/*============================================================================== + * Mutable JSON Value Creation API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_mut_val *yyjson_mut_raw(yyjson_mut_doc *doc, + const char *str) { + if (yyjson_likely(str)) return yyjson_mut_rawn(doc, str, strlen(str)); + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawn(yyjson_mut_doc *doc, + const char *str, + size_t len) { + if (yyjson_likely(doc && str)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; + val->uni.str = str; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawcpy(yyjson_mut_doc *doc, + const char *str) { + if (yyjson_likely(str)) return yyjson_mut_rawncpy(doc, str, strlen(str)); + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawncpy(yyjson_mut_doc *doc, + const char *str, + size_t len) { + if (yyjson_likely(doc && str)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_likely(val && new_str)) { + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; + val->uni.str = new_str; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_null(yyjson_mut_doc *doc) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_NULL | YYJSON_SUBTYPE_NONE; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_true(yyjson_mut_doc *doc) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_false(yyjson_mut_doc *doc) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_bool(yyjson_mut_doc *doc, + bool _val) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_BOOL | (uint8_t)((uint8_t)_val << 3); + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_uint(yyjson_mut_doc *doc, + uint64_t num) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + val->uni.u64 = num; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_sint(yyjson_mut_doc *doc, + int64_t num) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = num; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_int(yyjson_mut_doc *doc, + int64_t num) { + return yyjson_mut_sint(doc, num); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_real(yyjson_mut_doc *doc, + double num) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.f64 = num; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_str(yyjson_mut_doc *doc, + const char *str) { + if (yyjson_likely(doc && str)) { + size_t len = strlen(str); + bool noesc = unsafe_yyjson_is_str_noesc(str, len); + yyjson_subtype sub = noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | + (uint64_t)(YYJSON_TYPE_STR | sub); + val->uni.str = str; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_strn(yyjson_mut_doc *doc, + const char *str, + size_t len) { + if (yyjson_likely(doc && str)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = str; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_strcpy(yyjson_mut_doc *doc, + const char *str) { + if (yyjson_likely(doc && str)) { + size_t len = strlen(str); + bool noesc = unsafe_yyjson_is_str_noesc(str, len); + yyjson_subtype sub = noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_likely(val && new_str)) { + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | + (uint64_t)(YYJSON_TYPE_STR | sub); + val->uni.str = new_str; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_strncpy(yyjson_mut_doc *doc, + const char *str, + size_t len) { + if (yyjson_likely(doc && str)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_likely(val && new_str)) { + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = new_str; + return val; + } + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Array API (Implementation) + *============================================================================*/ + +yyjson_api_inline size_t yyjson_mut_arr_size(yyjson_mut_val *arr) { + return yyjson_mut_is_arr(arr) ? unsafe_yyjson_get_len(arr) : 0; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get(yyjson_mut_val *arr, + size_t idx) { + if (yyjson_likely(idx < yyjson_mut_arr_size(arr))) { + yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr; + while (idx-- > 0) val = val->next; + return val->next; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_first( + yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_arr_size(arr) > 0)) { + return ((yyjson_mut_val *)arr->uni.ptr)->next; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_last( + yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_arr_size(arr) > 0)) { + return ((yyjson_mut_val *)arr->uni.ptr); + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Array Iterator API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_arr_iter_init(yyjson_mut_val *arr, + yyjson_mut_arr_iter *iter) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && iter)) { + iter->idx = 0; + iter->max = unsafe_yyjson_get_len(arr); + iter->cur = iter->max ? (yyjson_mut_val *)arr->uni.ptr : NULL; + iter->pre = NULL; + iter->arr = arr; + return true; + } + if (iter) memset(iter, 0, sizeof(yyjson_mut_arr_iter)); + return false; +} + +yyjson_api_inline yyjson_mut_arr_iter yyjson_mut_arr_iter_with( + yyjson_mut_val *arr) { + yyjson_mut_arr_iter iter; + yyjson_mut_arr_iter_init(arr, &iter); + return iter; +} + +yyjson_api_inline bool yyjson_mut_arr_iter_has_next(yyjson_mut_arr_iter *iter) { + return iter ? iter->idx < iter->max : false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_next( + yyjson_mut_arr_iter *iter) { + if (iter && iter->idx < iter->max) { + yyjson_mut_val *val = iter->cur; + iter->pre = val; + iter->cur = val->next; + iter->idx++; + return iter->cur; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_remove( + yyjson_mut_arr_iter *iter) { + if (yyjson_likely(iter && 0 < iter->idx && iter->idx <= iter->max)) { + yyjson_mut_val *prev = iter->pre; + yyjson_mut_val *cur = iter->cur; + yyjson_mut_val *next = cur->next; + if (yyjson_unlikely(iter->idx == iter->max)) iter->arr->uni.ptr = prev; + iter->idx--; + iter->max--; + unsafe_yyjson_set_len(iter->arr, iter->max); + prev->next = next; + iter->cur = next; + return cur; + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Array Creation API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr(yyjson_mut_doc *doc) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_ARR | YYJSON_SUBTYPE_NONE; + return val; + } + } + return NULL; +} + +#define yyjson_mut_arr_with_func(func) \ + if (yyjson_likely(doc && ((0 < count && count < \ + (~(size_t)0) / sizeof(yyjson_mut_val) && vals) || count == 0))) { \ + yyjson_mut_val *arr = unsafe_yyjson_mut_val(doc, 1 + count); \ + if (yyjson_likely(arr)) { \ + arr->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; \ + if (count > 0) { \ + size_t i; \ + for (i = 0; i < count; i++) { \ + yyjson_mut_val *val = arr + i + 1; \ + func \ + val->next = val + 1; \ + } \ + arr[count].next = arr + 1; \ + arr->uni.ptr = arr + count; \ + } \ + return arr; \ + } \ + } \ + return NULL + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_bool( + yyjson_mut_doc *doc, const bool *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_BOOL | (uint8_t)((uint8_t)vals[i] << 3); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint( + yyjson_mut_doc *doc, const int64_t *vals, size_t count) { + return yyjson_mut_arr_with_sint64(doc, vals, count); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint( + yyjson_mut_doc *doc, const uint64_t *vals, size_t count) { + return yyjson_mut_arr_with_uint64(doc, vals, count); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_real( + yyjson_mut_doc *doc, const double *vals, size_t count) { + return yyjson_mut_arr_with_double(doc, vals, count); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint8( + yyjson_mut_doc *doc, const int8_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = (int64_t)vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint16( + yyjson_mut_doc *doc, const int16_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint32( + yyjson_mut_doc *doc, const int32_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint64( + yyjson_mut_doc *doc, const int64_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint8( + yyjson_mut_doc *doc, const uint8_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + val->uni.u64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint16( + yyjson_mut_doc *doc, const uint16_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + val->uni.u64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint32( + yyjson_mut_doc *doc, const uint32_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + val->uni.u64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint64( + yyjson_mut_doc *doc, const uint64_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + val->uni.u64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_float( + yyjson_mut_doc *doc, const float *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.f64 = (double)vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_double( + yyjson_mut_doc *doc, const double *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.f64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_str( + yyjson_mut_doc *doc, const char **vals, size_t count) { + yyjson_mut_arr_with_func({ + uint64_t len = (uint64_t)strlen(vals[i]); + val->tag = (len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = vals[i]; + if (yyjson_unlikely(!val->uni.str)) return NULL; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strn( + yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count) { + if (yyjson_unlikely(count > 0 && !lens)) return NULL; + yyjson_mut_arr_with_func({ + val->tag = ((uint64_t)lens[i] << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = vals[i]; + if (yyjson_unlikely(!val->uni.str)) return NULL; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strcpy( + yyjson_mut_doc *doc, const char **vals, size_t count) { + size_t len; + const char *str; + yyjson_mut_arr_with_func({ + str = vals[i]; + if (!str) return NULL; + len = strlen(str); + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_unlikely(!val->uni.str)) return NULL; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strncpy( + yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count) { + size_t len; + const char *str; + if (yyjson_unlikely(count > 0 && !lens)) return NULL; + yyjson_mut_arr_with_func({ + str = vals[i]; + len = lens[i]; + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_unlikely(!val->uni.str)) return NULL; + }); +} + +#undef yyjson_mut_arr_with_func + + + +/*============================================================================== + * Mutable JSON Array Modification API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_arr_insert(yyjson_mut_val *arr, + yyjson_mut_val *val, size_t idx) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(idx <= len)) { + unsafe_yyjson_set_len(arr, len + 1); + if (len == 0) { + val->next = val; + arr->uni.ptr = val; + } else { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + if (idx == len) { + prev->next = val; + val->next = next; + arr->uni.ptr = val; + } else { + while (idx-- > 0) { + prev = next; + next = next->next; + } + prev->next = val; + val->next = next; + } + } + return true; + } + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_append(yyjson_mut_val *arr, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { + size_t len = unsafe_yyjson_get_len(arr); + unsafe_yyjson_set_len(arr, len + 1); + if (len == 0) { + val->next = val; + } else { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + prev->next = val; + val->next = next; + } + arr->uni.ptr = val; + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_prepend(yyjson_mut_val *arr, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { + size_t len = unsafe_yyjson_get_len(arr); + unsafe_yyjson_set_len(arr, len + 1); + if (len == 0) { + val->next = val; + arr->uni.ptr = val; + } else { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + prev->next = val; + val->next = next; + } + return true; + } + return false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_replace(yyjson_mut_val *arr, + size_t idx, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(idx < len)) { + if (yyjson_likely(len > 1)) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + while (idx-- > 0) { + prev = next; + next = next->next; + } + prev->next = val; + val->next = next->next; + if ((void *)next == arr->uni.ptr) arr->uni.ptr = val; + return next; + } else { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + val->next = val; + arr->uni.ptr = val; + return prev; + } + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove(yyjson_mut_val *arr, + size_t idx) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(idx < len)) { + unsafe_yyjson_set_len(arr, len - 1); + if (yyjson_likely(len > 1)) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + while (idx-- > 0) { + prev = next; + next = next->next; + } + prev->next = next->next; + if ((void *)next == arr->uni.ptr) arr->uni.ptr = prev; + return next; + } else { + return ((yyjson_mut_val *)arr->uni.ptr); + } + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_first( + yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + size_t len = unsafe_yyjson_get_len(arr); + if (len > 1) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + prev->next = next->next; + unsafe_yyjson_set_len(arr, len - 1); + return next; + } else if (len == 1) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + unsafe_yyjson_set_len(arr, 0); + return prev; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_last( + yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(len > 1)) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + unsafe_yyjson_set_len(arr, len - 1); + while (--len > 0) prev = prev->next; + prev->next = next; + next = (yyjson_mut_val *)arr->uni.ptr; + arr->uni.ptr = prev; + return next; + } else if (len == 1) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + unsafe_yyjson_set_len(arr, 0); + return prev; + } + } + return NULL; +} + +yyjson_api_inline bool yyjson_mut_arr_remove_range(yyjson_mut_val *arr, + size_t _idx, size_t _len) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + yyjson_mut_val *prev, *next; + bool tail_removed; + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_unlikely(_idx + _len > len)) return false; + if (yyjson_unlikely(_len == 0)) return true; + unsafe_yyjson_set_len(arr, len - _len); + if (yyjson_unlikely(len == _len)) return true; + tail_removed = (_idx + _len == len); + prev = ((yyjson_mut_val *)arr->uni.ptr); + while (_idx-- > 0) prev = prev->next; + next = prev->next; + while (_len-- > 0) next = next->next; + prev->next = next; + if (yyjson_unlikely(tail_removed)) arr->uni.ptr = prev; + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_clear(yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + unsafe_yyjson_set_len(arr, 0); + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_rotate(yyjson_mut_val *arr, + size_t idx) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && + unsafe_yyjson_get_len(arr) > idx)) { + yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr; + while (idx-- > 0) val = val->next; + arr->uni.ptr = (void *)val; + return true; + } + return false; +} + + + +/*============================================================================== + * Mutable JSON Array Modification Convenience API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_arr_add_val(yyjson_mut_val *arr, + yyjson_mut_val *val) { + return yyjson_mut_arr_append(arr, val); +} + +yyjson_api_inline bool yyjson_mut_arr_add_null(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_null(doc); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_true(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_true(doc); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_false(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_false(doc); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_bool(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + bool _val) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_bool(doc, _val); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_uint(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + uint64_t num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_uint(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_sint(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + int64_t num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_sint(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_int(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + int64_t num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_sint(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_real(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + double num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_real(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_str(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_str(doc, str); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_strn(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str, size_t len) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_strn(doc, str, len); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_strcpy(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_strcpy(doc, str); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_strncpy(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str, size_t len) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_strncpy(doc, str, len); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_arr(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_arr(doc); + return yyjson_mut_arr_append(arr, val) ? val : NULL; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_obj(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_obj(doc); + return yyjson_mut_arr_append(arr, val) ? val : NULL; + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Object API (Implementation) + *============================================================================*/ + +yyjson_api_inline size_t yyjson_mut_obj_size(yyjson_mut_val *obj) { + return yyjson_mut_is_obj(obj) ? unsafe_yyjson_get_len(obj) : 0; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_get(yyjson_mut_val *obj, + const char *key) { + return yyjson_mut_obj_getn(obj, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_getn(yyjson_mut_val *obj, + const char *_key, + size_t key_len) { + size_t len = yyjson_mut_obj_size(obj); + if (yyjson_likely(len && _key)) { + yyjson_mut_val *key = ((yyjson_mut_val *)obj->uni.ptr)->next->next; + while (len-- > 0) { + if (unsafe_yyjson_equals_strn(key, _key, key_len)) return key->next; + key = key->next->next; + } + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Object Iterator API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_obj_iter_init(yyjson_mut_val *obj, + yyjson_mut_obj_iter *iter) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && iter)) { + iter->idx = 0; + iter->max = unsafe_yyjson_get_len(obj); + iter->cur = iter->max ? (yyjson_mut_val *)obj->uni.ptr : NULL; + iter->pre = NULL; + iter->obj = obj; + return true; + } + if (iter) memset(iter, 0, sizeof(yyjson_mut_obj_iter)); + return false; +} + +yyjson_api_inline yyjson_mut_obj_iter yyjson_mut_obj_iter_with( + yyjson_mut_val *obj) { + yyjson_mut_obj_iter iter; + yyjson_mut_obj_iter_init(obj, &iter); + return iter; +} + +yyjson_api_inline bool yyjson_mut_obj_iter_has_next(yyjson_mut_obj_iter *iter) { + return iter ? iter->idx < iter->max : false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_next( + yyjson_mut_obj_iter *iter) { + if (iter && iter->idx < iter->max) { + yyjson_mut_val *key = iter->cur; + iter->pre = key; + iter->cur = key->next->next; + iter->idx++; + return iter->cur; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get_val( + yyjson_mut_val *key) { + return key ? key->next : NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_remove( + yyjson_mut_obj_iter *iter) { + if (yyjson_likely(iter && 0 < iter->idx && iter->idx <= iter->max)) { + yyjson_mut_val *prev = iter->pre; + yyjson_mut_val *cur = iter->cur; + yyjson_mut_val *next = cur->next->next; + if (yyjson_unlikely(iter->idx == iter->max)) iter->obj->uni.ptr = prev; + iter->idx--; + iter->max--; + unsafe_yyjson_set_len(iter->obj, iter->max); + prev->next->next = next; + iter->cur = prev; + return cur->next; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get( + yyjson_mut_obj_iter *iter, const char *key) { + return yyjson_mut_obj_iter_getn(iter, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_getn( + yyjson_mut_obj_iter *iter, const char *key, size_t key_len) { + if (iter && key) { + size_t idx = 0; + size_t max = iter->max; + yyjson_mut_val *pre, *cur = iter->cur; + while (idx++ < max) { + pre = cur; + cur = cur->next->next; + if (unsafe_yyjson_equals_strn(cur, key, key_len)) { + iter->idx += idx; + if (iter->idx > max) iter->idx -= max + 1; + iter->pre = pre; + iter->cur = cur; + return cur->next; + } + } + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Object Creation API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj(yyjson_mut_doc *doc) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_OBJ | YYJSON_SUBTYPE_NONE; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_str(yyjson_mut_doc *doc, + const char **keys, + const char **vals, + size_t count) { + if (yyjson_likely(doc && ((count > 0 && keys && vals) || (count == 0)))) { + yyjson_mut_val *obj = unsafe_yyjson_mut_val(doc, 1 + count * 2); + if (yyjson_likely(obj)) { + obj->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_OBJ; + if (count > 0) { + size_t i; + for (i = 0; i < count; i++) { + yyjson_mut_val *key = obj + (i * 2 + 1); + yyjson_mut_val *val = obj + (i * 2 + 2); + uint64_t key_len = (uint64_t)strlen(keys[i]); + uint64_t val_len = (uint64_t)strlen(vals[i]); + key->tag = (key_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->tag = (val_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + key->uni.str = keys[i]; + val->uni.str = vals[i]; + key->next = val; + val->next = val + 1; + } + obj[count * 2].next = obj + 1; + obj->uni.ptr = obj + (count * 2 - 1); + } + return obj; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_kv(yyjson_mut_doc *doc, + const char **pairs, + size_t count) { + if (yyjson_likely(doc && ((count > 0 && pairs) || (count == 0)))) { + yyjson_mut_val *obj = unsafe_yyjson_mut_val(doc, 1 + count * 2); + if (yyjson_likely(obj)) { + obj->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_OBJ; + if (count > 0) { + size_t i; + for (i = 0; i < count; i++) { + yyjson_mut_val *key = obj + (i * 2 + 1); + yyjson_mut_val *val = obj + (i * 2 + 2); + const char *key_str = pairs[i * 2 + 0]; + const char *val_str = pairs[i * 2 + 1]; + uint64_t key_len = (uint64_t)strlen(key_str); + uint64_t val_len = (uint64_t)strlen(val_str); + key->tag = (key_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->tag = (val_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + key->uni.str = key_str; + val->uni.str = val_str; + key->next = val; + val->next = val + 1; + } + obj[count * 2].next = obj + 1; + obj->uni.ptr = obj + (count * 2 - 1); + } + return obj; + } + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Object Modification API (Implementation) + *============================================================================*/ + +yyjson_api_inline void unsafe_yyjson_mut_obj_add(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val, + size_t len) { + if (yyjson_likely(len)) { + yyjson_mut_val *prev_val = ((yyjson_mut_val *)obj->uni.ptr)->next; + yyjson_mut_val *next_key = prev_val->next; + prev_val->next = key; + val->next = next_key; + } else { + val->next = key; + } + key->next = val; + obj->uni.ptr = (void *)key; + unsafe_yyjson_set_len(obj, len + 1); +} + +yyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_obj_remove( + yyjson_mut_val *obj, const char *key, size_t key_len) { + size_t obj_len = unsafe_yyjson_get_len(obj); + if (obj_len) { + yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr; + yyjson_mut_val *cur_key = pre_key->next->next; + yyjson_mut_val *removed_item = NULL; + size_t i; + for (i = 0; i < obj_len; i++) { + if (unsafe_yyjson_equals_strn(cur_key, key, key_len)) { + if (!removed_item) removed_item = cur_key->next; + cur_key = cur_key->next->next; + pre_key->next->next = cur_key; + if (i + 1 == obj_len) obj->uni.ptr = pre_key; + i--; + obj_len--; + } else { + pre_key = cur_key; + cur_key = cur_key->next->next; + } + } + unsafe_yyjson_set_len(obj, obj_len); + return removed_item; + } else { + return NULL; + } +} + +yyjson_api_inline bool unsafe_yyjson_mut_obj_replace(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val) { + size_t key_len = unsafe_yyjson_get_len(key); + size_t obj_len = unsafe_yyjson_get_len(obj); + if (obj_len) { + yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr; + yyjson_mut_val *cur_key = pre_key->next->next; + size_t i; + for (i = 0; i < obj_len; i++) { + if (unsafe_yyjson_equals_strn(cur_key, key->uni.str, key_len)) { + cur_key->next->tag = val->tag; + cur_key->next->uni.u64 = val->uni.u64; + return true; + } else { + cur_key = cur_key->next->next; + } + } + } + return false; +} + +yyjson_api_inline void unsafe_yyjson_mut_obj_rotate(yyjson_mut_val *obj, + size_t idx) { + yyjson_mut_val *key = (yyjson_mut_val *)obj->uni.ptr; + while (idx-- > 0) key = key->next->next; + obj->uni.ptr = (void *)key; +} + +yyjson_api_inline bool yyjson_mut_obj_add(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && + yyjson_mut_is_str(key) && val)) { + unsafe_yyjson_mut_obj_add(obj, key, val, unsafe_yyjson_get_len(obj)); + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_obj_put(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val) { + bool replaced = false; + size_t key_len; + yyjson_mut_obj_iter iter; + yyjson_mut_val *cur_key; + if (yyjson_unlikely(!yyjson_mut_is_obj(obj) || + !yyjson_mut_is_str(key))) return false; + key_len = unsafe_yyjson_get_len(key); + yyjson_mut_obj_iter_init(obj, &iter); + while ((cur_key = yyjson_mut_obj_iter_next(&iter)) != 0) { + if (unsafe_yyjson_equals_strn(cur_key, key->uni.str, key_len)) { + if (!replaced && val) { + replaced = true; + val->next = cur_key->next->next; + cur_key->next = val; + } else { + yyjson_mut_obj_iter_remove(&iter); + } + } + } + if (!replaced && val) unsafe_yyjson_mut_obj_add(obj, key, val, iter.max); + return true; +} + +yyjson_api_inline bool yyjson_mut_obj_insert(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val, + size_t idx) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && + yyjson_mut_is_str(key) && val)) { + size_t len = unsafe_yyjson_get_len(obj); + if (yyjson_likely(len >= idx)) { + if (len > idx) { + void *ptr = obj->uni.ptr; + unsafe_yyjson_mut_obj_rotate(obj, idx); + unsafe_yyjson_mut_obj_add(obj, key, val, len); + obj->uni.ptr = ptr; + } else { + unsafe_yyjson_mut_obj_add(obj, key, val, len); + } + return true; + } + } + return false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove(yyjson_mut_val *obj, + yyjson_mut_val *key) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && yyjson_mut_is_str(key))) { + return unsafe_yyjson_mut_obj_remove(obj, key->uni.str, + unsafe_yyjson_get_len(key)); + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_key( + yyjson_mut_val *obj, const char *key) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && key)) { + size_t key_len = strlen(key); + return unsafe_yyjson_mut_obj_remove(obj, key, key_len); + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_keyn( + yyjson_mut_val *obj, const char *key, size_t key_len) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && key)) { + return unsafe_yyjson_mut_obj_remove(obj, key, key_len); + } + return NULL; +} + +yyjson_api_inline bool yyjson_mut_obj_clear(yyjson_mut_val *obj) { + if (yyjson_likely(yyjson_mut_is_obj(obj))) { + unsafe_yyjson_set_len(obj, 0); + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_obj_replace(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && + yyjson_mut_is_str(key) && val)) { + return unsafe_yyjson_mut_obj_replace(obj, key, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_obj_rotate(yyjson_mut_val *obj, + size_t idx) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && + unsafe_yyjson_get_len(obj) > idx)) { + unsafe_yyjson_mut_obj_rotate(obj, idx); + return true; + } + return false; +} + + + +/*============================================================================== + * Mutable JSON Object Modification Convenience API (Implementation) + *============================================================================*/ + +#define yyjson_mut_obj_add_func(func) \ + if (yyjson_likely(doc && yyjson_mut_is_obj(obj) && _key)) { \ + yyjson_mut_val *key = unsafe_yyjson_mut_val(doc, 2); \ + if (yyjson_likely(key)) { \ + size_t len = unsafe_yyjson_get_len(obj); \ + yyjson_mut_val *val = key + 1; \ + size_t key_len = strlen(_key); \ + bool noesc = unsafe_yyjson_is_str_noesc(_key, key_len); \ + key->tag = YYJSON_TYPE_STR; \ + key->tag |= noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; \ + key->tag |= (uint64_t)strlen(_key) << YYJSON_TAG_BIT; \ + key->uni.str = _key; \ + func \ + unsafe_yyjson_mut_obj_add(obj, key, val, len); \ + return true; \ + } \ + } \ + return false + +yyjson_api_inline bool yyjson_mut_obj_add_null(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_NULL | YYJSON_SUBTYPE_NONE; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_true(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_false(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_bool(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + bool _val) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_BOOL | (uint8_t)((uint8_t)(_val) << 3); + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_uint(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + uint64_t _val) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + val->uni.u64 = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_sint(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + int64_t _val) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_int(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + int64_t _val) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_real(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + double _val) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.f64 = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_str(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + const char *_val) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + size_t val_len = strlen(_val); + bool val_noesc = unsafe_yyjson_is_str_noesc(_val, val_len); + val->tag = ((uint64_t)strlen(_val) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->tag |= val_noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; + val->uni.str = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_strn(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + const char *_val, + size_t _len) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_strcpy(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + const char *_val) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + size_t _len = strlen(_val); + val->uni.str = unsafe_yyjson_mut_strncpy(doc, _val, _len); + if (yyjson_unlikely(!val->uni.str)) return false; + val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_strncpy(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + const char *_val, + size_t _len) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + val->uni.str = unsafe_yyjson_mut_strncpy(doc, _val, _len); + if (yyjson_unlikely(!val->uni.str)) return false; + val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_arr(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key) { + yyjson_mut_val *key = yyjson_mut_str(doc, _key); + yyjson_mut_val *val = yyjson_mut_arr(doc); + return yyjson_mut_obj_add(obj, key, val) ? val : NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_obj(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key) { + yyjson_mut_val *key = yyjson_mut_str(doc, _key); + yyjson_mut_val *val = yyjson_mut_obj(doc); + return yyjson_mut_obj_add(obj, key, val) ? val : NULL; +} + +yyjson_api_inline bool yyjson_mut_obj_add_val(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + yyjson_mut_val *_val) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + val = _val; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_str(yyjson_mut_val *obj, + const char *key) { + return yyjson_mut_obj_remove_strn(obj, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_strn( + yyjson_mut_val *obj, const char *_key, size_t _len) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && _key)) { + yyjson_mut_val *key; + yyjson_mut_obj_iter iter; + yyjson_mut_val *val_removed = NULL; + yyjson_mut_obj_iter_init(obj, &iter); + while ((key = yyjson_mut_obj_iter_next(&iter)) != NULL) { + if (unsafe_yyjson_equals_strn(key, _key, _len)) { + if (!val_removed) val_removed = key->next; + yyjson_mut_obj_iter_remove(&iter); + } + } + return val_removed; + } + return NULL; +} + +yyjson_api_inline bool yyjson_mut_obj_rename_key(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *new_key) { + if (!key || !new_key) return false; + return yyjson_mut_obj_rename_keyn(doc, obj, key, strlen(key), + new_key, strlen(new_key)); +} + +yyjson_api_inline bool yyjson_mut_obj_rename_keyn(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + size_t len, + const char *new_key, + size_t new_len) { + char *cpy_key = NULL; + yyjson_mut_val *old_key; + yyjson_mut_obj_iter iter; + if (!doc || !obj || !key || !new_key) return false; + yyjson_mut_obj_iter_init(obj, &iter); + while ((old_key = yyjson_mut_obj_iter_next(&iter))) { + if (unsafe_yyjson_equals_strn((void *)old_key, key, len)) { + if (!cpy_key) { + cpy_key = unsafe_yyjson_mut_strncpy(doc, new_key, new_len); + if (!cpy_key) return false; + } + yyjson_mut_set_strn(old_key, cpy_key, new_len); + } + } + return cpy_key != NULL; +} + + + +/*============================================================================== + * JSON Pointer API (Implementation) + *============================================================================*/ + +#define yyjson_ptr_set_err(_code, _msg) do { \ + if (err) { \ + err->code = YYJSON_PTR_ERR_##_code; \ + err->msg = _msg; \ + err->pos = 0; \ + } \ +} while(false) + +/* require: val != NULL, *ptr == '/', len > 0 */ +yyjson_api yyjson_val *unsafe_yyjson_ptr_getx(yyjson_val *val, + const char *ptr, size_t len, + yyjson_ptr_err *err); + +/* require: val != NULL, *ptr == '/', len > 0 */ +yyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_getx(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/* require: val/new_val/doc != NULL, *ptr == '/', len > 0 */ +yyjson_api bool unsafe_yyjson_mut_ptr_putx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, bool insert_new, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/* require: val/err != NULL, *ptr == '/', len > 0 */ +yyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_replacex( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); + +/* require: val/err != NULL, *ptr == '/', len > 0 */ +yyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_removex(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +yyjson_api_inline yyjson_val *yyjson_doc_ptr_get(yyjson_doc *doc, + const char *ptr) { + if (yyjson_unlikely(!ptr)) return NULL; + return yyjson_doc_ptr_getn(doc, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_val *yyjson_doc_ptr_getn(yyjson_doc *doc, + const char *ptr, size_t len) { + return yyjson_doc_ptr_getx(doc, ptr, len, NULL); +} + +yyjson_api_inline yyjson_val *yyjson_doc_ptr_getx(yyjson_doc *doc, + const char *ptr, size_t len, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (yyjson_unlikely(!doc || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + return doc->root; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_ptr_getx(doc->root, ptr, len, err); +} + +yyjson_api_inline yyjson_val *yyjson_ptr_get(yyjson_val *val, + const char *ptr) { + if (yyjson_unlikely(!ptr)) return NULL; + return yyjson_ptr_getn(val, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_val *yyjson_ptr_getn(yyjson_val *val, + const char *ptr, size_t len) { + return yyjson_ptr_getx(val, ptr, len, NULL); +} + +yyjson_api_inline yyjson_val *yyjson_ptr_getx(yyjson_val *val, + const char *ptr, size_t len, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (yyjson_unlikely(!val || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + return val; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_ptr_getx(val, ptr, len, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_get(yyjson_mut_doc *doc, + const char *ptr) { + if (!ptr) return NULL; + return yyjson_mut_doc_ptr_getn(doc, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getn(yyjson_mut_doc *doc, + const char *ptr, + size_t len) { + return yyjson_mut_doc_ptr_getx(doc, ptr, len, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getx(yyjson_mut_doc *doc, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + return doc->root; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_getx(doc->root, ptr, len, ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_get(yyjson_mut_val *val, + const char *ptr) { + if (!ptr) return NULL; + return yyjson_mut_ptr_getn(val, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getn(yyjson_mut_val *val, + const char *ptr, + size_t len) { + return yyjson_mut_ptr_getx(val, ptr, len, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getx(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + return val; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_add(yyjson_mut_doc *doc, + const char *ptr, + yyjson_mut_val *new_val) { + if (yyjson_unlikely(!ptr)) return false; + return yyjson_mut_doc_ptr_addn(doc, ptr, strlen(ptr), new_val); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_addn(yyjson_mut_doc *doc, + const char *ptr, + size_t len, + yyjson_mut_val *new_val) { + return yyjson_mut_doc_ptr_addx(doc, ptr, len, new_val, true, NULL, NULL); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_addx(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr || !new_val)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return false; + } + if (yyjson_unlikely(len == 0)) { + if (doc->root) { + yyjson_ptr_set_err(SET_ROOT, "cannot set document's root"); + return false; + } else { + doc->root = new_val; + return true; + } + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return false; + } + if (yyjson_unlikely(!doc->root && !create_parent)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return false; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_mut_val *root = yyjson_mut_obj(doc); + if (yyjson_unlikely(!root)) { + yyjson_ptr_set_err(MEMORY_ALLOCATION, "failed to create value"); + return false; + } + if (unsafe_yyjson_mut_ptr_putx(root, ptr, len, new_val, doc, + create_parent, true, ctx, err)) { + doc->root = root; + return true; + } + return false; + } + return unsafe_yyjson_mut_ptr_putx(doc->root, ptr, len, new_val, doc, + create_parent, true, ctx, err); +} + +yyjson_api_inline bool yyjson_mut_ptr_add(yyjson_mut_val *val, + const char *ptr, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc) { + if (yyjson_unlikely(!ptr)) return false; + return yyjson_mut_ptr_addn(val, ptr, strlen(ptr), new_val, doc); +} + +yyjson_api_inline bool yyjson_mut_ptr_addn(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc) { + return yyjson_mut_ptr_addx(val, ptr, len, new_val, doc, true, NULL, NULL); +} + +yyjson_api_inline bool yyjson_mut_ptr_addx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr || !new_val || !doc)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return false; + } + if (yyjson_unlikely(len == 0)) { + yyjson_ptr_set_err(SET_ROOT, "cannot set root"); + return false; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return false; + } + return unsafe_yyjson_mut_ptr_putx(val, ptr, len, new_val, + doc, create_parent, true, ctx, err); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_set(yyjson_mut_doc *doc, + const char *ptr, + yyjson_mut_val *new_val) { + if (yyjson_unlikely(!ptr)) return false; + return yyjson_mut_doc_ptr_setn(doc, ptr, strlen(ptr), new_val); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_setn(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val) { + return yyjson_mut_doc_ptr_setx(doc, ptr, len, new_val, true, NULL, NULL); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_setx(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return false; + } + if (yyjson_unlikely(len == 0)) { + if (ctx) ctx->old = doc->root; + doc->root = new_val; + return true; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return false; + } + if (!new_val) { + if (!doc->root) { + yyjson_ptr_set_err(RESOLVE, "JSON pointer cannot be resolved"); + return false; + } + return !!unsafe_yyjson_mut_ptr_removex(doc->root, ptr, len, ctx, err); + } + if (yyjson_unlikely(!doc->root && !create_parent)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return false; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_mut_val *root = yyjson_mut_obj(doc); + if (yyjson_unlikely(!root)) { + yyjson_ptr_set_err(MEMORY_ALLOCATION, "failed to create value"); + return false; + } + if (unsafe_yyjson_mut_ptr_putx(root, ptr, len, new_val, doc, + create_parent, false, ctx, err)) { + doc->root = root; + return true; + } + return false; + } + return unsafe_yyjson_mut_ptr_putx(doc->root, ptr, len, new_val, doc, + create_parent, false, ctx, err); +} + +yyjson_api_inline bool yyjson_mut_ptr_set(yyjson_mut_val *val, + const char *ptr, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc) { + if (yyjson_unlikely(!ptr)) return false; + return yyjson_mut_ptr_setn(val, ptr, strlen(ptr), new_val, doc); +} + +yyjson_api_inline bool yyjson_mut_ptr_setn(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc) { + return yyjson_mut_ptr_setx(val, ptr, len, new_val, doc, true, NULL, NULL); +} + +yyjson_api_inline bool yyjson_mut_ptr_setx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr || !doc)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return false; + } + if (yyjson_unlikely(len == 0)) { + yyjson_ptr_set_err(SET_ROOT, "cannot set root"); + return false; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return false; + } + if (!new_val) { + return !!unsafe_yyjson_mut_ptr_removex(val, ptr, len, ctx, err); + } + return unsafe_yyjson_mut_ptr_putx(val, ptr, len, new_val, doc, + create_parent, false, ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replace( + yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val) { + if (!ptr) return NULL; + return yyjson_mut_doc_ptr_replacen(doc, ptr, strlen(ptr), new_val); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacen( + yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val) { + return yyjson_mut_doc_ptr_replacex(doc, ptr, len, new_val, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacex( + yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr || !new_val)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + yyjson_mut_val *root = doc->root; + if (yyjson_unlikely(!root)) { + yyjson_ptr_set_err(RESOLVE, "JSON pointer cannot be resolved"); + return NULL; + } + if (ctx) ctx->old = root; + doc->root = new_val; + return root; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return NULL; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_replacex(doc->root, ptr, len, new_val, + ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replace( + yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val) { + if (!ptr) return NULL; + return yyjson_mut_ptr_replacen(val, ptr, strlen(ptr), new_val); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacen( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val) { + return yyjson_mut_ptr_replacex(val, ptr, len, new_val, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacex( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr || !new_val)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + yyjson_ptr_set_err(SET_ROOT, "cannot set root"); + return NULL; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_replacex(val, ptr, len, new_val, ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_remove( + yyjson_mut_doc *doc, const char *ptr) { + if (!ptr) return NULL; + return yyjson_mut_doc_ptr_removen(doc, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removen( + yyjson_mut_doc *doc, const char *ptr, size_t len) { + return yyjson_mut_doc_ptr_removex(doc, ptr, len, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removex( + yyjson_mut_doc *doc, const char *ptr, size_t len, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + yyjson_mut_val *root = doc->root; + if (ctx) ctx->old = root; + doc->root = NULL; + return root; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_removex(doc->root, ptr, len, ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_remove(yyjson_mut_val *val, + const char *ptr) { + if (!ptr) return NULL; + return yyjson_mut_ptr_removen(val, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removen(yyjson_mut_val *val, + const char *ptr, + size_t len) { + return yyjson_mut_ptr_removex(val, ptr, len, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removex(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + yyjson_ptr_set_err(SET_ROOT, "cannot set root"); + return NULL; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_removex(val, ptr, len, ctx, err); +} + +yyjson_api_inline bool yyjson_ptr_ctx_append(yyjson_ptr_ctx *ctx, + yyjson_mut_val *key, + yyjson_mut_val *val) { + yyjson_mut_val *ctn, *pre_key, *pre_val, *cur_key, *cur_val; + if (!ctx || !ctx->ctn || !val) return false; + ctn = ctx->ctn; + + if (yyjson_mut_is_obj(ctn)) { + if (!key) return false; + key->next = val; + pre_key = ctx->pre; + if (unsafe_yyjson_get_len(ctn) == 0) { + val->next = key; + ctn->uni.ptr = key; + ctx->pre = key; + } else if (!pre_key) { + pre_key = (yyjson_mut_val *)ctn->uni.ptr; + pre_val = pre_key->next; + val->next = pre_val->next; + pre_val->next = key; + ctn->uni.ptr = key; + ctx->pre = pre_key; + } else { + cur_key = pre_key->next->next; + cur_val = cur_key->next; + val->next = cur_val->next; + cur_val->next = key; + if (ctn->uni.ptr == cur_key) ctn->uni.ptr = key; + ctx->pre = cur_key; + } + } else { + pre_val = ctx->pre; + if (unsafe_yyjson_get_len(ctn) == 0) { + val->next = val; + ctn->uni.ptr = val; + ctx->pre = val; + } else if (!pre_val) { + pre_val = (yyjson_mut_val *)ctn->uni.ptr; + val->next = pre_val->next; + pre_val->next = val; + ctn->uni.ptr = val; + ctx->pre = pre_val; + } else { + cur_val = pre_val->next; + val->next = cur_val->next; + cur_val->next = val; + if (ctn->uni.ptr == cur_val) ctn->uni.ptr = val; + ctx->pre = cur_val; + } + } + unsafe_yyjson_inc_len(ctn); + return true; +} + +yyjson_api_inline bool yyjson_ptr_ctx_replace(yyjson_ptr_ctx *ctx, + yyjson_mut_val *val) { + yyjson_mut_val *ctn, *pre_key, *cur_key, *pre_val, *cur_val; + if (!ctx || !ctx->ctn || !ctx->pre || !val) return false; + ctn = ctx->ctn; + if (yyjson_mut_is_obj(ctn)) { + pre_key = ctx->pre; + pre_val = pre_key->next; + cur_key = pre_val->next; + cur_val = cur_key->next; + /* replace current value */ + cur_key->next = val; + val->next = cur_val->next; + ctx->old = cur_val; + } else { + pre_val = ctx->pre; + cur_val = pre_val->next; + /* replace current value */ + if (pre_val != cur_val) { + val->next = cur_val->next; + pre_val->next = val; + if (ctn->uni.ptr == cur_val) ctn->uni.ptr = val; + } else { + val->next = val; + ctn->uni.ptr = val; + ctx->pre = val; + } + ctx->old = cur_val; + } + return true; +} + +yyjson_api_inline bool yyjson_ptr_ctx_remove(yyjson_ptr_ctx *ctx) { + yyjson_mut_val *ctn, *pre_key, *pre_val, *cur_key, *cur_val; + size_t len; + if (!ctx || !ctx->ctn || !ctx->pre) return false; + ctn = ctx->ctn; + if (yyjson_mut_is_obj(ctn)) { + pre_key = ctx->pre; + pre_val = pre_key->next; + cur_key = pre_val->next; + cur_val = cur_key->next; + /* remove current key-value */ + pre_val->next = cur_val->next; + if (ctn->uni.ptr == cur_key) ctn->uni.ptr = pre_key; + ctx->pre = NULL; + ctx->old = cur_val; + } else { + pre_val = ctx->pre; + cur_val = pre_val->next; + /* remove current key-value */ + pre_val->next = cur_val->next; + if (ctn->uni.ptr == cur_val) ctn->uni.ptr = pre_val; + ctx->pre = NULL; + ctx->old = cur_val; + } + len = unsafe_yyjson_get_len(ctn) - 1; + if (len == 0) ctn->uni.ptr = NULL; + unsafe_yyjson_set_len(ctn, len); + return true; +} + +#undef yyjson_ptr_set_err + + + +/*============================================================================== + * JSON Value at Pointer API (Implementation) + *============================================================================*/ + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type bool. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_bool( + yyjson_val *root, const char *ptr, bool *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_bool(val)) { + *value = unsafe_yyjson_get_bool(val); + return true; + } else { + return false; + } +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type uint. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_uint( + yyjson_val *root, const char *ptr, uint64_t *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_uint(val)) { + *value = unsafe_yyjson_get_uint(val); + return true; + } else { + return false; + } +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type sint. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_sint( + yyjson_val *root, const char *ptr, int64_t *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_sint(val)) { + *value = unsafe_yyjson_get_sint(val); + return true; + } else { + return false; + } +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type real. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_real( + yyjson_val *root, const char *ptr, double *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_real(val)) { + *value = unsafe_yyjson_get_real(val); + return true; + } else { + return false; + } +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type sint, + uint or real. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_num( + yyjson_val *root, const char *ptr, double *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_num(val)) { + *value = unsafe_yyjson_get_num(val); + return true; + } else { + return false; + } +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type string. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_str( + yyjson_val *root, const char *ptr, const char **value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_str(val)) { + *value = unsafe_yyjson_get_str(val); + return true; + } else { + return false; + } +} + + + +/*============================================================================== + * Deprecated + *============================================================================*/ + +/** @deprecated renamed to `yyjson_doc_ptr_get` */ +yyjson_deprecated("renamed to yyjson_doc_ptr_get") +yyjson_api_inline yyjson_val *yyjson_doc_get_pointer(yyjson_doc *doc, + const char *ptr) { + return yyjson_doc_ptr_get(doc, ptr); +} + +/** @deprecated renamed to `yyjson_doc_ptr_getn` */ +yyjson_deprecated("renamed to yyjson_doc_ptr_getn") +yyjson_api_inline yyjson_val *yyjson_doc_get_pointern(yyjson_doc *doc, + const char *ptr, + size_t len) { + return yyjson_doc_ptr_getn(doc, ptr, len); +} + +/** @deprecated renamed to `yyjson_mut_doc_ptr_get` */ +yyjson_deprecated("renamed to yyjson_mut_doc_ptr_get") +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_pointer( + yyjson_mut_doc *doc, const char *ptr) { + return yyjson_mut_doc_ptr_get(doc, ptr); +} + +/** @deprecated renamed to `yyjson_mut_doc_ptr_getn` */ +yyjson_deprecated("renamed to yyjson_mut_doc_ptr_getn") +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_pointern( + yyjson_mut_doc *doc, const char *ptr, size_t len) { + return yyjson_mut_doc_ptr_getn(doc, ptr, len); +} + +/** @deprecated renamed to `yyjson_ptr_get` */ +yyjson_deprecated("renamed to yyjson_ptr_get") +yyjson_api_inline yyjson_val *yyjson_get_pointer(yyjson_val *val, + const char *ptr) { + return yyjson_ptr_get(val, ptr); +} + +/** @deprecated renamed to `yyjson_ptr_getn` */ +yyjson_deprecated("renamed to yyjson_ptr_getn") +yyjson_api_inline yyjson_val *yyjson_get_pointern(yyjson_val *val, + const char *ptr, + size_t len) { + return yyjson_ptr_getn(val, ptr, len); +} + +/** @deprecated renamed to `yyjson_mut_ptr_get` */ +yyjson_deprecated("renamed to yyjson_mut_ptr_get") +yyjson_api_inline yyjson_mut_val *yyjson_mut_get_pointer(yyjson_mut_val *val, + const char *ptr) { + return yyjson_mut_ptr_get(val, ptr); +} + +/** @deprecated renamed to `yyjson_mut_ptr_getn` */ +yyjson_deprecated("renamed to yyjson_mut_ptr_getn") +yyjson_api_inline yyjson_mut_val *yyjson_mut_get_pointern(yyjson_mut_val *val, + const char *ptr, + size_t len) { + return yyjson_mut_ptr_getn(val, ptr, len); +} + +/** @deprecated renamed to `yyjson_mut_ptr_getn` */ +yyjson_deprecated("renamed to unsafe_yyjson_ptr_getn") +yyjson_api_inline yyjson_val *unsafe_yyjson_get_pointer(yyjson_val *val, + const char *ptr, + size_t len) { + yyjson_ptr_err err; + return unsafe_yyjson_ptr_getx(val, ptr, len, &err); +} + +/** @deprecated renamed to `unsafe_yyjson_mut_ptr_getx` */ +yyjson_deprecated("renamed to unsafe_yyjson_mut_ptr_getx") +yyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_get_pointer( + yyjson_mut_val *val, const char *ptr, size_t len) { + yyjson_ptr_err err; + return unsafe_yyjson_mut_ptr_getx(val, ptr, len, NULL, &err); +} + + + +/*============================================================================== + * Compiler Hint End + *============================================================================*/ + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic pop +# endif +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif /* warning suppress end */ + +#ifdef __cplusplus +} +#endif /* extern "C" end */ + +#endif /* YYJSON_H */