Last active
November 26, 2015 20:12
-
-
Save xor-gate/3eb721f64bce27db8a31 to your computer and use it in GitHub Desktop.
c-struct-serializer-msgpack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
aa |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CC:=clang-3.5 | |
CFLAGS:=-Wall -Wextra -std=c99 -ggdb -O0 -I. | |
star_table: star_table.c | |
$(CC) $(CFLAGS) -o $@ $< mpack.c | |
test: star_table | |
./star_table; ~/bin/msgpack-cli decode test.bin --pp | |
pp: star_table.c | |
gcc -I. -E -P $< > star_table.c.pp | |
clean: | |
rm -f test.bin | |
rm -f star_table | |
rm -f star_table.c.pp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This is a sample MPack configuration file. Copy it to mpack-config.h somewhere | |
* in your project's include tree and, optionally, edit it to suit your setup. | |
* In most cases you can leave this file with the default config. | |
*/ | |
#ifndef MPACK_CONFIG_H | |
#define MPACK_CONFIG_H 1 | |
/* | |
* Features | |
*/ | |
/** Enables compilation of the base Tag Reader. */ | |
#define MPACK_READER 1 | |
/** Enables compilation of the static Expect API. */ | |
#define MPACK_EXPECT 1 | |
/** Enables compilation of the dynamic Node API. */ | |
#define MPACK_NODE 1 | |
/** Enables compilation of the Writer. */ | |
#define MPACK_WRITER 1 | |
/* | |
* Dependencies | |
*/ | |
/** | |
* Enables the use of C stdlib. This allows the library to use malloc | |
* for debugging and in allocation helpers. | |
*/ | |
#define MPACK_STDLIB 1 | |
/** | |
* Enables the use of C stdio. This adds helpers for easily | |
* reading/writing C files and makes debugging easier. | |
*/ | |
#define MPACK_STDIO 1 | |
/** | |
* \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. | |
* | |
* A memory allocator is required for the Node API. | |
*/ | |
/** | |
* \def MPACK_REALLOC | |
* | |
* Defines the realloc function used by mpack. It is used by growable buffers | |
* to resize more quickly. | |
* | |
* This is optional, even when MPACK_MALLOC is used. If MPACK_MALLOC is | |
* set and MPACK_REALLOC is not, MPACK_MALLOC is used with a simple copy | |
* to grow buffers. | |
*/ | |
#if defined(MPACK_STDLIB) && !defined(MPACK_MALLOC) | |
#define MPACK_MALLOC malloc | |
#define MPACK_REALLOC realloc | |
#endif | |
/** | |
* \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. | |
* | |
* A memory allocator is required for the Node API. | |
*/ | |
#if defined(MPACK_STDLIB) && !defined(MPACK_FREE) | |
#define MPACK_FREE free | |
#endif | |
/** | |
* Enables the setjmp()/longjmp() error handling option. MPACK_MALLOC is required. | |
* | |
* Note that you don't have to use it; this just enables the option. It can be | |
* disabled to avoid the dependency on setjmp.h . | |
*/ | |
#if defined(MPACK_MALLOC) | |
#define MPACK_SETJMP 1 | |
#endif | |
/* | |
* Debugging options | |
*/ | |
/** | |
* \def MPACK_DEBUG | |
* | |
* Enables debug features. You may want to wrap this around your | |
* own debug preprocs. By default, they are enabled if DEBUG or _DEBUG | |
* are defined. | |
* | |
* Note that MPACK_DEBUG cannot be defined differently for different | |
* source files because it affects layout of structs defined in header | |
* files. Your entire project must be compiled with the same value of | |
* MPACK_DEBUG. (This is why NDEBUG is not used.) | |
*/ | |
#if defined(DEBUG) || defined(_DEBUG) | |
#define MPACK_DEBUG 1 | |
#else | |
#define MPACK_DEBUG 0 | |
#endif | |
/** | |
* Set this to 1 to implement a custom mpack_assert_fail() function. This | |
* function must not return, and must have the following signature: | |
* | |
* void mpack_assert_fail(const char* message) | |
* | |
* Asserts are only used when MPACK_DEBUG is enabled, and can be triggered | |
* by bugs in mpack or bugs due to incorrect usage of mpack. | |
*/ | |
#define MPACK_CUSTOM_ASSERT 0 | |
/** | |
* \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 malloc() is | |
* available.) | |
*/ | |
#if MPACK_DEBUG && MPACK_READER && defined(MPACK_MALLOC) | |
#define MPACK_READ_TRACKING 1 | |
#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 malloc() is | |
* available.) | |
*/ | |
#if MPACK_DEBUG && MPACK_WRITER && defined(MPACK_MALLOC) | |
#define MPACK_WRITE_TRACKING 1 | |
#endif | |
/* | |
* Miscellaneous | |
*/ | |
/** | |
* Stack space to use when initializing a reader or writer with a | |
* stack-allocated buffer. | |
*/ | |
#define MPACK_STACK_SIZE 4096 | |
/** | |
* Buffer size to use for allocated buffers (such as for a file writer.) | |
*/ | |
#define MPACK_BUFFER_SIZE 65536 | |
/** | |
* Number of nodes in each allocated node page. | |
* | |
* Nodes are 16 bytes when compiled for a 32-bit architecture and | |
* 24 bytes when compiled for a 64-bit architecture. | |
* | |
* Using as many nodes fit in one memory page seems to provide the | |
* best performance, and has very little waste when parsing small | |
* messages. | |
*/ | |
#define MPACK_NODE_PAGE_SIZE (4096 / sizeof(mpack_node_t)) | |
/** | |
* 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. | |
*/ | |
#define MPACK_NODE_INITIAL_DEPTH 8 | |
/** | |
* The maximum depth for the node parser if MPACK_MALLOC is not available. | |
* The parsing stack is placed on the call stack. | |
*/ | |
#define MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC 32 | |
#endif | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2015 Nicholas Fraser | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
* | |
*/ | |
/* | |
* This is the MPack 0.6 amalgamation package. | |
* | |
* http://github.com/ludocode/mpack | |
*/ | |
#define MPACK_INTERNAL 1 | |
#define MPACK_EMIT_INLINE_DEFS 1 | |
#include "mpack.h" | |
/* mpack-platform.c */ | |
// We define MPACK_EMIT_INLINE_DEFS and include mpack.h to emit | |
// standalone definitions of all (non-static) inline functions in MPack. | |
#define MPACK_INTERNAL 1 | |
#define MPACK_EMIT_INLINE_DEFS 1 | |
/* #include "mpack-platform.h" */ | |
/* #include "mpack.h" */ | |
#if MPACK_DEBUG && MPACK_STDIO | |
#include <stdarg.h> | |
#endif | |
#if MPACK_DEBUG && 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(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_break_hit(const char* message) { | |
// If we have a custom assert handler, break just wraps it | |
// for simplicity. | |
mpack_assert_fail(message); | |
} | |
#else | |
void mpack_assert_fail(const char* message) { | |
MPACK_UNUSED(message); | |
#if MPACK_STDIO | |
fprintf(stderr, "%s\n", message); | |
#endif | |
#if defined(__GCC__) || defined(__clang__) | |
__builtin_trap(); | |
#elif defined(WIN32) | |
__debugbreak(); | |
#endif | |
#if MPACK_STDLIB | |
abort(); | |
#elif defined(__GCC__) || defined(__clang__) | |
__builtin_abort(); | |
#endif | |
MPACK_UNREACHABLE; | |
} | |
void mpack_break_hit(const char* message) { | |
MPACK_UNUSED(message); | |
#if MPACK_STDIO | |
fprintf(stderr, "%s\n", message); | |
#endif | |
#if defined(__GCC__) || defined(__clang__) | |
__builtin_trap(); | |
#elif defined(WIN32) | |
__debugbreak(); | |
#elif MPACK_STDLIB | |
abort(); | |
#elif defined(__GCC__) || defined(__clang__) | |
__builtin_abort(); | |
#endif | |
} | |
#endif | |
#if !MPACK_STDLIB | |
// The below are adapted from the C wikibook: | |
// https://en.wikibooks.org/wiki/C_Programming/Strings | |
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; | |
} | |
void* mpack_memcpy(void *s1, const void *s2, size_t n) { | |
char * __restrict dst = (char *)s1; | |
const char * __restrict src = (const char *)s2; | |
while (n-- != 0) | |
*dst++ = *src++; | |
return s1; | |
} | |
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; | |
} | |
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; | |
} | |
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) | |
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-common.c */ | |
#define MPACK_INTERNAL 1 | |
/* #include "mpack-common.h" */ | |
#if MPACK_DEBUG && MPACK_STDIO | |
#include <stdarg.h> | |
#endif | |
const char* mpack_error_to_string(mpack_error_t error) { | |
#if MPACK_DEBUG | |
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_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); | |
#undef MPACK_ERROR_STRING_CASE | |
default: break; | |
} | |
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_DEBUG | |
switch (type) { | |
#define MPACK_TYPE_STRING_CASE(e) case e: return #e | |
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_ext); | |
MPACK_TYPE_STRING_CASE(mpack_type_array); | |
MPACK_TYPE_STRING_CASE(mpack_type_map); | |
#undef MPACK_TYPE_STRING_CASE | |
default: break; | |
} | |
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 = left.v.i; | |
} | |
if (right.type == mpack_type_int && right.v.i >= 0) { | |
right.type = mpack_type_uint; | |
right.v.u = right.v.i; | |
} | |
if (left.type != right.type) | |
return (int)left.type - (int)right.type; | |
switch (left.type) { | |
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; | |
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; | |
// 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)); | |
default: | |
break; | |
} | |
mpack_assert(0, "unrecognized type %i", (int)left.type); | |
return false; | |
} | |
#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; | |
} | |
#endif | |
/* The below code is from Bjoern Hoehrmann's Flexible and Economical */ | |
/* UTF-8 decoder, modified to support MPack inlining and add the mpack prefix. */ | |
/* Copyright (c) 2008-2010 Bjoern Hoehrmann <[email protected]> */ | |
/* See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. */ | |
const uint8_t mpack_utf8d[] = { | |
/* The first part of the table maps bytes to character classes that */ | |
/* to reduce the size of the transition table and create bitmasks. */ | |
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,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,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, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, | |
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, | |
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, | |
10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, | |
/* The second part is a transition table that maps a combination */ | |
/* of a state of the automaton and a character class to a state. */ | |
0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, | |
12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, | |
12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, | |
12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, | |
12,36,12,12,12,12,12,12,12,12,12,12, | |
}; | |
/* mpack-writer.c */ | |
#define MPACK_INTERNAL 1 | |
/* #include "mpack-writer.h" */ | |
#if MPACK_WRITER | |
#if MPACK_WRITE_TRACKING | |
#define MPACK_WRITER_TRACK(writer, error) mpack_writer_flag_if_error(writer, error) | |
MPACK_STATIC_INLINE_SPEED void mpack_writer_flag_if_error(mpack_writer_t* writer, mpack_error_t error) { | |
if (error != mpack_ok) | |
mpack_writer_flag_error(writer, error); | |
} | |
#else | |
#define MPACK_WRITER_TRACK(writer, error) MPACK_UNUSED(writer) | |
#endif | |
MPACK_STATIC_INLINE_SPEED void mpack_writer_track_element(mpack_writer_t* writer) { | |
MPACK_WRITER_TRACK(writer, mpack_track_element(&writer->track, true)); | |
} | |
void mpack_writer_init(mpack_writer_t* writer, char* buffer, size_t size) { | |
mpack_memset(writer, 0, sizeof(*writer)); | |
writer->buffer = buffer; | |
writer->size = size; | |
MPACK_WRITER_TRACK(writer, mpack_track_init(&writer->track)); | |
} | |
void mpack_writer_init_error(mpack_writer_t* writer, mpack_error_t error) { | |
mpack_memset(writer, 0, sizeof(*writer)); | |
writer->error = error; | |
} | |
#ifdef MPACK_MALLOC | |
typedef struct mpack_growable_writer_t { | |
char** target_data; | |
size_t* target_size; | |
} mpack_growable_writer_t; | |
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) | |
// | |
// We handle these here, making sure used is the total count in all three cases. | |
mpack_log("flush size %i used %i data %p buffer %p\n", (int)writer->size, (int)writer->used, data, writer->buffer); | |
// if the given data is not the old buffer, we'll need to actually copy it into the buffer | |
bool is_extra_data = (data != writer->buffer); | |
// if we're flushing all data (used is zero), we should actually grow | |
size_t new_size = writer->size; | |
if (writer->used == 0 && count != 0) | |
new_size *= 2; | |
while (new_size < (is_extra_data ? writer->used + count : count)) | |
new_size *= 2; | |
if (new_size > writer->size) { | |
mpack_log("flush growing from %i to %i\n", (int)writer->size, (int)new_size); | |
char* new_buffer = (char*)mpack_realloc(writer->buffer, count, new_size); | |
if (new_buffer == NULL) { | |
mpack_writer_flag_error(writer, mpack_error_memory); | |
return; | |
} | |
writer->buffer = new_buffer; | |
writer->size = new_size; | |
} | |
if (is_extra_data) { | |
mpack_memcpy(writer->buffer + writer->used, data, count); | |
// add our extra data to count | |
writer->used += count; | |
} else { | |
// used is either zero or count; set it to count | |
writer->used = count; | |
} | |
} | |
static void mpack_growable_writer_teardown(mpack_writer_t* writer) { | |
mpack_growable_writer_t* growable_writer = (mpack_growable_writer_t*)writer->context; | |
if (mpack_writer_error(writer) == mpack_ok) { | |
// shrink the buffer to an appropriate size if the data is | |
// much smaller than the buffer | |
if (writer->used < writer->size / 2) { | |
char* buffer = (char*)mpack_realloc(writer->buffer, writer->used, writer->used); | |
if (!buffer) { | |
MPACK_FREE(writer->buffer); | |
mpack_writer_flag_error(writer, mpack_error_memory); | |
return; | |
} | |
writer->buffer = buffer; | |
writer->size = writer->used; | |
} | |
*growable_writer->target_data = writer->buffer; | |
*growable_writer->target_size = writer->used; | |
writer->buffer = NULL; | |
} else if (writer->buffer) { | |
MPACK_FREE(writer->buffer); | |
writer->buffer = NULL; | |
} | |
MPACK_FREE(growable_writer); | |
writer->context = NULL; | |
} | |
void mpack_writer_init_growable(mpack_writer_t* writer, char** target_data, size_t* target_size) { | |
*target_data = NULL; | |
*target_size = 0; | |
mpack_growable_writer_t* growable_writer = (mpack_growable_writer_t*) MPACK_MALLOC(sizeof(mpack_growable_writer_t)); | |
if (growable_writer == NULL) { | |
mpack_writer_init_error(writer, mpack_error_memory); | |
return; | |
} | |
mpack_memset(growable_writer, 0, sizeof(*growable_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); | |
mpack_writer_init(writer, buffer, capacity); | |
mpack_writer_set_context(writer, growable_writer); | |
mpack_writer_set_flush(writer, mpack_growable_writer_flush); | |
mpack_writer_set_teardown(writer, mpack_growable_writer_teardown); | |
} | |
#endif | |
#if MPACK_STDIO | |
typedef struct mpack_file_writer_t { | |
FILE* file; | |
char buffer[MPACK_BUFFER_SIZE]; | |
} mpack_file_writer_t; | |
static void mpack_file_writer_flush(mpack_writer_t* writer, const char* buffer, size_t count) { | |
mpack_file_writer_t* file_writer = (mpack_file_writer_t*)writer->context; | |
size_t written = fwrite((const void*)buffer, 1, count, file_writer->file); | |
if (written != count) | |
mpack_writer_flag_error(writer, mpack_error_io); | |
} | |
static void mpack_file_writer_teardown(mpack_writer_t* writer) { | |
mpack_file_writer_t* file_writer = (mpack_file_writer_t*)writer->context; | |
if (file_writer->file) { | |
int ret = fclose(file_writer->file); | |
file_writer->file = NULL; | |
if (ret != 0) | |
mpack_writer_flag_error(writer, mpack_error_io); | |
} | |
MPACK_FREE(file_writer); | |
} | |
void mpack_writer_init_file(mpack_writer_t* writer, const char* filename) { | |
mpack_file_writer_t* file_writer = (mpack_file_writer_t*) MPACK_MALLOC(sizeof(mpack_file_writer_t)); | |
if (file_writer == NULL) { | |
mpack_writer_init_error(writer, mpack_error_memory); | |
return; | |
} | |
file_writer->file = fopen(filename, "wb"); | |
if (file_writer->file == NULL) { | |
mpack_writer_init_error(writer, mpack_error_io); | |
MPACK_FREE(file_writer); | |
return; | |
} | |
mpack_writer_init(writer, file_writer->buffer, sizeof(file_writer->buffer)); | |
mpack_writer_set_context(writer, file_writer); | |
mpack_writer_set_flush(writer, mpack_file_writer_flush); | |
mpack_writer_set_teardown(writer, mpack_file_writer_teardown); | |
} | |
#endif | |
void mpack_writer_flag_error(mpack_writer_t* writer, mpack_error_t error) { | |
mpack_log("writer %p setting error %i: %s\n", writer, (int)error, mpack_error_to_string(error)); | |
if (writer->error == mpack_ok) { | |
writer->error = error; | |
if (writer->error_fn) | |
writer->error_fn(writer, writer->error); | |
} | |
} | |
static void mpack_write_native_big(mpack_writer_t* writer, const char* p, size_t 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)(writer->size - writer->used)); | |
mpack_assert(count > writer->size - writer->used, | |
"big write requested for %i bytes, but there is %i available " | |
"space in buffer. call mpack_write_native() instead", | |
(int)count, (int)(writer->size - writer->used)); | |
// we'll need a flush function | |
if (!writer->flush) { | |
mpack_writer_flag_error(writer, mpack_error_io); | |
return; | |
} | |
// we assume that the flush function is orders of magnitude slower | |
// than memcpy(), so we fill the buffer up first to try to flush as | |
// infrequently as possible. | |
// fill the remaining space in the buffer | |
size_t n = writer->size - writer->used; | |
if (count < n) | |
n = count; | |
mpack_memcpy(writer->buffer + writer->used, p, n); | |
writer->used += n; | |
p += n; | |
count -= n; | |
if (count == 0) | |
return; | |
// flush the buffer | |
size_t used = writer->used; | |
writer->used = 0; | |
writer->flush(writer, writer->buffer, used); | |
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 > writer->size - writer->used) { | |
writer->flush(writer, p, count); | |
if (mpack_writer_error(writer) != mpack_ok) | |
return; | |
} else { | |
mpack_memcpy(writer->buffer + writer->used, p, count); | |
writer->used += count; | |
} | |
} | |
MPACK_STATIC_INLINE_SPEED void mpack_write_native(mpack_writer_t* writer, const char* p, size_t count) { | |
if (mpack_writer_error(writer) != mpack_ok) | |
return; | |
if (writer->size - writer->used < count) { | |
mpack_write_native_big(writer, p, count); | |
} else { | |
mpack_memcpy(writer->buffer + writer->used, p, count); | |
writer->used += count; | |
} | |
} | |
MPACK_ALWAYS_INLINE void mpack_store_native_u8_at(char* p, uint8_t val) { | |
uint8_t* u = (uint8_t*)p; | |
u[0] = val; | |
} | |
MPACK_ALWAYS_INLINE void mpack_store_native_u16_at(char* p, uint16_t val) { | |
uint8_t* u = (uint8_t*)p; | |
u[0] = (uint8_t)((val >> 8) & 0xFF); | |
u[1] = (uint8_t)( val & 0xFF); | |
} | |
MPACK_ALWAYS_INLINE void mpack_store_native_u32_at(char* p, uint32_t val) { | |
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); | |
} | |
MPACK_ALWAYS_INLINE void mpack_store_native_u64_at(char* p, uint64_t val) { | |
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); | |
} | |
MPACK_STATIC_INLINE_SPEED void mpack_write_native_u8(mpack_writer_t* writer, uint8_t val) { | |
if (writer->size - writer->used >= sizeof(val)) { | |
mpack_store_native_u8_at(writer->buffer + writer->used, val); | |
writer->used += sizeof(val); | |
} else { | |
char c[sizeof(val)]; | |
mpack_store_native_u8_at(c, val); | |
mpack_write_native_big(writer, c, sizeof(c)); | |
} | |
} | |
MPACK_STATIC_INLINE_SPEED void mpack_write_native_u16(mpack_writer_t* writer, uint16_t val) { | |
if (writer->size - writer->used >= sizeof(val)) { | |
mpack_store_native_u16_at(writer->buffer + writer->used, val); | |
writer->used += sizeof(val); | |
} else { | |
char c[sizeof(val)]; | |
mpack_store_native_u16_at(c, val); | |
mpack_write_native_big(writer, c, sizeof(c)); | |
} | |
} | |
MPACK_STATIC_INLINE_SPEED void mpack_write_native_u32(mpack_writer_t* writer, uint32_t val) { | |
if (writer->size - writer->used >= sizeof(val)) { | |
mpack_store_native_u32_at(writer->buffer + writer->used, val); | |
writer->used += sizeof(val); | |
} else { | |
char c[sizeof(val)]; | |
mpack_store_native_u32_at(c, val); | |
mpack_write_native_big(writer, c, sizeof(c)); | |
} | |
} | |
MPACK_STATIC_INLINE_SPEED void mpack_write_native_u64(mpack_writer_t* writer, uint64_t val) { | |
if (writer->size - writer->used >= sizeof(val)) { | |
mpack_store_native_u64_at(writer->buffer + writer->used, val); | |
writer->used += sizeof(val); | |
} else { | |
char c[sizeof(val)]; | |
mpack_store_native_u64_at(c, val); | |
mpack_write_native_big(writer, c, sizeof(c)); | |
} | |
} | |
MPACK_STATIC_INLINE void mpack_write_native_i8 (mpack_writer_t* writer, int8_t val) {mpack_write_native_u8 (writer, (uint8_t )val);} | |
MPACK_STATIC_INLINE void mpack_write_native_i16 (mpack_writer_t* writer, int16_t val) {mpack_write_native_u16 (writer, (uint16_t)val);} | |
MPACK_STATIC_INLINE void mpack_write_native_i32 (mpack_writer_t* writer, int32_t val) {mpack_write_native_u32 (writer, (uint32_t)val);} | |
MPACK_STATIC_INLINE void mpack_write_native_i64 (mpack_writer_t* writer, int64_t val) {mpack_write_native_u64 (writer, (uint64_t)val);} | |
MPACK_STATIC_INLINE_SPEED void mpack_write_native_float(mpack_writer_t* writer, float value) { | |
union { | |
float f; | |
uint32_t i; | |
} u; | |
u.f = value; | |
mpack_write_native_u32(writer, u.i); | |
} | |
MPACK_STATIC_INLINE_SPEED void mpack_write_native_double(mpack_writer_t* writer, double value) { | |
union { | |
double d; | |
uint64_t i; | |
} u; | |
u.d = value; | |
mpack_write_native_u64(writer, u.i); | |
} | |
mpack_error_t mpack_writer_destroy(mpack_writer_t* writer) { | |
MPACK_WRITER_TRACK(writer, mpack_track_destroy(&writer->track, false)); | |
// flush any outstanding data | |
if (mpack_writer_error(writer) == mpack_ok && writer->used != 0 && writer->flush != NULL) { | |
writer->flush(writer, writer->buffer, writer->used); | |
writer->flush = NULL; | |
} | |
if (writer->teardown) { | |
writer->teardown(writer); | |
writer->teardown = NULL; | |
} | |
return writer->error; | |
} | |
void mpack_writer_destroy_cancel(mpack_writer_t* writer) { | |
MPACK_WRITER_TRACK(writer, mpack_track_destroy(&writer->track, true)); | |
if (writer->teardown) | |
writer->teardown(writer); | |
writer->teardown = NULL; | |
} | |
void mpack_write_tag(mpack_writer_t* writer, mpack_tag_t value) { | |
mpack_writer_track_element(writer); | |
switch (value.type) { | |
case mpack_type_nil: mpack_write_nil (writer); break; | |
case mpack_type_bool: mpack_write_bool (writer, value.v.b); break; | |
case mpack_type_float: mpack_write_float (writer, value.v.f); break; | |
case mpack_type_double: mpack_write_double(writer, value.v.d); break; | |
case mpack_type_int: mpack_write_int (writer, value.v.i); break; | |
case mpack_type_uint: mpack_write_uint (writer, value.v.u); break; | |
case mpack_type_str: mpack_start_str(writer, value.v.l); break; | |
case mpack_type_bin: mpack_start_bin(writer, value.v.l); break; | |
case mpack_type_ext: mpack_start_ext(writer, value.exttype, value.v.l); break; | |
case mpack_type_array: mpack_start_array(writer, value.v.n); break; | |
case mpack_type_map: mpack_start_map(writer, value.v.n); break; | |
default: | |
mpack_assert(0, "unrecognized type %i", (int)value.type); | |
break; | |
} | |
} | |
void mpack_write_u8(mpack_writer_t* writer, uint8_t value) { | |
mpack_writer_track_element(writer); | |
if (value <= 0x7f) { | |
mpack_write_native_u8(writer, (uint8_t)value); | |
} else { | |
mpack_write_native_u8(writer, 0xcc); | |
mpack_write_native_u8(writer, (uint8_t)value); | |
} | |
} | |
void mpack_write_u16(mpack_writer_t* writer, uint16_t value) { | |
mpack_writer_track_element(writer); | |
if (value <= 0x7f) { | |
mpack_write_native_u8(writer, (uint8_t)value); | |
} else if (value <= UINT8_MAX) { | |
mpack_write_native_u8(writer, 0xcc); | |
mpack_write_native_u8(writer, (uint8_t)value); | |
} else { | |
mpack_write_native_u8(writer, 0xcd); | |
mpack_write_native_u16(writer, value); | |
} | |
} | |
void mpack_write_u32(mpack_writer_t* writer, uint32_t value) { | |
mpack_writer_track_element(writer); | |
if (value <= 0x7f) { | |
mpack_write_native_u8(writer, (uint8_t)value); | |
} else if (value <= UINT8_MAX) { | |
mpack_write_native_u8(writer, 0xcc); | |
mpack_write_native_u8(writer, (uint8_t)value); | |
} else if (value <= UINT16_MAX) { | |
mpack_write_native_u8(writer, 0xcd); | |
mpack_write_native_u16(writer, (uint16_t)value); | |
} else { | |
mpack_write_native_u8(writer, 0xce); | |
mpack_write_native_u32(writer, value); | |
} | |
} | |
void mpack_write_u64(mpack_writer_t* writer, uint64_t value) { | |
mpack_writer_track_element(writer); | |
if (value <= 0x7f) { | |
mpack_write_native_u8(writer, (uint8_t)value); | |
} else if (value <= UINT8_MAX) { | |
mpack_write_native_u8(writer, 0xcc); | |
mpack_write_native_u8(writer, (uint8_t)value); | |
} else if (value <= UINT16_MAX) { | |
mpack_write_native_u8(writer, 0xcd); | |
mpack_write_native_u16(writer, (uint16_t)value); | |
} else if (value <= UINT32_MAX) { | |
mpack_write_native_u8(writer, 0xce); | |
mpack_write_native_u32(writer, (uint32_t)value); | |
} else { | |
mpack_write_native_u8(writer, 0xcf); | |
mpack_write_native_u64(writer, value); | |
} | |
} | |
void mpack_write_i8(mpack_writer_t* writer, int8_t value) { | |
// write any non-negative number as a uint | |
if (value >= 0) { | |
mpack_write_u8(writer, (uint8_t)value); | |
return; | |
} | |
mpack_writer_track_element(writer); | |
if (value >= -32) { | |
mpack_write_native_i8(writer, (int8_t)0xe0 | (int8_t)value); // TODO: remove this (compatibility/1.1 difference?) | |
} else { | |
mpack_write_native_u8(writer, 0xd0); | |
mpack_write_native_i8(writer, value); | |
} | |
} | |
void mpack_write_i16(mpack_writer_t* writer, int16_t value) { | |
// write any non-negative number as a uint | |
if (value >= 0) { | |
mpack_write_u16(writer, (uint16_t)value); | |
return; | |
} | |
mpack_writer_track_element(writer); | |
if (value >= -32) { | |
mpack_write_native_i8(writer, (int8_t)0xe0 | (int8_t)value); // TODO: remove this (compatibility/1.1 difference?) | |
} else if (value >= INT8_MIN) { | |
mpack_write_native_u8(writer, 0xd0); | |
mpack_write_native_i8(writer, (int8_t)value); | |
} else { | |
mpack_write_native_u8(writer, 0xd1); | |
mpack_write_native_i16(writer, value); | |
} | |
} | |
void mpack_write_i32(mpack_writer_t* writer, int32_t value) { | |
// write any non-negative number as a uint | |
if (value >= 0) { | |
mpack_write_u32(writer, (uint32_t)value); | |
return; | |
} | |
mpack_writer_track_element(writer); | |
if (value >= -32) { | |
mpack_write_native_i8(writer, (int8_t)0xe0 | (int8_t)value); // TODO: remove this (compatibility/1.1 difference?) | |
} else if (value >= INT8_MIN) { | |
mpack_write_native_u8(writer, 0xd0); | |
mpack_write_native_i8(writer, (int8_t)value); | |
} else if (value >= INT16_MIN) { | |
mpack_write_native_u8(writer, 0xd1); | |
mpack_write_native_i16(writer, (int16_t)value); | |
} else { | |
mpack_write_native_u8(writer, 0xd2); | |
mpack_write_native_i32(writer, value); | |
} | |
} | |
void mpack_write_i64(mpack_writer_t* writer, int64_t value) { | |
// write any non-negative number as a uint | |
if (value >= 0) { | |
mpack_write_u64(writer, (uint64_t)value); | |
return; | |
} | |
mpack_writer_track_element(writer); | |
if (value >= -32) { | |
mpack_write_native_i8(writer, (int8_t)0xe0 | (int8_t)value); // TODO: remove this (compatibility/1.1 difference?) | |
} else if (value >= INT8_MIN) { | |
mpack_write_native_u8(writer, 0xd0); | |
mpack_write_native_i8(writer, (int8_t)value); | |
} else if (value >= INT16_MIN) { | |
mpack_write_native_u8(writer, 0xd1); | |
mpack_write_native_i16(writer, (int16_t)value); | |
} else if (value >= INT32_MIN) { | |
mpack_write_native_u8(writer, 0xd2); | |
mpack_write_native_i32(writer, (int32_t)value); | |
} else { | |
mpack_write_native_u8(writer, 0xd3); | |
mpack_write_native_i64(writer, value); | |
} | |
} | |
void mpack_write_bool(mpack_writer_t* writer, bool value) { | |
mpack_writer_track_element(writer); | |
mpack_write_native_u8(writer, (uint8_t)(0xc2 | (value ? 1 : 0))); | |
} | |
void mpack_write_true(mpack_writer_t* writer) { | |
mpack_writer_track_element(writer); | |
mpack_write_native_u8(writer, (uint8_t)0xc3); | |
} | |
void mpack_write_false(mpack_writer_t* writer) { | |
mpack_writer_track_element(writer); | |
mpack_write_native_u8(writer, (uint8_t)0xc2); | |
} | |
void mpack_write_nil(mpack_writer_t* writer) { | |
mpack_writer_track_element(writer); | |
mpack_write_native_u8(writer, 0xc0); | |
} | |
void mpack_write_float(mpack_writer_t* writer, float value) { | |
mpack_writer_track_element(writer); | |
mpack_write_native_u8(writer, 0xca); | |
mpack_write_native_float(writer, value); | |
} | |
void mpack_write_double(mpack_writer_t* writer, double value) { | |
mpack_writer_track_element(writer); | |
mpack_write_native_u8(writer, 0xcb); | |
mpack_write_native_double(writer, value); | |
} | |
#if MPACK_WRITE_TRACKING | |
void mpack_finish_array(mpack_writer_t* writer) { | |
MPACK_WRITER_TRACK(writer, mpack_track_pop(&writer->track, mpack_type_array)); | |
} | |
void mpack_finish_map(mpack_writer_t* writer) { | |
MPACK_WRITER_TRACK(writer, mpack_track_pop(&writer->track, mpack_type_map)); | |
} | |
void mpack_finish_str(mpack_writer_t* writer) { | |
MPACK_WRITER_TRACK(writer, mpack_track_pop(&writer->track, mpack_type_str)); | |
} | |
void mpack_finish_bin(mpack_writer_t* writer) { | |
MPACK_WRITER_TRACK(writer, mpack_track_pop(&writer->track, mpack_type_bin)); | |
} | |
void mpack_finish_ext(mpack_writer_t* writer) { | |
MPACK_WRITER_TRACK(writer, mpack_track_pop(&writer->track, mpack_type_ext)); | |
} | |
void mpack_finish_type(mpack_writer_t* writer, mpack_type_t type) { | |
MPACK_WRITER_TRACK(writer, mpack_track_pop(&writer->track, type)); | |
} | |
#endif | |
void mpack_start_array(mpack_writer_t* writer, uint32_t count) { | |
if (mpack_writer_error(writer) != mpack_ok) | |
return; | |
mpack_writer_track_element(writer); | |
if (count <= 15) { | |
mpack_write_native_u8(writer, (uint8_t)(0x90 | count)); | |
} else if (count <= UINT16_MAX) { | |
mpack_write_native_u8(writer, 0xdc); | |
mpack_write_native_u16(writer, (uint16_t)count); | |
} else { | |
mpack_write_native_u8(writer, 0xdd); | |
mpack_write_native_u32(writer, count); | |
} | |
MPACK_WRITER_TRACK(writer, mpack_track_push(&writer->track, mpack_type_array, count)); | |
} | |
void mpack_start_map(mpack_writer_t* writer, uint32_t count) { | |
if (mpack_writer_error(writer) != mpack_ok) | |
return; | |
mpack_writer_track_element(writer); | |
if (count <= 15) { | |
mpack_write_native_u8(writer, (uint8_t)(0x80 | count)); | |
} else if (count <= UINT16_MAX) { | |
mpack_write_native_u8(writer, 0xde); | |
mpack_write_native_u16(writer, (uint16_t)count); | |
} else { | |
mpack_write_native_u8(writer, 0xdf); | |
mpack_write_native_u32(writer, count); | |
} | |
MPACK_WRITER_TRACK(writer, mpack_track_push(&writer->track, mpack_type_map, count)); | |
} | |
void mpack_start_str(mpack_writer_t* writer, uint32_t count) { | |
if (mpack_writer_error(writer) != mpack_ok) | |
return; | |
mpack_writer_track_element(writer); | |
if (count <= 31) { | |
mpack_write_native_u8(writer, (uint8_t)(0xa0 | count)); | |
} else if (count <= UINT8_MAX) { | |
// TODO: THIS NOT AVAILABLE IN COMPATIBILITY MODE?? was not in 1.0? | |
mpack_write_native_u8(writer, 0xd9); | |
mpack_write_native_u8(writer, (uint8_t)count); | |
} else if (count <= UINT16_MAX) { | |
mpack_write_native_u8(writer, 0xda); | |
mpack_write_native_u16(writer, (uint16_t)count); | |
} else { | |
mpack_write_native_u8(writer, 0xdb); | |
mpack_write_native_u32(writer, count); | |
} | |
MPACK_WRITER_TRACK(writer, mpack_track_push(&writer->track, mpack_type_str, count)); | |
} | |
void mpack_start_bin(mpack_writer_t* writer, uint32_t count) { | |
if (mpack_writer_error(writer) != mpack_ok) | |
return; | |
mpack_writer_track_element(writer); | |
if (count <= UINT8_MAX) { | |
mpack_write_native_u8(writer, 0xc4); | |
mpack_write_native_u8(writer, (uint8_t)count); | |
} else if (count <= UINT16_MAX) { | |
mpack_write_native_u8(writer, 0xc5); | |
mpack_write_native_u16(writer, (uint16_t)count); | |
} else { | |
mpack_write_native_u8(writer, 0xc6); | |
mpack_write_native_u32(writer, count); | |
} | |
MPACK_WRITER_TRACK(writer, mpack_track_push(&writer->track, mpack_type_bin, count)); | |
} | |
void mpack_start_ext(mpack_writer_t* writer, int8_t exttype, uint32_t count) { | |
if (mpack_writer_error(writer) != mpack_ok) | |
return; | |
// TODO: fail if compatibility mode | |
mpack_writer_track_element(writer); | |
if (count == 1) { | |
mpack_write_native_u8(writer, 0xd4); | |
mpack_write_native_i8(writer, exttype); | |
} else if (count == 2) { | |
mpack_write_native_u8(writer, 0xd5); | |
mpack_write_native_i8(writer, exttype); | |
} else if (count == 4) { | |
mpack_write_native_u8(writer, 0xd6); | |
mpack_write_native_i8(writer, exttype); | |
} else if (count == 8) { | |
mpack_write_native_u8(writer, 0xd7); | |
mpack_write_native_i8(writer, exttype); | |
} else if (count == 16) { | |
mpack_write_native_u8(writer, 0xd8); | |
mpack_write_native_i8(writer, exttype); | |
} else if (count <= UINT8_MAX) { | |
mpack_write_native_u8(writer, 0xc7); | |
mpack_write_native_u8(writer, (uint8_t)count); | |
mpack_write_native_i8(writer, exttype); | |
} else if (count <= UINT16_MAX) { | |
mpack_write_native_u8(writer, 0xc8); | |
mpack_write_native_u16(writer, (uint16_t)count); | |
mpack_write_native_i8(writer, exttype); | |
} else { | |
mpack_write_native_u8(writer, 0xc9); | |
mpack_write_native_u32(writer, count); | |
mpack_write_native_i8(writer, exttype); | |
} | |
MPACK_WRITER_TRACK(writer, mpack_track_push(&writer->track, mpack_type_ext, count)); | |
} | |
void mpack_write_str(mpack_writer_t* writer, const char* data, uint32_t count) { | |
mpack_start_str(writer, count); | |
mpack_write_bytes(writer, data, count); | |
mpack_finish_str(writer); | |
} | |
void mpack_write_bin(mpack_writer_t* writer, const char* data, uint32_t count) { | |
mpack_start_bin(writer, count); | |
mpack_write_bytes(writer, data, count); | |
mpack_finish_bin(writer); | |
} | |
void mpack_write_ext(mpack_writer_t* writer, int8_t exttype, const char* data, uint32_t count) { | |
mpack_start_ext(writer, exttype, count); | |
mpack_write_bytes(writer, data, count); | |
mpack_finish_ext(writer); | |
} | |
void mpack_write_bytes(mpack_writer_t* writer, const char* data, size_t count) { | |
MPACK_WRITER_TRACK(writer, mpack_track_bytes(&writer->track, false, count)); | |
mpack_write_native(writer, data, count); | |
} | |
void mpack_write_cstr(mpack_writer_t* writer, const char* str) { | |
size_t len = mpack_strlen(str); | |
if (len > UINT32_MAX) | |
mpack_writer_flag_error(writer, mpack_error_invalid); | |
mpack_write_str(writer, str, (uint32_t)len); | |
} | |
#endif | |
/* mpack-reader.c */ | |
#define MPACK_INTERNAL 1 | |
/* #include "mpack-reader.h" */ | |
#if MPACK_READER | |
void mpack_reader_init(mpack_reader_t* reader, char* buffer, size_t size, size_t count) { | |
mpack_memset(reader, 0, sizeof(*reader)); | |
reader->buffer = buffer; | |
reader->size = size; | |
reader->left = count; | |
MPACK_UNUSED(MPACK_READER_TRACK(reader, mpack_track_init(&reader->track))); | |
} | |
void mpack_reader_init_error(mpack_reader_t* reader, mpack_error_t error) { | |
mpack_memset(reader, 0, sizeof(*reader)); | |
reader->error = error; | |
} | |
void mpack_reader_init_data(mpack_reader_t* reader, const char* data, size_t count) { | |
mpack_memset(reader, 0, sizeof(*reader)); | |
reader->left = count; | |
// unfortunately we have to cast away the const to store the buffer, | |
// but we won't be modifying it because there's no fill function. | |
// the buffer size is left at 0 to ensure no fill function can be | |
// set or used (see mpack_reader_set_fill().) | |
#ifdef __cplusplus | |
reader->buffer = const_cast<char*>(data); | |
#else | |
reader->buffer = (char*)data; | |
#endif | |
MPACK_UNUSED(MPACK_READER_TRACK(reader, mpack_track_init(&reader->track))); | |
} | |
#if MPACK_STDIO | |
typedef struct mpack_file_reader_t { | |
FILE* file; | |
char buffer[MPACK_BUFFER_SIZE]; | |
} mpack_file_reader_t; | |
static size_t mpack_file_reader_fill(mpack_reader_t* reader, char* buffer, size_t count) { | |
mpack_file_reader_t* file_reader = (mpack_file_reader_t*)reader->context; | |
return fread((void*)buffer, 1, count, file_reader->file); | |
} | |
static void mpack_file_reader_teardown(mpack_reader_t* reader) { | |
mpack_file_reader_t* file_reader = (mpack_file_reader_t*)reader->context; | |
if (file_reader->file) { | |
int ret = fclose(file_reader->file); | |
file_reader->file = NULL; | |
if (ret != 0) | |
mpack_reader_flag_error(reader, mpack_error_io); | |
} | |
MPACK_FREE(file_reader); | |
} | |
void mpack_reader_init_file(mpack_reader_t* reader, const char* filename) { | |
mpack_file_reader_t* file_reader = (mpack_file_reader_t*) MPACK_MALLOC(sizeof(mpack_file_reader_t)); | |
if (file_reader == NULL) { | |
mpack_reader_init_error(reader, mpack_error_memory); | |
return; | |
} | |
file_reader->file = fopen(filename, "rb"); | |
if (file_reader->file == NULL) { | |
mpack_reader_init_error(reader, mpack_error_io); | |
MPACK_FREE(file_reader); | |
return; | |
} | |
mpack_reader_init(reader, file_reader->buffer, sizeof(file_reader->buffer), 0); | |
mpack_reader_set_context(reader, file_reader); | |
mpack_reader_set_fill(reader, mpack_file_reader_fill); | |
mpack_reader_set_teardown(reader, mpack_file_reader_teardown); | |
} | |
#endif | |
mpack_error_t mpack_reader_destroy_impl(mpack_reader_t* reader, bool cancel) { | |
MPACK_UNUSED(cancel); | |
MPACK_UNUSED(MPACK_READER_TRACK(reader, mpack_track_destroy(&reader->track, cancel))); | |
if (reader->teardown) | |
reader->teardown(reader); | |
reader->teardown = NULL; | |
return reader->error; | |
} | |
void mpack_reader_destroy_cancel(mpack_reader_t* reader) { | |
mpack_reader_destroy_impl(reader, true); | |
} | |
mpack_error_t mpack_reader_destroy(mpack_reader_t* reader) { | |
return mpack_reader_destroy_impl(reader, false); | |
} | |
size_t mpack_reader_remaining(mpack_reader_t* reader, const char** data) { | |
MPACK_UNUSED(MPACK_READER_TRACK(reader, mpack_track_check_empty(&reader->track))); | |
if (data) | |
*data = reader->buffer + reader->pos; | |
return reader->left; | |
} | |
void mpack_reader_flag_error(mpack_reader_t* reader, mpack_error_t error) { | |
mpack_log("reader %p setting error %i: %s\n", reader, (int)error, mpack_error_to_string(error)); | |
if (reader->error == mpack_ok) { | |
reader->error = error; | |
if (reader->error_fn) | |
reader->error_fn(reader, error); | |
} | |
} | |
// A helper to call the reader fill function. This makes sure it's | |
// implemented and guards against overflow in case it returns -1. | |
MPACK_STATIC_INLINE_SPEED size_t mpack_fill(mpack_reader_t* reader, char* p, size_t count) { | |
if (!reader->fill) | |
return 0; | |
size_t ret = reader->fill(reader, p, count); | |
if (ret == ((size_t)(-1))) | |
return 0; | |
return ret; | |
} | |
// Reads count bytes into p. Used when there are not enough bytes | |
// left in the buffer to satisfy a read. | |
void mpack_read_native_big(mpack_reader_t* reader, char* p, size_t count) { | |
if (mpack_reader_error(reader) != mpack_ok) { | |
mpack_memset(p, 0, count); | |
return; | |
} | |
mpack_log("big read for %i bytes into %p, %i left in buffer, buffer size %i\n", | |
(int)count, p, (int)reader->left, (int)reader->size); | |
if (count <= reader->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)reader->left); | |
mpack_reader_flag_error(reader, mpack_error_bug); | |
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 (reader->left > 0) { | |
mpack_log("flushing %i bytes remaining in buffer\n", (int)reader->left); | |
mpack_memcpy(p, reader->buffer + reader->pos, reader->left); | |
count -= reader->left; | |
p += reader->left; | |
reader->pos += reader->left; | |
reader->left = 0; | |
} | |
// we read only in multiples of the buffer size. read the middle portion, if any | |
size_t middle = count - (count % reader->size); | |
if (middle > 0) { | |
mpack_log("reading %i bytes in middle\n", (int)middle); | |
if (mpack_fill(reader, p, middle) < middle) { | |
mpack_reader_flag_error(reader, mpack_error_io); | |
mpack_memset(p, 0, count); | |
return; | |
} | |
count -= middle; | |
p += middle; | |
if (count == 0) | |
return; | |
} | |
// fill the buffer | |
reader->pos = 0; | |
reader->left = mpack_fill(reader, reader->buffer, reader->size); | |
mpack_log("filled %i bytes into buffer\n", (int)reader->left); | |
if (reader->left < count) { | |
mpack_reader_flag_error(reader, mpack_error_io); | |
mpack_memset(p, 0, count); | |
return; | |
} | |
// serve the remainder | |
mpack_log("serving %i remaining bytes from %p to %p\n", (int)count, reader->buffer+reader->pos,p); | |
mpack_memcpy(p, reader->buffer + reader->pos, count); | |
reader->pos += count; | |
reader->left -= count; | |
} | |
void mpack_skip_bytes(mpack_reader_t* reader, size_t count) { | |
// TODO: This is currently very slow, potentially even slower than just | |
// reading the data. Skip needs to be implemented properly. | |
char c[128]; | |
size_t i = 0; | |
while (i < count && mpack_reader_error(reader) == mpack_ok) { | |
size_t amount = ((count - i) > sizeof(c)) ? sizeof(c) : (count - i); | |
mpack_read_bytes(reader, c, amount); | |
i += amount; | |
} | |
} | |
void mpack_read_bytes(mpack_reader_t* reader, char* p, size_t count) { | |
mpack_reader_track_bytes(reader, count); | |
mpack_read_native(reader, p, count); | |
} | |
// internal inplace reader for when it straddles the end of the buffer | |
// this is split out to inline the common case, although this isn't done | |
// right now because we can't inline tracking yet | |
static const char* mpack_read_bytes_inplace_big(mpack_reader_t* reader, size_t count) { | |
// we should only arrive here from inplace straddle; this should already be checked | |
mpack_assert(mpack_reader_error(reader) == mpack_ok, "already in error state? %s", | |
mpack_error_to_string(mpack_reader_error(reader))); | |
mpack_assert(reader->left < count, "already enough bytes in buffer: %i left, %i count", (int)reader->left, (int)count); | |
// we'll need a fill function to get more data | |
if (!reader->fill) { | |
mpack_reader_flag_error(reader, mpack_error_io); | |
return NULL; | |
} | |
// make sure the buffer is big enough to actually fit the data | |
if (count > reader->size) { | |
mpack_reader_flag_error(reader, mpack_error_too_big); | |
return NULL; | |
} | |
// shift the remaining data back to the start and fill the buffer back up | |
mpack_memmove(reader->buffer, reader->buffer + reader->pos, reader->left); | |
reader->pos = 0; | |
reader->left += mpack_fill(reader, reader->buffer + reader->left, reader->size - reader->left); | |
if (reader->left < count) { | |
mpack_reader_flag_error(reader, mpack_error_io); | |
return NULL; | |
} | |
reader->pos += count; | |
reader->left -= count; | |
return reader->buffer; | |
} | |
const char* mpack_read_bytes_inplace(mpack_reader_t* reader, size_t count) { | |
if (mpack_reader_error(reader) != mpack_ok) | |
return NULL; | |
mpack_reader_track_bytes(reader, count); | |
// if we have enough bytes already in the buffer, we can return it directly. | |
if (reader->left >= count) { | |
reader->pos += count; | |
reader->left -= count; | |
return reader->buffer + reader->pos - count; | |
} | |
return mpack_read_bytes_inplace_big(reader, count); | |
} | |
mpack_tag_t mpack_read_tag(mpack_reader_t* reader) { | |
mpack_tag_t var = mpack_tag_nil(); | |
// get the type | |
uint8_t type = mpack_read_native_u8(reader); | |
if (mpack_reader_error(reader)) | |
return mpack_tag_nil(); | |
if (mpack_reader_track_element(reader) != mpack_ok) | |
return mpack_tag_nil(); | |
// 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. the compiler optimizes | |
// this nicely (and it takes very little space.) | |
switch (type) { | |
// 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: | |
var.type = mpack_type_uint; | |
var.v.u = type; | |
return var; | |
// 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: | |
var.type = mpack_type_int; | |
var.v.i = (int8_t)type; | |
return var; | |
// 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: | |
var.type = mpack_type_map; | |
var.v.n = type & ~0xf0; | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_map, var.v.n)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// 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: | |
var.type = mpack_type_array; | |
var.v.n = type & ~0xf0; | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_array, var.v.n)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// 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: | |
var.type = mpack_type_str; | |
var.v.l = type & ~0xe0; | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_str, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// nil | |
case 0xc0: | |
return mpack_tag_nil(); | |
// bool | |
case 0xc2: case 0xc3: | |
var.type = mpack_type_bool; | |
var.v.b = type & 1; | |
return var; | |
// bin8 | |
case 0xc4: | |
var.type = mpack_type_bin; | |
var.v.l = mpack_read_native_u8(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_bin, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// bin16 | |
case 0xc5: | |
var.type = mpack_type_bin; | |
var.v.l = mpack_read_native_u16(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_bin, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// bin32 | |
case 0xc6: | |
var.type = mpack_type_bin; | |
var.v.l = mpack_read_native_u32(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_bin, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// ext8 | |
case 0xc7: | |
var.type = mpack_type_ext; | |
var.v.l = mpack_read_native_u8(reader); | |
var.exttype = mpack_read_native_i8(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_ext, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// ext16 | |
case 0xc8: | |
var.type = mpack_type_ext; | |
var.v.l = mpack_read_native_u16(reader); | |
var.exttype = mpack_read_native_i8(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_ext, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// ext32 | |
case 0xc9: | |
var.type = mpack_type_ext; | |
var.v.l = mpack_read_native_u32(reader); | |
var.exttype = mpack_read_native_i8(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_ext, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// float | |
case 0xca: | |
var.type = mpack_type_float; | |
var.v.f = mpack_read_native_float(reader); | |
return var; | |
// double | |
case 0xcb: | |
var.type = mpack_type_double; | |
var.v.d = mpack_read_native_double(reader); | |
return var; | |
// uint8 | |
case 0xcc: | |
var.type = mpack_type_uint; | |
var.v.u = mpack_read_native_u8(reader); | |
return var; | |
// uint16 | |
case 0xcd: | |
var.type = mpack_type_uint; | |
var.v.u = mpack_read_native_u16(reader); | |
return var; | |
// uint32 | |
case 0xce: | |
var.type = mpack_type_uint; | |
var.v.u = mpack_read_native_u32(reader); | |
return var; | |
// uint64 | |
case 0xcf: | |
var.type = mpack_type_uint; | |
var.v.u = mpack_read_native_u64(reader); | |
return var; | |
// int8 | |
case 0xd0: | |
var.type = mpack_type_int; | |
var.v.i = mpack_read_native_i8(reader); | |
return var; | |
// int16 | |
case 0xd1: | |
var.type = mpack_type_int; | |
var.v.i = mpack_read_native_i16(reader); | |
return var; | |
// int32 | |
case 0xd2: | |
var.type = mpack_type_int; | |
var.v.i = mpack_read_native_i32(reader); | |
return var; | |
// int64 | |
case 0xd3: | |
var.type = mpack_type_int; | |
var.v.i = mpack_read_native_i64(reader); | |
return var; | |
// fixext1 | |
case 0xd4: | |
var.type = mpack_type_ext; | |
var.v.l = 1; | |
var.exttype = mpack_read_native_i8(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_ext, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// fixext2 | |
case 0xd5: | |
var.type = mpack_type_ext; | |
var.v.l = 2; | |
var.exttype = mpack_read_native_i8(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_ext, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// fixext4 | |
case 0xd6: | |
var.type = mpack_type_ext; | |
var.v.l = 4; | |
var.exttype = mpack_read_native_i8(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_ext, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// fixext8 | |
case 0xd7: | |
var.type = mpack_type_ext; | |
var.v.l = 8; | |
var.exttype = mpack_read_native_i8(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_ext, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// fixext16 | |
case 0xd8: | |
var.type = mpack_type_ext; | |
var.v.l = 16; | |
var.exttype = mpack_read_native_i8(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_ext, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// str8 | |
case 0xd9: | |
var.type = mpack_type_str; | |
var.v.l = mpack_read_native_u8(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_str, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// str16 | |
case 0xda: | |
var.type = mpack_type_str; | |
var.v.l = mpack_read_native_u16(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_str, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// str32 | |
case 0xdb: | |
var.type = mpack_type_str; | |
var.v.l = mpack_read_native_u32(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_str, var.v.l)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// array16 | |
case 0xdc: | |
var.type = mpack_type_array; | |
var.v.n = mpack_read_native_u16(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_array, var.v.n)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// array32 | |
case 0xdd: | |
var.type = mpack_type_array; | |
var.v.n = mpack_read_native_u32(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_array, var.v.n)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// map16 | |
case 0xde: | |
var.type = mpack_type_map; | |
var.v.n = mpack_read_native_u16(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_map, var.v.n)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// map32 | |
case 0xdf: | |
var.type = mpack_type_map; | |
var.v.n = mpack_read_native_u32(reader); | |
if (MPACK_READER_TRACK(reader, mpack_track_push(&reader->track, mpack_type_map, var.v.n)) != mpack_ok) | |
return mpack_tag_nil(); | |
return var; | |
// reserved | |
case 0xc1: | |
break; | |
} | |
// unrecognized type | |
mpack_reader_flag_error(reader, mpack_error_invalid); | |
return mpack_tag_nil(); | |
} | |
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; | |
case mpack_type_ext: | |
mpack_skip_bytes(reader, var.v.l); | |
mpack_done_ext(reader); | |
break; | |
case mpack_type_array: { | |
for (; var.v.n > 0; --var.v.n) { | |
mpack_discard(reader); | |
if (mpack_reader_error(reader)) | |
break; | |
} | |
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; | |
} | |
break; | |
} | |
default: | |
break; | |
} | |
} | |
#if MPACK_READ_TRACKING | |
void mpack_done_array(mpack_reader_t* reader) { | |
MPACK_UNUSED(MPACK_READER_TRACK(reader, mpack_track_pop(&reader->track, mpack_type_array))); | |
} | |
void mpack_done_map(mpack_reader_t* reader) { | |
MPACK_UNUSED(MPACK_READER_TRACK(reader, mpack_track_pop(&reader->track, mpack_type_map))); | |
} | |
void mpack_done_str(mpack_reader_t* reader) { | |
MPACK_UNUSED(MPACK_READER_TRACK(reader, mpack_track_pop(&reader->track, mpack_type_str))); | |
} | |
void mpack_done_bin(mpack_reader_t* reader) { | |
MPACK_UNUSED(MPACK_READER_TRACK(reader, mpack_track_pop(&reader->track, mpack_type_bin))); | |
} | |
void mpack_done_ext(mpack_reader_t* reader) { | |
MPACK_UNUSED(MPACK_READER_TRACK(reader, mpack_track_pop(&reader->track, mpack_type_ext))); | |
} | |
void mpack_done_type(mpack_reader_t* reader, mpack_type_t type) { | |
MPACK_UNUSED(MPACK_READER_TRACK(reader, mpack_track_pop(&reader->track, type))); | |
} | |
#endif | |
#if MPACK_DEBUG && MPACK_STDIO && !MPACK_NO_PRINT | |
static void mpack_debug_print_element(mpack_reader_t* reader, size_t depth) { | |
mpack_tag_t val = mpack_read_tag(reader); | |
if (mpack_reader_error(reader) != mpack_ok) | |
return; | |
switch (val.type) { | |
case mpack_type_nil: | |
printf("null"); | |
break; | |
case mpack_type_bool: | |
printf(val.v.b ? "true" : "false"); | |
break; | |
case mpack_type_float: | |
printf("%f", val.v.f); | |
break; | |
case mpack_type_double: | |
printf("%f", val.v.d); | |
break; | |
case mpack_type_int: | |
printf("%" PRIi64, val.v.i); | |
break; | |
case mpack_type_uint: | |
printf("%" PRIu64, val.v.u); | |
break; | |
case mpack_type_bin: | |
// skip data | |
for (size_t i = 0; i < val.v.l; ++i) | |
mpack_read_native_u8(reader); | |
if (mpack_reader_error(reader) != mpack_ok) | |
return; | |
printf("<binary data>"); | |
mpack_done_bin(reader); | |
break; | |
case mpack_type_ext: | |
// skip data | |
for (size_t i = 0; i < val.v.l; ++i) | |
mpack_read_native_u8(reader); | |
if (mpack_reader_error(reader) != mpack_ok) | |
return; | |
printf("<ext data of type %i>", val.exttype); | |
mpack_done_ext(reader); | |
break; | |
case mpack_type_str: | |
putchar('"'); | |
for (size_t i = 0; i < val.v.l; ++i) { | |
char c; | |
mpack_read_bytes(reader, &c, 1); | |
if (mpack_reader_error(reader) != mpack_ok) | |
return; | |
switch (c) { | |
case '\n': printf("\\n"); break; | |
case '\\': printf("\\\\"); break; | |
case '"': printf("\\\""); break; | |
default: putchar(c); break; | |
} | |
} | |
putchar('"'); | |
mpack_done_str(reader); | |
break; | |
case mpack_type_array: | |
printf("[\n"); | |
for (size_t i = 0; i < val.v.n; ++i) { | |
if (mpack_reader_error(reader) != mpack_ok) | |
return; | |
for (size_t j = 0; j < depth + 1; ++j) | |
printf(" "); | |
mpack_debug_print_element(reader, depth + 1); | |
if (mpack_reader_error(reader) != mpack_ok) | |
return; | |
if (i != val.v.n - 1) | |
putchar(','); | |
putchar('\n'); | |
} | |
for (size_t i = 0; i < depth; ++i) | |
printf(" "); | |
putchar(']'); | |
mpack_done_array(reader); | |
break; | |
case mpack_type_map: | |
printf("{\n"); | |
for (size_t i = 0; i < val.v.n; ++i) { | |
for (size_t j = 0; j < depth + 1; ++j) | |
printf(" "); | |
mpack_debug_print_element(reader, depth + 1); | |
if (mpack_reader_error(reader) != mpack_ok) | |
return; | |
printf(": "); | |
mpack_debug_print_element(reader, depth + 1); | |
if (mpack_reader_error(reader) != mpack_ok) | |
return; | |
if (i != val.v.n - 1) | |
putchar(','); | |
putchar('\n'); | |
} | |
for (size_t i = 0; i < depth; ++i) | |
printf(" "); | |
putchar('}'); | |
mpack_done_map(reader); | |
break; | |
} | |
} | |
void mpack_debug_print(const char* data, int len) { | |
mpack_reader_t reader; | |
mpack_reader_init_data(&reader, data, len); | |
int depth = 2; | |
for (int i = 0; i < depth; ++i) | |
printf(" "); | |
mpack_debug_print_element(&reader, depth); | |
putchar('\n'); | |
if (mpack_reader_error(&reader) != mpack_ok) | |
printf("<mpack parsing error %s>\n", mpack_error_to_string(mpack_reader_error(&reader))); | |
else if (mpack_reader_remaining(&reader, NULL) > 0) | |
printf("<%i extra bytes at end of mpack>\n", (int)mpack_reader_remaining(&reader, NULL)); | |
} | |
#endif | |
#endif | |
/* mpack-expect.c */ | |
#define MPACK_INTERNAL 1 | |
/* #include "mpack-expect.h" */ | |
#if MPACK_EXPECT | |
// Basic Number Functions | |
uint8_t mpack_expect_u8(mpack_reader_t* reader) { | |
mpack_tag_t var = mpack_read_tag(reader); | |
if (var.type == mpack_type_uint) { | |
if (var.v.u <= UINT8_MAX) | |
return (uint8_t)var.v.u; | |
} else if (var.type == mpack_type_int) { | |
if (var.v.i >= 0 && var.v.i <= UINT8_MAX) | |
return (uint8_t)var.v.i; | |
} | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return 0; | |
} | |
uint16_t mpack_expect_u16(mpack_reader_t* reader) { | |
mpack_tag_t var = mpack_read_tag(reader); | |
if (var.type == mpack_type_uint) { | |
if (var.v.u <= UINT16_MAX) | |
return (uint16_t)var.v.u; | |
} else if (var.type == mpack_type_int) { | |
if (var.v.i >= 0 && var.v.i <= UINT16_MAX) | |
return (uint16_t)var.v.i; | |
} | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return 0; | |
} | |
uint32_t mpack_expect_u32(mpack_reader_t* reader) { | |
mpack_tag_t var = mpack_read_tag(reader); | |
if (var.type == mpack_type_uint) { | |
if (var.v.u <= UINT32_MAX) | |
return (uint32_t)var.v.u; | |
} else if (var.type == mpack_type_int) { | |
if (var.v.i >= 0 && var.v.i <= UINT32_MAX) | |
return (uint32_t)var.v.i; | |
} | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return 0; | |
} | |
uint64_t mpack_expect_u64(mpack_reader_t* reader) { | |
mpack_tag_t var = mpack_read_tag(reader); | |
if (var.type == mpack_type_uint) { | |
return var.v.u; | |
} else if (var.type == mpack_type_int) { | |
if (var.v.i >= 0) | |
return (uint64_t)var.v.i; | |
} | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return 0; | |
} | |
int8_t mpack_expect_i8(mpack_reader_t* reader) { | |
mpack_tag_t var = mpack_read_tag(reader); | |
if (var.type == mpack_type_uint) { | |
if (var.v.u <= INT8_MAX) | |
return (int8_t)var.v.u; | |
} else if (var.type == mpack_type_int) { | |
if (var.v.i >= INT8_MIN && var.v.i <= INT8_MAX) | |
return (int8_t)var.v.i; | |
} | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return 0; | |
} | |
int16_t mpack_expect_i16(mpack_reader_t* reader) { | |
mpack_tag_t var = mpack_read_tag(reader); | |
if (var.type == mpack_type_uint) { | |
if (var.v.u <= INT16_MAX) | |
return (int16_t)var.v.u; | |
} else if (var.type == mpack_type_int) { | |
if (var.v.i >= INT16_MIN && var.v.i <= INT16_MAX) | |
return (int16_t)var.v.i; | |
} | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return 0; | |
} | |
int32_t mpack_expect_i32(mpack_reader_t* reader) { | |
mpack_tag_t var = mpack_read_tag(reader); | |
if (var.type == mpack_type_uint) { | |
if (var.v.u <= INT32_MAX) | |
return (int32_t)var.v.u; | |
} else if (var.type == mpack_type_int) { | |
if (var.v.i >= INT32_MIN && var.v.i <= INT32_MAX) | |
return (int32_t)var.v.i; | |
} | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return 0; | |
} | |
int64_t mpack_expect_i64(mpack_reader_t* reader) { | |
mpack_tag_t var = mpack_read_tag(reader); | |
if (var.type == mpack_type_uint) { | |
if (var.v.u <= INT64_MAX) | |
return (int64_t)var.v.u; | |
} else if (var.type == mpack_type_int) { | |
return var.v.i; | |
} | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return 0; | |
} | |
float mpack_expect_float(mpack_reader_t* reader) { | |
mpack_tag_t var = mpack_read_tag(reader); | |
if (var.type == mpack_type_uint) | |
return (float)var.v.u; | |
else if (var.type == mpack_type_int) | |
return (float)var.v.i; | |
else if (var.type == mpack_type_float) | |
return var.v.f; | |
else if (var.type == mpack_type_double) | |
return (float)var.v.d; | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return 0.0f; | |
} | |
double mpack_expect_double(mpack_reader_t* reader) { | |
mpack_tag_t var = mpack_read_tag(reader); | |
if (var.type == mpack_type_uint) | |
return (double)var.v.u; | |
else if (var.type == mpack_type_int) | |
return (double)var.v.i; | |
else if (var.type == mpack_type_float) | |
return (double)var.v.f; | |
else if (var.type == mpack_type_double) | |
return var.v.d; | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return 0.0; | |
} | |
float mpack_expect_float_strict(mpack_reader_t* reader) { | |
mpack_tag_t var = mpack_read_tag(reader); | |
if (var.type == mpack_type_float) | |
return var.v.f; | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return 0.0f; | |
} | |
double mpack_expect_double_strict(mpack_reader_t* reader) { | |
mpack_tag_t var = mpack_read_tag(reader); | |
if (var.type == mpack_type_float) | |
return (double)var.v.f; | |
else if (var.type == mpack_type_double) | |
return var.v.d; | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return 0.0; | |
} | |
// Ranged Number Functions | |
int8_t mpack_expect_i8_range(mpack_reader_t* reader, int8_t min_value, int8_t max_value) { | |
// 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 | |
int8_t val = mpack_expect_i8(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; | |
} | |
// TODO: missing i16_range, i32_range, i64_range? | |
uint8_t mpack_expect_u8_range(mpack_reader_t* reader, uint8_t min_value, uint8_t max_value) { | |
// make sure the range is sensible | |
mpack_assert(min_value <= max_value, "min_value %u must be less than or equal to max_value %u", | |
min_value, max_value); | |
// read the value | |
uint8_t val = mpack_expect_u8(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; | |
} | |
uint16_t mpack_expect_u16_range(mpack_reader_t* reader, uint16_t min_value, uint16_t max_value) { | |
// make sure the range is sensible | |
mpack_assert(min_value <= max_value, "min_value %u must be less than or equal to max_value %u", | |
min_value, max_value); | |
// read the value | |
uint16_t val = mpack_expect_u16(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; | |
} | |
uint32_t mpack_expect_u32_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value) { | |
// make sure the range is sensible | |
mpack_assert(min_value <= max_value, "min_value %u must be less than or equal to max_value %u", | |
min_value, max_value); | |
// read the value | |
uint32_t val = mpack_expect_u32(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; | |
} | |
uint64_t mpack_expect_u64_range(mpack_reader_t* reader, uint64_t min_value, uint64_t max_value) { | |
// make sure the range is sensible | |
mpack_assert(min_value <= max_value, | |
"min_value %" PRIu64 " must be less than or equal to max_value %" PRIu64, min_value, max_value); | |
// read the value | |
uint64_t val = mpack_expect_u64(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; | |
} | |
float mpack_expect_float_range(mpack_reader_t* reader, float min_value, float max_value) { | |
// make sure the range is sensible | |
mpack_assert(min_value <= max_value, "min_value %f must be less than or equal to max_value %f", | |
min_value, max_value); | |
// read the value | |
float val = mpack_expect_float(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; | |
} | |
double mpack_expect_double_range(mpack_reader_t* reader, double min_value, double max_value) { | |
// make sure the range is sensible | |
mpack_assert(min_value <= max_value, "min_value %f must be less than or equal to max_value %f", | |
min_value, max_value); | |
// read the value | |
double val = mpack_expect_double(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; | |
} | |
// 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) { | |
mpack_reader_track_element(reader); | |
uint8_t type = mpack_read_native_u8(reader); | |
if (reader->error != mpack_ok) | |
return; | |
if (type != 0xc0) | |
mpack_reader_flag_error(reader, mpack_error_type); | |
} | |
bool mpack_expect_bool(mpack_reader_t* reader) { | |
mpack_reader_track_element(reader); | |
uint8_t type = mpack_read_native_u8(reader); | |
if (reader->error != mpack_ok) | |
return false; | |
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); | |
} | |
// 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_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; | |
} | |
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); | |
} | |
uint32_t mpack_expect_array_range(mpack_reader_t* reader, uint32_t min_count, uint32_t max_count) { | |
// make sure the range is sensible | |
mpack_assert(min_count <= max_count, "min_count %u must be less than or equal to max_count %u", | |
min_count, max_count); | |
// read the count | |
uint32_t count = mpack_expect_array(reader); | |
if (mpack_reader_error(reader) != mpack_ok) | |
return min_count; | |
// make sure it fits | |
if (count < min_count || count > max_count) { | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return min_count; | |
} | |
return count; | |
} | |
#ifdef MPACK_MALLOC | |
void* mpack_expect_array_alloc_impl(mpack_reader_t* reader, size_t element_size, uint32_t max_count, size_t* out_count) { | |
size_t count = *out_count = mpack_expect_array(reader); | |
if (mpack_reader_error(reader)) | |
return NULL; | |
if (count > max_count) { | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return NULL; | |
} | |
void* p = MPACK_MALLOC(element_size * count); | |
if (p == NULL) | |
mpack_reader_flag_error(reader, mpack_error_memory); | |
return p; | |
} | |
#endif | |
// String Functions | |
uint32_t mpack_expect_str(mpack_reader_t* reader) { | |
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; | |
} | |
size_t mpack_expect_str_buf(mpack_reader_t* reader, char* buf, size_t bufsize) { | |
size_t strsize = mpack_expect_str(reader); | |
if (mpack_reader_error(reader)) | |
return 0; | |
if (strsize > bufsize) { | |
mpack_reader_flag_error(reader, mpack_error_too_big); | |
return 0; | |
} | |
mpack_read_bytes(reader, buf, strsize); | |
if (mpack_reader_error(reader)) | |
return 0; | |
mpack_done_str(reader); | |
return strsize; | |
} | |
// Binary Blob Functions | |
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) { | |
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_cstr(mpack_reader_t* reader, char* buf, size_t bufsize) { | |
// make sure buffer makes sense | |
mpack_assert(bufsize >= 1, "buffer size is zero; you must have room for at least a null-terminator"); | |
// expect a str | |
size_t rawsize = mpack_expect_str_buf(reader, buf, bufsize - 1); | |
if (mpack_reader_error(reader)) { | |
buf[0] = 0; | |
return; | |
} | |
buf[rawsize] = 0; | |
// check it for null bytes | |
for (size_t i = 0; i < rawsize; ++i) { | |
if (buf[i] == 0) { | |
buf[0] = 0; | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return; | |
} | |
} | |
} | |
void mpack_expect_utf8_cstr(mpack_reader_t* reader, char* buf, size_t bufsize) { | |
// make sure buffer makes sense | |
mpack_assert(bufsize >= 1, "buffer size is zero; you must have room for at least a null-terminator"); | |
// expect a raw | |
size_t rawsize = mpack_expect_str_buf(reader, buf, bufsize - 1); | |
if (mpack_reader_error(reader)) { | |
buf[0] = 0; | |
return; | |
} | |
buf[rawsize] = 0; | |
// check encoding | |
uint32_t state = 0; | |
uint32_t codepoint = 0; | |
for (size_t i = 0; i < rawsize; ++i) { | |
if (mpack_utf8_decode(&state, &codepoint, buf[i]) == MPACK_UTF8_REJECT) { | |
buf[0] = 0; | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return; | |
} | |
} | |
} | |
#ifdef MPACK_MALLOC | |
char* mpack_expect_cstr_alloc(mpack_reader_t* reader, size_t maxsize) { | |
// 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; | |
} | |
// read size | |
size_t length = mpack_expect_str(reader); // TODO: use expect str max? create expect str max... | |
if (mpack_reader_error(reader)) | |
return NULL; | |
if (length > (maxsize - 1)) { | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return NULL; | |
} | |
// allocate | |
char* str = (char*)MPACK_MALLOC(length + 1); | |
if (str == NULL) { | |
mpack_reader_flag_error(reader, mpack_error_memory); | |
return NULL; | |
} | |
// read with jump disabled so we don't leak our buffer | |
mpack_reader_track_bytes(reader, length); | |
mpack_read_native_nojump(reader, str, length); | |
if (mpack_reader_error(reader)) { | |
MPACK_FREE(str); | |
reader->error_fn(reader, mpack_reader_error(reader)); | |
return NULL; | |
} | |
str[length] = 0; | |
mpack_done_str(reader); | |
return str; | |
} | |
#endif | |
void mpack_expect_cstr_match(mpack_reader_t* reader, const char* str) { | |
if (reader->error != mpack_ok) | |
return; | |
// expect a str the correct length | |
size_t len = mpack_strlen(str); | |
if (len > UINT32_MAX) | |
mpack_reader_flag_error(reader, mpack_error_invalid); | |
mpack_expect_str_length(reader, (uint32_t)len); | |
if (mpack_reader_error(reader)) | |
return; | |
// check each byte | |
for (size_t i = 0; i < len; ++i) { | |
mpack_reader_track_bytes(reader, 1); | |
if (mpack_read_native_u8(reader) != *str++) { | |
mpack_reader_flag_error(reader, mpack_error_type); | |
return; | |
} | |
} | |
mpack_done_str(reader); | |
} | |
#endif | |
/* mpack-node.c */ | |
#define MPACK_INTERNAL 1 | |
/* #include "mpack-node.h" */ | |
#if MPACK_NODE | |
/* | |
* Tree Parsing | |
*/ | |
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_t* tree; | |
const char* data; | |
size_t left; // bytes left in data | |
size_t possible_nodes_left; | |
size_t level; | |
size_t depth; | |
mpack_level_t* stack; | |
bool stack_allocated; | |
} mpack_tree_parser_t; | |
MPACK_STATIC_INLINE_SPEED uint8_t mpack_tree_u8(mpack_tree_parser_t* parser) { | |
if (parser->possible_nodes_left < sizeof(uint8_t)) { | |
mpack_tree_flag_error(parser->tree, mpack_error_io); | |
return 0; | |
} | |
uint8_t val = mpack_load_native_u8(parser->data); | |
parser->data += sizeof(uint8_t); | |
parser->left -= sizeof(uint8_t); | |
parser->possible_nodes_left -= sizeof(uint8_t); | |
return val; | |
} | |
MPACK_STATIC_INLINE_SPEED uint16_t mpack_tree_u16(mpack_tree_parser_t* parser) { | |
if (parser->possible_nodes_left < sizeof(uint16_t)) { | |
mpack_tree_flag_error(parser->tree, mpack_error_io); | |
return 0; | |
} | |
uint16_t val = mpack_load_native_u16(parser->data); | |
parser->data += sizeof(uint16_t); | |
parser->left -= sizeof(uint16_t); | |
parser->possible_nodes_left -= sizeof(uint16_t); | |
return val; | |
} | |
MPACK_STATIC_INLINE_SPEED uint32_t mpack_tree_u32(mpack_tree_parser_t* parser) { | |
if (parser->possible_nodes_left < sizeof(uint32_t)) { | |
mpack_tree_flag_error(parser->tree, mpack_error_io); | |
return 0; | |
} | |
uint32_t val = mpack_load_native_u32(parser->data); | |
parser->data += sizeof(uint32_t); | |
parser->left -= sizeof(uint32_t); | |
parser->possible_nodes_left -= sizeof(uint32_t); | |
return val; | |
} | |
MPACK_STATIC_INLINE_SPEED uint64_t mpack_tree_u64(mpack_tree_parser_t* parser) { | |
if (parser->possible_nodes_left < sizeof(uint64_t)) { | |
mpack_tree_flag_error(parser->tree, mpack_error_io); | |
return 0; | |
} | |
uint64_t val = mpack_load_native_u64(parser->data); | |
parser->data += sizeof(uint64_t); | |
parser->left -= sizeof(uint64_t); | |
parser->possible_nodes_left -= sizeof(uint64_t); | |
return val; | |
} | |
MPACK_STATIC_INLINE int8_t mpack_tree_i8 (mpack_tree_parser_t* parser) {return (int8_t) mpack_tree_u8(parser); } | |
MPACK_STATIC_INLINE int16_t mpack_tree_i16(mpack_tree_parser_t* parser) {return (int16_t)mpack_tree_u16(parser);} | |
MPACK_STATIC_INLINE int32_t mpack_tree_i32(mpack_tree_parser_t* parser) {return (int32_t)mpack_tree_u32(parser);} | |
MPACK_STATIC_INLINE int64_t mpack_tree_i64(mpack_tree_parser_t* parser) {return (int64_t)mpack_tree_u64(parser);} | |
MPACK_STATIC_INLINE_SPEED float mpack_tree_float(mpack_tree_parser_t* parser) { | |
union { | |
float f; | |
uint32_t i; | |
} u; | |
u.i = mpack_tree_u32(parser); | |
return u.f; | |
} | |
MPACK_STATIC_INLINE_SPEED double mpack_tree_double(mpack_tree_parser_t* parser) { | |
union { | |
double d; | |
uint64_t i; | |
} u; | |
u.i = mpack_tree_u64(parser); | |
return u.d; | |
} | |
void mpack_tree_parse_children(mpack_tree_parser_t* parser, mpack_node_data_t* node) { | |
mpack_type_t type = node->type; | |
size_t total = node->value.content.n; | |
// Make sure we have enough room in the stack | |
if (parser->level + 1 == parser->depth) { | |
#ifdef MPACK_MALLOC | |
size_t new_depth = parser->depth * 2; | |
mpack_log("growing stack to depth %i\n", (int)new_depth); | |
// Replace the stack-allocated parsing stack | |
if (parser->stack_allocated) { | |
mpack_level_t* new_stack = (mpack_level_t*)MPACK_MALLOC(sizeof(mpack_level_t) * new_depth); | |
if (!new_stack) { | |
mpack_tree_flag_error(parser->tree, mpack_error_memory); | |
parser->level = 0; | |
return; | |
} | |
memcpy(new_stack, parser->stack, sizeof(mpack_level_t) * parser->depth); | |
parser->stack = new_stack; | |
parser->stack_allocated = false; | |
// Realloc the allocated parsing stack | |
} else { | |
parser->stack = (mpack_level_t*)mpack_realloc(parser->stack, sizeof(mpack_level_t) * parser->depth, sizeof(mpack_level_t) * new_depth); | |
if (!parser->stack) { | |
mpack_tree_flag_error(parser->tree, mpack_error_memory); | |
parser->level = 0; | |
return; | |
} | |
} | |
parser->depth = new_depth; | |
#else | |
mpack_tree_flag_error(parser->tree, mpack_error_too_big); | |
parser->level = 0; | |
return; | |
#endif | |
} | |
// Calculate total elements to read | |
if (type == mpack_type_map) { | |
if ((uint64_t)total * 2 > (uint64_t)SIZE_MAX) { | |
mpack_tree_flag_error(parser->tree, mpack_error_too_big); | |
parser->level = 0; | |
return; | |
} | |
total *= 2; | |
} | |
// Each node is at least one byte. Count these bytes now to make | |
// sure there is enough data left. | |
if (total > parser->possible_nodes_left) { | |
mpack_tree_flag_error(parser->tree, mpack_error_invalid); | |
parser->level = 0; | |
return; | |
} | |
parser->possible_nodes_left -= total; | |
// If there are enough nodes left in the current page, no need to grow | |
if (total <= parser->tree->page.left) { | |
node->value.content.children = parser->tree->page.nodes + parser->tree->page.pos; | |
parser->tree->page.pos += total; | |
parser->tree->page.left -= total; | |
} else { | |
#ifdef MPACK_MALLOC | |
// We can't grow if we're using a fixed pool | |
if (!parser->tree->owned) { | |
mpack_tree_flag_error(parser->tree, mpack_error_too_big); | |
parser->level = 0; | |
return; | |
} | |
// 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. | |
// Allocate the new link first. The two cases below put it into the list before trying | |
// to allocate its nodes so it gets freed later in case of allocation failure. | |
mpack_tree_link_t* link = (mpack_tree_link_t*)MPACK_MALLOC(sizeof(mpack_tree_link_t)); | |
if (link == NULL) { | |
mpack_tree_flag_error(parser->tree, mpack_error_invalid); | |
parser->level = 0; | |
return; | |
} | |
if (total > MPACK_NODE_PAGE_SIZE || parser->tree->page.left > MPACK_NODE_PAGE_SIZE / 8) { | |
mpack_log("allocating seperate page for %i children, %i left in page of size %i\n", | |
(int)total, (int)parser->tree->page.left, (int)MPACK_NODE_PAGE_SIZE); | |
// Allocate only this node's children and insert it after the current page | |
link->next = parser->tree->page.next; | |
parser->tree->page.next = link; | |
link->nodes = (mpack_node_data_t*)MPACK_MALLOC(sizeof(mpack_node_data_t) * total); | |
if (link->nodes == NULL) { | |
mpack_tree_flag_error(parser->tree, mpack_error_invalid); | |
parser->level = 0; | |
return; | |
} | |
// Use the new page for the node's children. pos and left are not used. | |
node->value.content.children = link->nodes; | |
} else { | |
mpack_log("allocating new page for %i children, wasting %i in page of size %i\n", | |
(int)total, (int)parser->tree->page.left, (int)MPACK_NODE_PAGE_SIZE); | |
// Move the current page into the new link, and allocate a new page | |
*link = parser->tree->page; | |
parser->tree->page.next = link; | |
parser->tree->page.nodes = (mpack_node_data_t*)MPACK_MALLOC(sizeof(mpack_node_data_t) * MPACK_NODE_PAGE_SIZE); | |
if (parser->tree->page.nodes == NULL) { | |
mpack_tree_flag_error(parser->tree, mpack_error_invalid); | |
parser->level = 0; | |
return; | |
} | |
// Take this node's children from the page | |
node->value.content.children = parser->tree->page.nodes; | |
parser->tree->page.pos = total; | |
parser->tree->page.left = MPACK_NODE_PAGE_SIZE - total; | |
} | |
#else | |
// We can't grow if we don't have an allocator | |
mpack_tree_flag_error(parser->tree, mpack_error_too_big); | |
parser->level = 0; | |
return; | |
#endif | |
} | |
// Push this node onto the stack to read its children | |
++parser->level; | |
parser->stack[parser->level].child = node->value.content.children; | |
parser->stack[parser->level].left = total; | |
} | |
void mpack_tree_parse_bytes(mpack_tree_parser_t* parser, mpack_node_data_t* node) { | |
size_t length = node->value.data.l; | |
if (length > parser->possible_nodes_left) { | |
mpack_tree_flag_error(parser->tree, mpack_error_invalid); | |
parser->level = 0; | |
return; | |
} | |
node->value.data.bytes = parser->data; | |
parser->data += length; | |
parser->left -= length; | |
parser->possible_nodes_left -= length; | |
} | |
void mpack_tree_parse(mpack_tree_t* tree, const char* data, size_t length) { | |
mpack_log("starting parse\n"); | |
// This function is unfortunately huge and ugly, but there isn't | |
// a good way to break it apart without losing performance. It's | |
// well-commented to try to make up for it. | |
if (length == 0) { | |
mpack_tree_init_error(tree, mpack_error_io); | |
return; | |
} | |
if (tree->page.left == 0) { | |
mpack_break("initial page has no nodes!"); | |
mpack_tree_init_error(tree, mpack_error_bug); | |
return; | |
} | |
tree->root = tree->page.nodes + tree->page.pos; | |
++tree->page.pos; | |
--tree->page.left; | |
// Setup parser | |
mpack_tree_parser_t parser; | |
mpack_memset(&parser, 0, sizeof(parser)); | |
parser.tree = tree; | |
parser.data = data; | |
parser.left = length; | |
// 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. | |
// Even when we have a malloc() function, it's much faster to | |
// allocate the initial parsing stack on the call stack. We | |
// replace it with a heap allocation if we need to grow it. | |
#ifdef MPACK_MALLOC | |
static const size_t initial_depth = MPACK_NODE_INITIAL_DEPTH; | |
parser.stack_allocated = true; | |
#else | |
static const size_t initial_depth = MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC; | |
#endif | |
mpack_level_t stack_[initial_depth]; | |
parser.depth = initial_depth; | |
parser.stack = stack_; | |
// We keep track of the number of possible nodes left in the data. This | |
// is to ensure that malicious nested data is not trying to make us | |
// run out of memory by allocating too many nodes. (For example malicious | |
// data that repeats 0xDE 0xFF 0xFF would otherwise cause us to run out | |
// of memory. With this, the parser can only allocate 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 stack.) | |
parser.possible_nodes_left = length; | |
// configure the root node | |
--parser.possible_nodes_left; | |
tree->node_count = 1; | |
parser.level = 0; | |
parser.stack[0].child = tree->root; | |
parser.stack[0].left = 1; | |
do { | |
mpack_node_data_t* node = parser.stack[parser.level].child; | |
--parser.stack[parser.level].left; | |
++parser.stack[parser.level].child; | |
// read the type (we've already counted this byte in possible_nodes_left) | |
++parser.possible_nodes_left; | |
uint8_t type = mpack_tree_u8(&parser); | |
// 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. | |
switch (type) { | |
// 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; | |
break; | |
// 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; | |
break; | |
// 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->value.content.n = type & ~0xf0; | |
mpack_tree_parse_children(&parser, node); | |
break; | |
// 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->value.content.n = type & ~0xf0; | |
mpack_tree_parse_children(&parser, node); | |
break; | |
// 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->value.data.l = type & ~0xe0; | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// nil | |
case 0xc0: | |
node->type = mpack_type_nil; | |
break; | |
// bool | |
case 0xc2: case 0xc3: | |
node->type = mpack_type_bool; | |
node->value.b = type & 1; | |
break; | |
// bin8 | |
case 0xc4: | |
node->type = mpack_type_bin; | |
node->value.data.l = mpack_tree_u8(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// bin16 | |
case 0xc5: | |
node->type = mpack_type_bin; | |
node->value.data.l = mpack_tree_u16(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// bin32 | |
case 0xc6: | |
node->type = mpack_type_bin; | |
node->value.data.l = mpack_tree_u32(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// ext8 | |
case 0xc7: | |
node->type = mpack_type_ext; | |
node->value.data.l = mpack_tree_u8(&parser); | |
node->exttype = mpack_tree_i8(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// ext16 | |
case 0xc8: | |
node->type = mpack_type_ext; | |
node->value.data.l = mpack_tree_u16(&parser); | |
node->exttype = mpack_tree_i8(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// ext32 | |
case 0xc9: | |
node->type = mpack_type_ext; | |
node->value.data.l = mpack_tree_u32(&parser); | |
node->exttype = mpack_tree_i8(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// float | |
case 0xca: | |
node->type = mpack_type_float; | |
node->value.f = mpack_tree_float(&parser); | |
break; | |
// double | |
case 0xcb: | |
node->type = mpack_type_double; | |
node->value.d = mpack_tree_double(&parser); | |
break; | |
// uint8 | |
case 0xcc: | |
node->type = mpack_type_uint; | |
node->value.u = mpack_tree_u8(&parser); | |
break; | |
// uint16 | |
case 0xcd: | |
node->type = mpack_type_uint; | |
node->value.u = mpack_tree_u16(&parser); | |
break; | |
// uint32 | |
case 0xce: | |
node->type = mpack_type_uint; | |
node->value.u = mpack_tree_u32(&parser); | |
break; | |
// uint64 | |
case 0xcf: | |
node->type = mpack_type_uint; | |
node->value.u = mpack_tree_u64(&parser); | |
break; | |
// int8 | |
case 0xd0: | |
node->type = mpack_type_int; | |
node->value.i = mpack_tree_i8(&parser); | |
break; | |
// int16 | |
case 0xd1: | |
node->type = mpack_type_int; | |
node->value.i = mpack_tree_i16(&parser); | |
break; | |
// int32 | |
case 0xd2: | |
node->type = mpack_type_int; | |
node->value.i = mpack_tree_i32(&parser); | |
break; | |
// int64 | |
case 0xd3: | |
node->type = mpack_type_int; | |
node->value.i = mpack_tree_i64(&parser); | |
break; | |
// fixext1 | |
case 0xd4: | |
node->type = mpack_type_ext; | |
node->value.data.l = 1; | |
node->exttype = mpack_tree_i8(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// fixext2 | |
case 0xd5: | |
node->type = mpack_type_ext; | |
node->value.data.l = 2; | |
node->exttype = mpack_tree_i8(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// fixext4 | |
case 0xd6: | |
node->type = mpack_type_ext; | |
node->value.data.l = 4; | |
node->exttype = mpack_tree_i8(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// fixext8 | |
case 0xd7: | |
node->type = mpack_type_ext; | |
node->value.data.l = 8; | |
node->exttype = mpack_tree_i8(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// fixext16 | |
case 0xd8: | |
node->type = mpack_type_ext; | |
node->value.data.l = 16; | |
node->exttype = mpack_tree_i8(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// str8 | |
case 0xd9: | |
node->type = mpack_type_str; | |
node->value.data.l = mpack_tree_u8(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// str16 | |
case 0xda: | |
node->type = mpack_type_str; | |
node->value.data.l = mpack_tree_u16(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// str32 | |
case 0xdb: | |
node->type = mpack_type_str; | |
node->value.data.l = mpack_tree_u32(&parser); | |
mpack_tree_parse_bytes(&parser, node); | |
break; | |
// array16 | |
case 0xdc: | |
node->type = mpack_type_array; | |
node->value.content.n = mpack_tree_u16(&parser); | |
mpack_tree_parse_children(&parser, node); | |
break; | |
// array32 | |
case 0xdd: | |
node->type = mpack_type_array; | |
node->value.content.n = mpack_tree_u32(&parser); | |
mpack_tree_parse_children(&parser, node); | |
break; | |
// map16 | |
case 0xde: | |
node->type = mpack_type_map; | |
node->value.content.n = mpack_tree_u16(&parser); | |
mpack_tree_parse_children(&parser, node); | |
break; | |
// map32 | |
case 0xdf: | |
node->type = mpack_type_map; | |
node->value.content.n = mpack_tree_u32(&parser); | |
mpack_tree_parse_children(&parser, node); | |
break; | |
// reserved | |
case 0xc1: | |
mpack_tree_flag_error(tree, mpack_error_invalid); | |
break; | |
} | |
// Pop any empty compound types from the stack | |
while (parser.level != 0 && parser.stack[parser.level].left == 0) | |
--parser.level; | |
} while (parser.level != 0 && mpack_tree_error(parser.tree) == mpack_ok); | |
#ifdef MPACK_MALLOC | |
if (!parser.stack_allocated) | |
MPACK_FREE(parser.stack); | |
#endif | |
tree->size = length - parser.left; | |
mpack_log("parsed tree of %i bytes, %i bytes left\n", (int)tree->size, (int)parser.left); | |
mpack_log("%i nodes in final page\n", (int)tree->page.pos); | |
// This seems like a bug / performance flaw in GCC. In release the | |
// below assert would compile to: | |
// | |
// (!(possible_nodes_left == remaining) ? __builtin_unreachable() : ((void)0)) | |
// | |
// This produces identical assembly with GCC 5.1 on ARM64 under -O3, but | |
// with -O3 -flto, node parsing is over 4% slower. This should be a no-op | |
// even in -flto since the function ends here and possible_nodes_left | |
// does not escape this function. | |
// | |
// Leaving a TODO: here to explore this further. In the meantime we preproc it | |
// under MPACK_DEBUG. | |
#if MPACK_DEBUG | |
mpack_assert(parser.possible_nodes_left == parser.left, | |
"incorrect calculation of possible nodes! %i possible nodes, but %i bytes remaining", | |
(int)parser.possible_nodes_left, (int)parser.left); | |
#endif | |
} | |
/* | |
* Tree functions | |
*/ | |
mpack_node_t mpack_tree_root(mpack_tree_t* tree) { | |
return mpack_node(tree, (mpack_tree_error(tree) != mpack_ok) ? &tree->nil_node : tree->root); | |
} | |
void mpack_tree_init_clear(mpack_tree_t* tree) { | |
mpack_memset(tree, 0, sizeof(*tree)); | |
tree->nil_node.type = mpack_type_nil; | |
} | |
#ifdef MPACK_MALLOC | |
void mpack_tree_init(mpack_tree_t* tree, const char* data, size_t length) { | |
mpack_tree_init_clear(tree); | |
tree->owned = true; | |
// allocate first page | |
mpack_log("allocating initial page of size %i\n", (int)MPACK_NODE_PAGE_SIZE); | |
tree->page.nodes = (mpack_node_data_t*)MPACK_MALLOC(sizeof(mpack_node_data_t) * MPACK_NODE_PAGE_SIZE); | |
if (tree->page.nodes == NULL) { | |
tree->error = mpack_error_memory; | |
return; | |
} | |
tree->page.next = NULL; | |
tree->page.pos = 0; | |
tree->page.left = MPACK_NODE_PAGE_SIZE; | |
mpack_tree_parse(tree, data, 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); | |
tree->page.next = NULL; | |
tree->page.nodes = node_pool; | |
tree->page.pos = 0; | |
tree->page.left = node_pool_count; | |
mpack_tree_parse(tree, data, length); | |
} | |
void mpack_tree_init_error(mpack_tree_t* tree, mpack_error_t error) { | |
mpack_tree_init_clear(tree); | |
tree->error = error; | |
} | |
#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, const char* filename, size_t max_size) { | |
// open the file | |
FILE* file = fopen(filename, "rb"); | |
if (!file) { | |
mpack_tree_init_error(tree, mpack_error_io); | |
return false; | |
} | |
// get the file size | |
fseek(file, 0, SEEK_END); | |
long size = ftell(file); | |
fseek(file, 0, SEEK_SET); | |
if (size < 0) { | |
fclose(file); | |
mpack_tree_init_error(tree, mpack_error_io); | |
return false; | |
} | |
if (size == 0) { | |
fclose(file); | |
mpack_tree_init_error(tree, mpack_error_invalid); | |
return false; | |
} | |
// make sure the size is less than max_size | |
// (this mess exists to safely convert between long and size_t regardless of their widths) | |
if (max_size != 0 && (((uint64_t)LONG_MAX > (uint64_t)SIZE_MAX && size > (long)SIZE_MAX) || (size_t)size > max_size)) { | |
fclose(file); | |
mpack_tree_init_error(tree, mpack_error_too_big); | |
return false; | |
} | |
// allocate data | |
file_tree->data = (char*)MPACK_MALLOC(size); | |
if (file_tree->data == NULL) { | |
fclose(file); | |
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) { | |
fclose(file); | |
mpack_tree_init_error(tree, mpack_error_io); | |
MPACK_FREE(file_tree->data); | |
return false; | |
} | |
total += read; | |
} | |
fclose(file); | |
file_tree->size = (size_t)size; | |
return true; | |
} | |
void mpack_tree_init_file(mpack_tree_t* tree, const char* filename, size_t max_size) { | |
// the C STDIO family of file functions use long (e.g. ftell) | |
if (max_size > LONG_MAX) { | |
mpack_break("max_size of %" PRIu64 " is invalid, maximum is LONG_MAX", (uint64_t)max_size); | |
mpack_tree_init_error(tree, mpack_error_too_big); | |
return; | |
} | |
// 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, filename, max_size)) { | |
MPACK_FREE(file_tree); | |
return; | |
} | |
mpack_tree_init(tree, file_tree->data, file_tree->size); | |
mpack_tree_set_context(tree, file_tree); | |
mpack_tree_set_teardown(tree, mpack_file_tree_teardown); | |
} | |
#endif | |
mpack_error_t mpack_tree_destroy(mpack_tree_t* tree) { | |
#ifdef MPACK_MALLOC | |
if (tree->owned) { | |
if (tree->page.nodes) | |
MPACK_FREE(tree->page.nodes); | |
mpack_tree_link_t* link = tree->page.next; | |
while (link) { | |
mpack_tree_link_t* next = link->next; | |
if (link->nodes) | |
MPACK_FREE(link->nodes); | |
MPACK_FREE(link); | |
link = next; | |
} | |
} | |
#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) { | |
mpack_log("tree %p setting error %i: %s\n", tree, (int)error, mpack_error_to_string(error)); | |
if (tree->error == mpack_ok) { | |
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) { | |
mpack_tag_t tag; | |
mpack_memset(&tag, 0, sizeof(tag)); | |
tag.type = node.data->type; | |
switch (node.data->type) { | |
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->value.data.l; break; | |
case mpack_type_bin: tag.v.l = node.data->value.data.l; break; | |
case mpack_type_ext: tag.v.l = node.data->value.data.l; break; | |
case mpack_type_array: tag.v.n = node.data->value.content.n; break; | |
case mpack_type_map: tag.v.n = node.data->value.content.n; break; | |
} | |
return tag; | |
} | |
#if MPACK_DEBUG && MPACK_STDIO && !MPACK_NO_PRINT | |
static void mpack_node_print_element(mpack_node_t node, size_t depth) { | |
mpack_node_data_t* data = node.data; | |
switch (data->type) { | |
case mpack_type_nil: | |
printf("null"); | |
break; | |
case mpack_type_bool: | |
printf(data->value.b ? "true" : "false"); | |
break; | |
case mpack_type_float: | |
printf("%f", data->value.f); | |
break; | |
case mpack_type_double: | |
printf("%f", data->value.d); | |
break; | |
case mpack_type_int: | |
printf("%" PRIi64, data->value.i); | |
break; | |
case mpack_type_uint: | |
printf("%" PRIu64, data->value.u); | |
break; | |
case mpack_type_bin: | |
printf("<binary data of length %u>", data->value.data.l); | |
break; | |
case mpack_type_ext: | |
printf("<ext data of type %i and length %u>", data->exttype, data->value.data.l); | |
break; | |
case mpack_type_str: | |
{ | |
putchar('"'); | |
const char* bytes = mpack_node_data(node); | |
for (size_t i = 0; i < data->value.data.l; ++i) { | |
char c = bytes[i]; | |
switch (c) { | |
case '\n': printf("\\n"); break; | |
case '\\': printf("\\\\"); break; | |
case '"': printf("\\\""); break; | |
default: putchar(c); break; | |
} | |
} | |
putchar('"'); | |
} | |
break; | |
case mpack_type_array: | |
printf("[\n"); | |
for (size_t i = 0; i < data->value.content.n; ++i) { | |
for (size_t j = 0; j < depth + 1; ++j) | |
printf(" "); | |
mpack_node_print_element(mpack_node_array_at(node, i), depth + 1); | |
if (i != data->value.content.n - 1) | |
putchar(','); | |
putchar('\n'); | |
} | |
for (size_t i = 0; i < depth; ++i) | |
printf(" "); | |
putchar(']'); | |
break; | |
case mpack_type_map: | |
printf("{\n"); | |
for (size_t i = 0; i < data->value.content.n; ++i) { | |
for (size_t j = 0; j < depth + 1; ++j) | |
printf(" "); | |
mpack_node_print_element(mpack_node_map_key_at(node, i), depth + 1); | |
printf(": "); | |
mpack_node_print_element(mpack_node_map_value_at(node, i), depth + 1); | |
if (i != data->value.content.n - 1) | |
putchar(','); | |
putchar('\n'); | |
} | |
for (size_t i = 0; i < depth; ++i) | |
printf(" "); | |
putchar('}'); | |
break; | |
} | |
} | |
void mpack_node_print(mpack_node_t node) { | |
int depth = 2; | |
for (int i = 0; i < depth; ++i) | |
printf(" "); | |
mpack_node_print_element(node, depth); | |
putchar('\n'); | |
} | |
#endif | |
/* | |
* Node Data Functions | |
*/ | |
size_t mpack_node_copy_data(mpack_node_t node, char* buffer, size_t size) { | |
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 && type != mpack_type_ext) { | |
mpack_node_flag_error(node, mpack_error_type); | |
return 0; | |
} | |
if (node.data->value.data.l > size) { | |
mpack_node_flag_error(node, mpack_error_too_big); | |
return 0; | |
} | |
mpack_memcpy(buffer, node.data->value.data.bytes, node.data->value.data.l); | |
return (size_t)node.data->value.data.l; | |
} | |
void mpack_node_copy_cstr(mpack_node_t node, char* buffer, size_t size) { | |
if (mpack_node_error(node) != mpack_ok) | |
return; | |
mpack_assert(size >= 1, "buffer size is zero; you must have room for at least a null-terminator"); | |
if (node.data->type != mpack_type_str) { | |
buffer[0] = '\0'; | |
mpack_node_flag_error(node, mpack_error_type); | |
return; | |
} | |
if (node.data->value.data.l > size - 1) { | |
buffer[0] = '\0'; | |
mpack_node_flag_error(node, mpack_error_too_big); | |
return; | |
} | |
mpack_memcpy(buffer, node.data->value.data.bytes, node.data->value.data.l); | |
buffer[node.data->value.data.l] = '\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 && type != mpack_type_ext) { | |
mpack_node_flag_error(node, mpack_error_type); | |
return NULL; | |
} | |
if (node.data->value.data.l > maxlen) { | |
mpack_node_flag_error(node, mpack_error_too_big); | |
return NULL; | |
} | |
char* ret = (char*) MPACK_MALLOC((size_t)node.data->value.data.l); | |
if (ret == NULL) { | |
mpack_node_flag_error(node, mpack_error_memory); | |
return NULL; | |
} | |
mpack_memcpy(ret, node.data->value.data.bytes, node.data->value.data.l); | |
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->value.data.l > maxlen - 1) { | |
mpack_node_flag_error(node, mpack_error_too_big); | |
return NULL; | |
} | |
char* ret = (char*) MPACK_MALLOC((size_t)(node.data->value.data.l + 1)); | |
if (ret == NULL) { | |
mpack_node_flag_error(node, mpack_error_memory); | |
return NULL; | |
} | |
mpack_memcpy(ret, node.data->value.data.bytes, node.data->value.data.l); | |
ret[node.data->value.data.l] = '\0'; | |
return ret; | |
} | |
#endif | |
/* | |
* Compound Node Functions | |
*/ | |
mpack_node_t mpack_node_map_int_impl(mpack_node_t node, int64_t num, bool optional) { | |
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); | |
} | |
for (size_t i = 0; i < node.data->value.content.n; ++i) { | |
mpack_node_data_t* key = mpack_node_child(node, i * 2); | |
mpack_node_data_t* value = mpack_node_child(node, i * 2 + 1); | |
if (key->type == mpack_type_int && key->value.i == num) | |
return mpack_node(node.tree, value); | |
if (key->type == mpack_type_uint && num >= 0 && key->value.u == (uint64_t)num) | |
return mpack_node(node.tree, value); | |
} | |
if (!optional) | |
mpack_node_flag_error(node, mpack_error_data); | |
return mpack_tree_nil_node(node.tree); | |
} | |
mpack_node_t mpack_node_map_uint_impl(mpack_node_t node, uint64_t num, bool optional) { | |
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); | |
} | |
for (size_t i = 0; i < node.data->value.content.n; ++i) { | |
mpack_node_data_t* key = mpack_node_child(node, i * 2); | |
mpack_node_data_t* value = mpack_node_child(node, i * 2 + 1); | |
if (key->type == mpack_type_uint && key->value.u == num) | |
return mpack_node(node.tree, value); | |
if (key->type == mpack_type_int && key->value.i >= 0 && (uint64_t)key->value.i == num) | |
return mpack_node(node.tree, value); | |
} | |
if (!optional) | |
mpack_node_flag_error(node, mpack_error_data); | |
return mpack_tree_nil_node(node.tree); | |
} | |
mpack_node_t mpack_node_map_str_impl(mpack_node_t node, const char* str, size_t length, bool optional) { | |
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); | |
} | |
for (size_t i = 0; i < node.data->value.content.n; ++i) { | |
mpack_node_data_t* key = mpack_node_child(node, i * 2); | |
mpack_node_data_t* value = mpack_node_child(node, i * 2 + 1); | |
if (key->type == mpack_type_str && key->value.data.l == length && mpack_memcmp(str, key->value.data.bytes, length) == 0) | |
return mpack_node(node.tree, value); | |
} | |
if (!optional) | |
mpack_node_flag_error(node, mpack_error_data); | |
return mpack_tree_nil_node(node.tree); | |
} | |
bool mpack_node_map_contains_str(mpack_node_t node, const char* str, size_t length) { | |
if (mpack_node_error(node) != mpack_ok) | |
return false; | |
if (node.data->type != mpack_type_map) { | |
mpack_node_flag_error(node, mpack_error_type); | |
return false; | |
} | |
for (size_t i = 0; i < node.data->value.content.n; ++i) { | |
mpack_node_data_t* key = mpack_node_child(node, i * 2); | |
if (key->type == mpack_type_str && key->value.data.l == length && mpack_memcmp(str, key->value.data.bytes, length) == 0) | |
return true; | |
} | |
return false; | |
} | |
#endif | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2015 Nicholas Fraser | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
* | |
*/ | |
/* | |
* This is the MPack 0.6 amalgamation package. | |
* | |
* http://github.com/ludocode/mpack | |
*/ | |
#ifndef MPACK_H | |
#define MPACK_H 1 | |
#define MPACK_AMALGAMATED 1 | |
#include "mpack-config.h" | |
/* mpack-platform.h */ | |
/** | |
* @file | |
* | |
* Abstracts all platform-specific code from MPack. This contains | |
* implementations of standard C functions when libc is not available, | |
* as well as wrappers to library functions. | |
*/ | |
#ifndef MPACK_PLATFORM_H | |
#define MPACK_PLATFORM_H 1 | |
/* For now, nothing in here should be seen by Doxygen. */ | |
/** @cond */ | |
#if defined(WIN32) && defined(MPACK_INTERNAL) && MPACK_INTERNAL | |
#define _CRT_SECURE_NO_WARNINGS 1 | |
#endif | |
/* #include "mpack-config.h" */ | |
/* | |
* Now that the config is included, we define to 0 any of the configuration | |
* options and other switches that aren't defined. This supports -Wundef | |
* without us having to write "#if defined(X) && X" everywhere (and while | |
* allowing configs to be pre-defined to 0.) | |
*/ | |
#ifndef MPACK_READER | |
#define MPACK_READER 0 | |
#endif | |
#ifndef MPACK_EXPECT | |
#define MPACK_EXPECT 0 | |
#endif | |
#ifndef MPACK_NODE | |
#define MPACK_NODE 0 | |
#endif | |
#ifndef MPACK_WRITER | |
#define MPACK_WRITER 0 | |
#endif | |
#ifndef MPACK_STDLIB | |
#define MPACK_STDLIB 0 | |
#endif | |
#ifndef MPACK_STDIO | |
#define MPACK_STDIO 0 | |
#endif | |
#ifndef MPACK_DEBUG | |
#define MPACK_DEBUG 0 | |
#endif | |
#ifndef MPACK_CUSTOM_ASSERT | |
#define MPACK_CUSTOM_ASSERT 0 | |
#endif | |
#ifndef MPACK_READ_TRACKING | |
#define MPACK_READ_TRACKING 0 | |
#endif | |
#ifndef MPACK_WRITE_TRACKING | |
#define MPACK_WRITE_TRACKING 0 | |
#endif | |
#ifndef MPACK_NO_TRACKING | |
#define MPACK_NO_TRACKING 0 | |
#endif | |
#ifndef MPACK_OPTIMIZE_FOR_SIZE | |
#define MPACK_OPTIMIZE_FOR_SIZE 0 | |
#endif | |
#ifndef MPACK_EMIT_INLINE_DEFS | |
#define MPACK_EMIT_INLINE_DEFS 0 | |
#endif | |
#ifndef MPACK_AMALGAMATED | |
#define MPACK_AMALGAMATED 0 | |
#endif | |
#ifndef MPACK_INTERNAL | |
#define MPACK_INTERNAL 0 | |
#endif | |
#ifndef MPACK_NO_PRINT | |
#define MPACK_NO_PRINT 0 | |
#endif | |
/* System headers (based on configuration) */ | |
#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 | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <stdbool.h> | |
#include <inttypes.h> | |
#include <limits.h> | |
#if MPACK_STDLIB | |
#include <string.h> | |
#include <stdlib.h> | |
#endif | |
#if MPACK_STDIO | |
#include <stdio.h> | |
#endif | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
/* Miscellaneous helper macros */ | |
#define MPACK_UNUSED(var) ((void)(var)) | |
#define MPACK_STRINGIFY_IMPL(arg) #arg | |
#define MPACK_STRINGIFY(arg) MPACK_STRINGIFY_IMPL(arg) | |
/* | |
* Definition of inline macros. | |
* | |
* MPack supports several different modes for inline functions: | |
* - functions declared with a platform-specific always-inline (MPACK_ALWAYS_INLINE) | |
* - functions declared inline regardless of optimization options (MPACK_INLINE) | |
* - functions declared inline only in builds optimized for speed (MPACK_INLINE_SPEED) | |
* | |
* 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 "inline" does not emit a definition and "extern inline" does | |
* - The GNU model, where "inline" emits a definition and "extern inline" does not | |
* - The C++ model, where "inline" emits a definition with weak 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. | |
* | |
* Inline functions in source files are defined static, so MPACK_STATIC_INLINE | |
* is used for small functions and MPACK_STATIC_INLINE_SPEED is used for | |
* larger optionally inline functions. | |
*/ | |
#if defined(__cplusplus) | |
// C++ rules | |
// The linker will need weak symbol support to link C++ object files, | |
// so we don't need to worry about emitting a single definition. | |
#define MPACK_INLINE 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 | |
#else | |
// C99 rules | |
#if MPACK_EMIT_INLINE_DEFS | |
#define MPACK_INLINE extern inline | |
#else | |
#define MPACK_INLINE inline | |
#endif | |
#endif | |
#define MPACK_STATIC_INLINE static inline | |
#if MPACK_OPTIMIZE_FOR_SIZE | |
#define MPACK_STATIC_INLINE_SPEED static | |
#define MPACK_INLINE_SPEED /* nothing */ | |
#if MPACK_EMIT_INLINE_DEFS | |
#define MPACK_DEFINE_INLINE_SPEED 1 | |
#else | |
#define MPACK_DEFINE_INLINE_SPEED 0 | |
#endif | |
#else | |
#define MPACK_STATIC_INLINE_SPEED static inline | |
#define MPACK_INLINE_SPEED MPACK_INLINE | |
#define MPACK_DEFINE_INLINE_SPEED 1 | |
#endif | |
#ifdef MPACK_OPTIMIZE_FOR_SPEED | |
#error "You should define MPACK_OPTIMIZE_FOR_SIZE, not MPACK_OPTIMIZE_FOR_SPEED." | |
#endif | |
/* Some compiler-specific keywords and builtins */ | |
#if defined(__GNUC__) || defined(__clang__) | |
#define MPACK_UNREACHABLE __builtin_unreachable() | |
#define MPACK_NORETURN(fn) fn __attribute__((noreturn)) | |
#define MPACK_ALWAYS_INLINE __attribute__((always_inline)) MPACK_INLINE | |
#elif defined(_MSC_VER) | |
#define MPACK_UNREACHABLE __assume(0) | |
#define MPACK_NORETURN(fn) __declspec(noreturn) fn | |
#define MPACK_ALWAYS_INLINE __forceinline | |
#else | |
#define MPACK_UNREACHABLE ((void)0) | |
#define MPACK_NORETURN(fn) fn | |
#define MPACK_ALWAYS_INLINE MPACK_INLINE | |
#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(const char* message)); | |
#if MPACK_STDIO | |
MPACK_NORETURN(void mpack_assert_fail_format(const char* format, ...)); | |
#define mpack_assert_fail_at(line, file, expr, ...) \ | |
mpack_assert_fail_format("mpack assertion failed at " file ":" #line "\n" expr "\n" __VA_ARGS__) | |
#else | |
#define mpack_assert_fail_at(line, file, ...) \ | |
mpack_assert_fail("mpack assertion failed at " file ":" #line ) | |
#endif | |
#define mpack_assert_fail_pos(line, file, expr, ...) mpack_assert_fail_at(line, file, expr, __VA_ARGS__) | |
#define mpack_assert(expr, ...) ((!(expr)) ? mpack_assert_fail_pos(__LINE__, __FILE__, #expr, __VA_ARGS__) : (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_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_break_hit_at(line, file, __VA_ARGS__) | |
#define mpack_break(...) mpack_break_hit_pos(__LINE__, __FILE__, __VA_ARGS__) | |
#else | |
#define mpack_assert(expr, ...) ((!(expr)) ? MPACK_UNREACHABLE, (void)0 : (void)0) | |
#define mpack_break(...) ((void)0) | |
#endif | |
/* Wrap some needed libc functions */ | |
#if MPACK_STDLIB | |
#define mpack_memset memset | |
#define mpack_memcpy memcpy | |
#define mpack_memmove memmove | |
#define mpack_memcmp memcmp | |
#define mpack_strlen strlen | |
#else | |
void* mpack_memset(void *s, int c, size_t n); | |
void* mpack_memcpy(void *s1, const void *s2, size_t n); | |
void* mpack_memmove(void *s1, const void *s2, size_t n); | |
int mpack_memcmp(const void* s1, const void* s2, size_t n); | |
size_t mpack_strlen(const char *s); | |
#endif | |
/* Debug logging */ | |
#if 0 | |
#define mpack_log(...) printf(__VA_ARGS__); | |
#else | |
#define mpack_log(...) ((void)0) | |
#endif | |
/* Make sure our configuration makes sense */ | |
#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 | |
#if MPACK_READ_TRACKING && !defined(MPACK_READER) | |
#error "MPACK_READ_TRACKING requires MPACK_READER." | |
#endif | |
#if MPACK_WRITE_TRACKING && !defined(MPACK_WRITER) | |
#error "MPACK_WRITE_TRACKING requires MPACK_WRITER." | |
#endif | |
#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_ALWAYS_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 | |
/** | |
* @} | |
*/ | |
#ifdef __cplusplus | |
} | |
#endif | |
/** @endcond */ | |
#endif | |
/* mpack-common.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" */ | |
/* Version information */ | |
#define MPACK_VERSION_MAJOR 0 /**< The major version number of MPack. */ | |
#define MPACK_VERSION_MINOR 6 /**< The minor version number of MPack. */ | |
#define MPACK_VERSION_PATCH 0 /**< 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_AMALGAMATED | |
#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 | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
/** | |
* @defgroup common Common Elements | |
* | |
* Contains types and functions shared by both the encoding and decoding | |
* portions of MPack. | |
* | |
* @{ | |
*/ | |
/** | |
* 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_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_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. | |
*/ | |
typedef enum mpack_type_t { | |
mpack_type_nil = 1, /**< A null value. */ | |
mpack_type_bool, /**< A boolean (true or false.) */ | |
mpack_type_float, /**< A 32-bit IEEE 754 floating point number. */ | |
mpack_type_double, /**< A 64-bit IEEE 754 floating point number. */ | |
mpack_type_int, /**< A 64-bit signed integer. */ | |
mpack_type_uint, /**< A 64-bit unsigned integer. */ | |
mpack_type_str, /**< A string. */ | |
mpack_type_bin, /**< A chunk of binary data. */ | |
mpack_type_ext, /**< A typed MessagePack extension object containing 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. */ | |
} 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); | |
/** | |
* An MPack tag is a MessagePack object header. It is a variant type representing | |
* any kind of object, and includes the value of that object when it is not a | |
* compound type (i.e. boolean, integer, float.) | |
* | |
* If the type is compound (str, bin, ext, array or map), the embedded data is | |
* stored separately. | |
*/ | |
typedef struct mpack_tag_t { | |
mpack_type_t type; /**< The type of value. */ | |
int8_t exttype; /**< The extension type if the type is @ref mpack_type_ext. */ | |
/** The value for non-compound types. */ | |
union | |
{ | |
bool b; /**< The value if the type is bool. */ | |
float f; /**< The value if the type is float. */ | |
double d; /**< The value if the type is double. */ | |
int64_t i; /**< The value if the type is signed int. */ | |
uint64_t u; /**< The value if the type is unsigned int. */ | |
uint32_t l; /**< The number of bytes if the type is str, bin or ext. */ | |
/** 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; | |
} mpack_tag_t; | |
/** Generates a nil tag. */ | |
MPACK_INLINE mpack_tag_t mpack_tag_nil(void) { | |
mpack_tag_t ret; | |
mpack_memset(&ret, 0, sizeof(ret)); | |
ret.type = mpack_type_nil; | |
return ret; | |
} | |
/** Generates a signed int tag. */ | |
MPACK_INLINE mpack_tag_t mpack_tag_int(int64_t value) { | |
mpack_tag_t ret; | |
mpack_memset(&ret, 0, sizeof(ret)); | |
ret.type = mpack_type_int; | |
ret.v.i = value; | |
return ret; | |
} | |
/** Generates an unsigned int tag. */ | |
MPACK_INLINE mpack_tag_t mpack_tag_uint(uint64_t value) { | |
mpack_tag_t ret; | |
mpack_memset(&ret, 0, sizeof(ret)); | |
ret.type = mpack_type_uint; | |
ret.v.u = value; | |
return ret; | |
} | |
/** Generates a bool tag. */ | |
MPACK_INLINE mpack_tag_t mpack_tag_bool(bool value) { | |
mpack_tag_t ret; | |
mpack_memset(&ret, 0, sizeof(ret)); | |
ret.type = mpack_type_bool; | |
ret.v.b = value; | |
return ret; | |
} | |
/** Generates a float tag. */ | |
MPACK_INLINE mpack_tag_t mpack_tag_float(float value) { | |
mpack_tag_t ret; | |
mpack_memset(&ret, 0, sizeof(ret)); | |
ret.type = mpack_type_float; | |
ret.v.f = value; | |
return ret; | |
} | |
/** Generates a double tag. */ | |
MPACK_INLINE mpack_tag_t mpack_tag_double(double value) { | |
mpack_tag_t ret; | |
mpack_memset(&ret, 0, sizeof(ret)); | |
ret.type = mpack_type_double; | |
ret.v.d = value; | |
return ret; | |
} | |
/** | |
* 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. | |
* | |
* See mpack_tag_equal() for information on when tags are considered | |
* to be equal. | |
* | |
* The ordering is not guaranteed to be preserved across mpack versions; do not | |
* rely on it in serialized data. | |
*/ | |
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 much match exactly. | |
* | |
* Floating point numbers are compared bit-for-bit, not using the language's operator==. | |
*/ | |
MPACK_INLINE bool mpack_tag_equal(mpack_tag_t left, mpack_tag_t right) { | |
return mpack_tag_cmp(left, right) == 0; | |
} | |
/** | |
* @} | |
*/ | |
/* Helpers for fetching an arbitrarily sized int from a memory | |
* location, regardless of endianness or alignment. */ | |
/** @cond */ | |
MPACK_ALWAYS_INLINE uint8_t mpack_load_native_u8(const char* p) { | |
return (uint8_t)p[0]; | |
} | |
MPACK_ALWAYS_INLINE uint16_t mpack_load_native_u16(const char* p) { | |
return (uint16_t)((((uint16_t)(uint8_t)p[0]) << 8) | | |
((uint16_t)(uint8_t)p[1])); | |
} | |
MPACK_ALWAYS_INLINE uint32_t mpack_load_native_u32(const char* p) { | |
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]); | |
} | |
MPACK_ALWAYS_INLINE uint64_t mpack_load_native_u64(const char* p) { | |
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]); | |
} | |
/** @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; | |
uint64_t left; // we need 64-bit because (2 * INT32_MAX) elements can be stored in a map | |
} 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); | |
// These look like some overly large inline functions, but really | |
// they are mostly asserts. They boil down to just a few checks | |
// and assignments. | |
MPACK_INLINE_SPEED mpack_error_t mpack_track_push(mpack_track_t* track, mpack_type_t type, uint64_t count); | |
MPACK_INLINE_SPEED mpack_error_t mpack_track_pop(mpack_track_t* track, mpack_type_t type); | |
MPACK_INLINE_SPEED mpack_error_t mpack_track_element(mpack_track_t* track, bool read); | |
MPACK_INLINE_SPEED mpack_error_t mpack_track_bytes(mpack_track_t* track, bool read, uint64_t count); | |
MPACK_INLINE_SPEED mpack_error_t mpack_track_check_empty(mpack_track_t* track); | |
MPACK_INLINE_SPEED mpack_error_t mpack_track_destroy(mpack_track_t* track, bool cancel); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED mpack_error_t mpack_track_push(mpack_track_t* track, mpack_type_t type, uint64_t count) { | |
mpack_assert(track->elements, "null track elements!"); | |
// maps have twice the number of elements (key/value pairs) | |
if (type == mpack_type_map) | |
count *= 2; | |
// grow if needed | |
if (track->count == track->capacity) { | |
mpack_error_t error = mpack_track_grow(track); | |
if (error != mpack_ok) | |
return error; | |
} | |
// insert new track | |
track->elements[track->count].type = type; | |
track->elements[track->count].left = count; | |
++track->count; | |
return mpack_ok; | |
} | |
MPACK_INLINE_SPEED mpack_error_t mpack_track_pop(mpack_track_t* track, mpack_type_t type) { | |
mpack_assert(track->elements, "null track elements!"); | |
if (track->count == 0) { | |
mpack_break("attempting to close a %s but nothing was opened!", mpack_type_to_string(type)); | |
return mpack_error_bug; | |
} | |
mpack_track_element_t* element = &track->elements[track->count - 1]; | |
if (element->type != type) { | |
mpack_break("attempting to close a %s but the open element is a %s!", | |
mpack_type_to_string(type), mpack_type_to_string(element->type)); | |
return mpack_error_bug; | |
} | |
if (element->left != 0) { | |
mpack_break("attempting to close a %s but there are %" PRIu64 " %s left", | |
mpack_type_to_string(type), element->left, | |
(type == mpack_type_map || type == mpack_type_array) ? "elements" : "bytes"); | |
return mpack_error_bug; | |
} | |
--track->count; | |
return mpack_ok; | |
} | |
MPACK_INLINE_SPEED mpack_error_t mpack_track_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 elements at will | |
if (track->count == 0) | |
return mpack_ok; | |
mpack_track_element_t* element = &track->elements[track->count - 1]; | |
if (element->type != mpack_type_map && element->type != mpack_type_array) { | |
mpack_break("elements cannot be %s within an %s", read ? "read" : "written", | |
mpack_type_to_string(element->type)); | |
return mpack_error_bug; | |
} | |
if (element->left == 0) { | |
mpack_break("too many elements %s for %s", read ? "read" : "written", | |
mpack_type_to_string(element->type)); | |
return mpack_error_bug; | |
} | |
--element->left; | |
return mpack_ok; | |
} | |
MPACK_INLINE_SPEED mpack_error_t mpack_track_bytes(mpack_track_t* track, bool read, uint64_t count) { | |
MPACK_UNUSED(read); | |
mpack_assert(track->elements, "null track elements!"); | |
if (track->count == 0) { | |
mpack_break("bytes cannot be %s with no open bin, str or ext", read ? "read" : "written"); | |
return mpack_error_bug; | |
} | |
mpack_track_element_t* element = &track->elements[track->count - 1]; | |
if (element->type == mpack_type_map || element->type == mpack_type_array) { | |
mpack_break("bytes cannot be %s within an %s", read ? "read" : "written", | |
mpack_type_to_string(element->type)); | |
return mpack_error_bug; | |
} | |
if (element->left < count) { | |
mpack_break("too many bytes %s for %s", read ? "read" : "written", | |
mpack_type_to_string(element->type)); | |
return mpack_error_bug; | |
} | |
element->left -= count; | |
return mpack_ok; | |
} | |
MPACK_INLINE_SPEED mpack_error_t mpack_track_check_empty(mpack_track_t* track) { | |
if (track->count != 0) { | |
mpack_assert(0, "unclosed %s", mpack_type_to_string(track->elements[0].type)); | |
return mpack_error_bug; | |
} | |
return mpack_ok; | |
} | |
MPACK_INLINE_SPEED mpack_error_t mpack_track_destroy(mpack_track_t* track, bool cancel) { | |
mpack_error_t error = cancel ? mpack_ok : mpack_track_check_empty(track); | |
MPACK_FREE(track->elements); | |
track->elements = NULL; | |
return error; | |
} | |
#endif | |
#endif | |
/** @endcond */ | |
#endif | |
#if MPACK_INTERNAL | |
/* The below code is from Bjoern Hoehrmann's Flexible and Economical */ | |
/* UTF-8 decoder, modified to support MPack inlining and add the mpack prefix. */ | |
/* Copyright (c) 2008-2010 Bjoern Hoehrmann <[email protected]> */ | |
/* See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. */ | |
#define MPACK_UTF8_ACCEPT 0 | |
#define MPACK_UTF8_REJECT 12 | |
MPACK_INLINE_SPEED uint32_t mpack_utf8_decode(uint32_t* state, uint32_t* codep, uint32_t byte); | |
#if MPACK_DEFINE_INLINE_SPEED | |
extern const uint8_t mpack_utf8d[]; | |
MPACK_INLINE_SPEED uint32_t mpack_utf8_decode(uint32_t* state, uint32_t* codep, uint32_t byte) { | |
uint32_t type = mpack_utf8d[byte]; | |
*codep = (*state != MPACK_UTF8_ACCEPT) ? | |
(byte & 0x3fu) | (*codep << 6) : | |
(0xff >> type) & (byte); | |
*state = mpack_utf8d[256 + *state + type]; | |
return *state; | |
} | |
#endif | |
#endif | |
#ifdef __cplusplus | |
} | |
#endif | |
#endif | |
/* mpack-writer.h */ | |
/** | |
* @file | |
* | |
* Declares the MPack Writer. | |
*/ | |
#ifndef MPACK_WRITER_H | |
#define MPACK_WRITER_H 1 | |
/* #include "mpack-common.h" */ | |
#if MPACK_WRITER | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
#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. | |
* | |
* @{ | |
*/ | |
/** | |
* 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.) | |
* | |
* 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++ | |
* 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); | |
struct mpack_writer_t { | |
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 */ | |
size_t size; /* Size of the buffer */ | |
size_t used; /* How many bytes have been written into 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 | |
}; | |
/** | |
* @name Core Writer 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_io 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 mpack 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 should be freed with MPACK_FREE(). | |
* The data pointer is NULL during writing, and will remain NULL | |
* if an error occurs. | |
* | |
* mpack_error_memory is raised if the buffer fails to grow. | |
* | |
* @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. | |
*/ | |
void mpack_writer_init_file(mpack_writer_t* writer, const char* filename); | |
#endif | |
/** | |
* @def mpack_writer_init_stack(writer, flush, context) | |
* @hideinitializer | |
* | |
* Initializes an mpack writer using stack space as a buffer. A flush function | |
* should be added to the writer to flush the buffer. | |
*/ | |
#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) | |
#define mpack_writer_init_stack(writer) \ | |
mpack_writer_init_stack_line(__LINE__, (writer)) | |
/** | |
* Cleans up the mpack writer, flushing any buffered bytes to the | |
* underlying stream, if any. Returns the final error state of the | |
* writer in case an error occurred flushing. Causes an assert if | |
* there are any unclosed compound types in tracking mode. | |
* | |
* Note that if a jump handler is set, a writer may jump during destroy if it | |
* fails to flush any remaining data. In this case the writer will not be fully | |
* destroyed; you can still get the error state, and you must call destroy as | |
* usual in the jump handler. | |
*/ | |
mpack_error_t mpack_writer_destroy(mpack_writer_t* writer); | |
/** | |
* Cleans up the mpack writer, discarding any open writes and unflushed data. | |
* | |
* Use this to cancel writing in the middle of writing a document (for example | |
* in case an error occurred.) This should be used instead of mpack_writer_destroy() | |
* because the former will assert in tracking mode if there are any unclosed | |
* compound types. | |
*/ | |
void mpack_writer_destroy_cancel(mpack_writer_t* writer); | |
/** | |
* 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. | |
*/ | |
MPACK_INLINE void mpack_writer_set_context(mpack_writer_t* writer, void* context) { | |
writer->context = 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_io. | |
* | |
* 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. | |
*/ | |
MPACK_INLINE void mpack_writer_set_flush(mpack_writer_t* writer, mpack_writer_flush_t flush) { | |
mpack_assert(writer->size != 0, "cannot use flush function without a writeable buffer!"); | |
writer->flush = 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 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; | |
} | |
/** | |
* 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 writer->used; | |
} | |
/** | |
* Places the writer in the given error state, jumping if a jump target is set. | |
* | |
* This allows you to externally flag errors, for example if you are validating | |
* data as you read it. | |
* | |
* If the writer is already in an error state, this call is ignored and no jump | |
* is performed. | |
*/ | |
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.) | |
*/ | |
void mpack_write_tag(mpack_writer_t* writer, mpack_tag_t tag); | |
/** | |
* @} | |
*/ | |
/** | |
* @name Typed Write Functions | |
* @{ | |
*/ | |
/*! 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); | |
} | |
/*! Writes a float. */ | |
void mpack_write_float(mpack_writer_t* writer, float value); | |
/*! Writes a double. */ | |
void mpack_write_double(mpack_writer_t* writer, double value); | |
/*! 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); | |
/** | |
* 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. | |
*/ | |
void mpack_write_str(mpack_writer_t* writer, const char* str, uint32_t length); | |
/** | |
* Writes a binary blob. | |
* | |
* To stream a binary blob in chunks, use mpack_start_bin() instead. | |
*/ | |
void mpack_write_bin(mpack_writer_t* writer, const char* data, uint32_t count); | |
/** | |
* 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. | |
*/ | |
void mpack_write_ext(mpack_writer_t* writer, int8_t exttype, const char* data, uint32_t count); | |
/** | |
* Opens an array. count elements should follow, and mpack_finish_array() | |
* should be called when done. | |
*/ | |
void mpack_start_array(mpack_writer_t* writer, uint32_t count); | |
/** | |
* Opens a map. count*2 elements should follow, and mpack_finish_map() | |
* should be called when done. | |
* | |
* 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. | |
*/ | |
void mpack_start_map(mpack_writer_t* writer, uint32_t count); | |
/** | |
* 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); | |
/** | |
* 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. | |
*/ | |
void mpack_start_ext(mpack_writer_t* writer, int8_t exttype, uint32_t count); | |
/** | |
* Writes a portion of bytes for a string, binary blob or extension type which | |
* was opened by one of the mpack_start_*() functions. The corresponding | |
* mpack_finish_*() function should 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_start_str() | |
* @see mpack_start_bin() | |
* @see mpack_start_ext() | |
* @see mpack_finish_str() | |
* @see mpack_finish_bin() | |
* @see mpack_finish_ext() | |
* @see mpack_write_str() | |
* @see mpack_write_bin() | |
* @see mpack_write_ext() | |
*/ | |
void mpack_write_bytes(mpack_writer_t* writer, const char* data, size_t count); | |
#if MPACK_WRITE_TRACKING | |
/** | |
* Finishes writing an array. | |
* | |
* This will track writes to ensure that the correct number of elements are written. | |
*/ | |
void mpack_finish_array(mpack_writer_t* writer); | |
/** | |
* Finishes writing a map. | |
* | |
* This will track writes to ensure that the correct number of elements are written. | |
*/ | |
void mpack_finish_map(mpack_writer_t* writer); | |
/** | |
* Finishes writing a string. | |
* | |
* This will track writes to ensure that the correct number of bytes are written. | |
*/ | |
void mpack_finish_str(mpack_writer_t* writer); | |
/** | |
* Finishes writing a binary blob. | |
* | |
* This will track writes to ensure that the correct number of bytes are written. | |
*/ | |
void mpack_finish_bin(mpack_writer_t* writer); | |
/** | |
* Finishes writing an extended type binary data blob. | |
* | |
* This will track writes to ensure that the correct number of bytes are written. | |
*/ | |
void mpack_finish_ext(mpack_writer_t* writer); | |
/** | |
* Finishes writing the given compound type. | |
* | |
* This will track writes to ensure that the correct number of elements | |
* or bytes are written. | |
*/ | |
void mpack_finish_type(mpack_writer_t* writer, mpack_type_t type); | |
#else | |
MPACK_INLINE void mpack_finish_array(mpack_writer_t* writer) {MPACK_UNUSED(writer);} | |
MPACK_INLINE void mpack_finish_map(mpack_writer_t* writer) {MPACK_UNUSED(writer);} | |
MPACK_INLINE void mpack_finish_str(mpack_writer_t* writer) {MPACK_UNUSED(writer);} | |
MPACK_INLINE void mpack_finish_bin(mpack_writer_t* writer) {MPACK_UNUSED(writer);} | |
MPACK_INLINE void mpack_finish_ext(mpack_writer_t* writer) {MPACK_UNUSED(writer);} | |
MPACK_INLINE void mpack_finish_type(mpack_writer_t* writer, mpack_type_t type) {MPACK_UNUSED(writer); MPACK_UNUSED(type);} | |
#endif | |
/** | |
* 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. | |
*/ | |
void mpack_write_cstr(mpack_writer_t* writer, const char* str); | |
/** | |
* @} | |
*/ | |
/** | |
* @} | |
*/ | |
#ifdef __cplusplus | |
} | |
#endif | |
#endif | |
#endif | |
/* mpack-reader.h */ | |
/** | |
* @file | |
* | |
* Declares the core MPack Tag Reader. | |
*/ | |
#ifndef MPACK_READER_H | |
#define MPACK_READER_H 1 | |
/* #include "mpack-common.h" */ | |
#if MPACK_READER | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
#if MPACK_READ_TRACKING | |
struct mpack_track_t; | |
#endif | |
/** | |
* @defgroup reader Core Reader API | |
* | |
* The MPack Core Reader API contains functions for imperatively reading | |
* dynamically typed data from a MessagePack stream. This forms the basis | |
* of the Expect and Node APIs. | |
* | |
* @{ | |
*/ | |
/** | |
* 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 as | |
* much as possible, returning the number of bytes put into the buffer. | |
* | |
* In case of error, it should flag an appropriate error on the reader. | |
*/ | |
typedef size_t (*mpack_reader_fill_t)(mpack_reader_t* reader, char* buffer, 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++ | |
* 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); | |
struct mpack_reader_t { | |
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 */ | |
void* context; /* Context for reader callbacks */ | |
char* buffer; /* Byte buffer */ | |
size_t size; /* Size of the buffer, or zero if it's const */ | |
size_t left; /* How many bytes are left in the buffer */ | |
size_t pos; /* Position within the buffer */ | |
mpack_error_t error; /* Error state */ | |
#if MPACK_READ_TRACKING | |
mpack_track_t track; /* Stack of map/array/str/bin/ext reads */ | |
#endif | |
}; | |
/** | |
* 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 mpack 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. | |
*/ | |
void mpack_reader_init_file(mpack_reader_t* reader, const char* filename); | |
#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 has any incomplete | |
* reads. If you want to cancel reading in the middle of a compound | |
* element and don't care about the rest of the document, call | |
* mpack_reader_destroy_cancel() instead. | |
* | |
* @see mpack_reader_destroy_cancel() | |
*/ | |
mpack_error_t mpack_reader_destroy(mpack_reader_t* reader); | |
/** | |
* Cleans up the mpack reader, discarding any open reads. | |
* | |
* This should be used if you decide to cancel reading in the middle | |
* of the document. | |
*/ | |
void mpack_reader_destroy_cancel(mpack_reader_t* reader); | |
/** | |
* 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. | |
*/ | |
MPACK_INLINE void mpack_reader_set_context(mpack_reader_t* reader, void* context) { | |
reader->context = context; | |
} | |
/** | |
* Sets the fill function to refill the data buffer when it runs out of data. | |
* | |
* If no fill function is used, trying to read past the end of the | |
* buffer will result in mpack_error_io. | |
* | |
* 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. | |
*/ | |
MPACK_INLINE void mpack_reader_set_fill(mpack_reader_t* reader, mpack_reader_fill_t fill) { | |
mpack_assert(reader->size != 0, "cannot use fill function without a writeable buffer!"); | |
reader->fill = fill; | |
} | |
/** | |
* 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 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; | |
} | |
/** | |
* 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, jumping if a jump target 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 jump | |
* is performed. | |
*/ | |
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_SPEED mpack_error_t mpack_reader_flag_if_error(mpack_reader_t* reader, mpack_error_t error); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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); | |
} | |
#endif | |
/** | |
* 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. | |
* | |
* @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 mpack_reader_t 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 actual data, | |
* and the corresponding done function (or cancel) should be called when | |
* done. | |
* | |
* Note that 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() | |
* @see mpack_cancel() | |
*/ | |
mpack_tag_t mpack_read_tag(mpack_reader_t* reader); | |
/** | |
* 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. | |
*/ | |
void mpack_read_bytes(mpack_reader_t* reader, char* p, size_t count); | |
/** | |
* Reads bytes from a string, binary blob or extension object in-place in | |
* the buffer. This can be used to avoid copying the data. | |
* | |
* The returned pointer is invalidated the next time the reader's fill | |
* function is called, or when the buffer is destroyed. | |
* | |
* The size requested must be at most the buffer size. If the requested size is | |
* larger than the buffer size, mpack_error_too_big is raised and the | |
* return value is undefined. | |
* | |
* The reader will move data around in the buffer if needed to ensure that | |
* the pointer can always be returned, so it is unlikely to be faster unless | |
* 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(). | |
* | |
* As with all read functions, the return value is undefined if the reader | |
* is in an error state. | |
* | |
* @see mpack_should_read_bytes_inplace() | |
*/ | |
const char* mpack_read_bytes_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 where possible but will fall back to a normal read if the data | |
* is too large. | |
* | |
* @see mpack_read_bytes_inplace() | |
*/ | |
MPACK_INLINE_SPEED bool mpack_should_read_bytes_inplace(mpack_reader_t* reader, size_t count); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED bool mpack_should_read_bytes_inplace(mpack_reader_t* reader, size_t count) { | |
return (reader->size == 0 || count > reader->size / 8); | |
} | |
#endif | |
#if MPACK_READ_TRACKING | |
/** | |
* Finishes reading an array. | |
* | |
* This will track reads to ensure that the correct number of elements are read. | |
*/ | |
void mpack_done_array(mpack_reader_t* reader); | |
/** | |
* @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. | |
*/ | |
void mpack_done_map(mpack_reader_t* reader); | |
/** | |
* @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. | |
*/ | |
void mpack_done_str(mpack_reader_t* reader); | |
/** | |
* @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. | |
*/ | |
void mpack_done_bin(mpack_reader_t* reader); | |
/** | |
* @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. | |
*/ | |
void mpack_done_ext(mpack_reader_t* reader); | |
/** | |
* 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_array(mpack_reader_t* reader) {MPACK_UNUSED(reader);} | |
MPACK_INLINE void mpack_done_map(mpack_reader_t* reader) {MPACK_UNUSED(reader);} | |
MPACK_INLINE void mpack_done_str(mpack_reader_t* reader) {MPACK_UNUSED(reader);} | |
MPACK_INLINE void mpack_done_bin(mpack_reader_t* reader) {MPACK_UNUSED(reader);} | |
MPACK_INLINE void mpack_done_ext(mpack_reader_t* reader) {MPACK_UNUSED(reader);} | |
MPACK_INLINE void mpack_done_type(mpack_reader_t* reader, mpack_type_t type) {MPACK_UNUSED(reader); MPACK_UNUSED(type);} | |
#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); | |
#if MPACK_DEBUG && MPACK_STDIO && !MPACK_NO_PRINT | |
/*! Converts a chunk of messagepack to JSON and pretty-prints it to stdout. */ | |
void mpack_debug_print(const char* data, int len); | |
#endif | |
/** | |
* @} | |
*/ | |
#if MPACK_INTERNAL | |
void mpack_read_native_big(mpack_reader_t* reader, char* p, size_t count); | |
// Reads count bytes into p, deferring to mpack_read_native_big() if more | |
// bytes are needed than are available in the buffer. | |
MPACK_INLINE_SPEED void mpack_read_native(mpack_reader_t* reader, char* p, size_t count); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED void mpack_read_native(mpack_reader_t* reader, char* p, size_t count) { | |
if (count > reader->left) { | |
mpack_read_native_big(reader, p, count); | |
} else { | |
mpack_memcpy(p, reader->buffer + reader->pos, count); | |
reader->pos += count; | |
reader->left -= count; | |
} | |
} | |
#endif | |
// 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 out of an error handler. | |
MPACK_INLINE_SPEED void mpack_read_native_nojump(mpack_reader_t* reader, char* p, size_t count); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED void mpack_read_native_nojump(mpack_reader_t* reader, char* p, size_t count) { | |
mpack_reader_error_t error_fn = reader->error_fn; | |
reader->error_fn = NULL; | |
mpack_read_native(reader, p, count); | |
reader->error_fn = error_fn; | |
} | |
#endif | |
MPACK_ALWAYS_INLINE uint8_t mpack_read_native_u8(mpack_reader_t* reader) { | |
if (reader->left >= sizeof(uint8_t)) { | |
uint8_t ret = mpack_load_native_u8(reader->buffer + reader->pos); | |
reader->pos += sizeof(uint8_t); | |
reader->left -= sizeof(uint8_t); | |
return ret; | |
} | |
char c[sizeof(uint8_t)]; | |
mpack_read_native_big(reader, c, sizeof(c)); | |
return mpack_load_native_u8(c); | |
} | |
MPACK_ALWAYS_INLINE uint16_t mpack_read_native_u16(mpack_reader_t* reader) { | |
if (reader->left >= sizeof(uint16_t)) { | |
uint16_t ret = mpack_load_native_u16(reader->buffer + reader->pos); | |
reader->pos += sizeof(uint16_t); | |
reader->left -= sizeof(uint16_t); | |
return ret; | |
} | |
char c[sizeof(uint16_t)]; | |
mpack_read_native_big(reader, c, sizeof(c)); | |
return mpack_load_native_u16(c); | |
} | |
MPACK_ALWAYS_INLINE uint32_t mpack_read_native_u32(mpack_reader_t* reader) { | |
if (reader->left >= sizeof(uint32_t)) { | |
uint32_t ret = mpack_load_native_u32(reader->buffer + reader->pos); | |
reader->pos += sizeof(uint32_t); | |
reader->left -= sizeof(uint32_t); | |
return ret; | |
} | |
char c[sizeof(uint32_t)]; | |
mpack_read_native_big(reader, c, sizeof(c)); | |
return mpack_load_native_u32(c); | |
} | |
MPACK_ALWAYS_INLINE uint64_t mpack_read_native_u64(mpack_reader_t* reader) { | |
if (reader->left >= sizeof(uint64_t)) { | |
uint64_t ret = mpack_load_native_u64(reader->buffer + reader->pos); | |
reader->pos += sizeof(uint64_t); | |
reader->left -= sizeof(uint64_t); | |
return ret; | |
} | |
char c[sizeof(uint64_t)]; | |
mpack_read_native_big(reader, c, sizeof(c)); | |
return mpack_load_native_u64(c); | |
} | |
MPACK_ALWAYS_INLINE int8_t mpack_read_native_i8 (mpack_reader_t* reader) {return (int8_t) mpack_read_native_u8 (reader);} | |
MPACK_ALWAYS_INLINE int16_t mpack_read_native_i16 (mpack_reader_t* reader) {return (int16_t)mpack_read_native_u16 (reader);} | |
MPACK_ALWAYS_INLINE int32_t mpack_read_native_i32 (mpack_reader_t* reader) {return (int32_t)mpack_read_native_u32 (reader);} | |
MPACK_ALWAYS_INLINE int64_t mpack_read_native_i64 (mpack_reader_t* reader) {return (int64_t)mpack_read_native_u64 (reader);} | |
MPACK_ALWAYS_INLINE float mpack_read_native_float(mpack_reader_t* reader) { | |
union { | |
float f; | |
uint32_t i; | |
} u; | |
u.i = mpack_read_native_u32(reader); | |
return u.f; | |
} | |
MPACK_ALWAYS_INLINE double mpack_read_native_double(mpack_reader_t* reader) { | |
union { | |
double d; | |
uint64_t i; | |
} u; | |
u.i = mpack_read_native_u64(reader); | |
return u.d; | |
} | |
#if MPACK_READ_TRACKING | |
#define MPACK_READER_TRACK(reader, error) mpack_reader_flag_if_error((reader), (error)) | |
#else | |
#define MPACK_READER_TRACK(reader, error) (MPACK_UNUSED(reader), mpack_ok) | |
#endif | |
MPACK_INLINE_SPEED mpack_error_t mpack_reader_track_element(mpack_reader_t* reader); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED mpack_error_t mpack_reader_track_element(mpack_reader_t* reader) { | |
return MPACK_READER_TRACK(reader, mpack_track_element(&reader->track, true)); | |
} | |
#endif | |
MPACK_INLINE_SPEED mpack_error_t mpack_reader_track_bytes(mpack_reader_t* reader, uint64_t count); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED mpack_error_t mpack_reader_track_bytes(mpack_reader_t* reader, uint64_t count) { | |
MPACK_UNUSED(count); | |
return MPACK_READER_TRACK(reader, mpack_track_bytes(&reader->track, true, count)); | |
} | |
#endif | |
#endif | |
#ifdef __cplusplus | |
} | |
#endif | |
#endif | |
#endif | |
/* mpack-expect.h */ | |
/** | |
* @file | |
* | |
* Declares the MPack static Expect API. | |
*/ | |
#ifndef MPACK_EXPECT_H | |
#define MPACK_EXPECT_H 1 | |
/* #include "mpack-reader.h" */ | |
#if MPACK_EXPECT | |
#if !MPACK_READER | |
#error "MPACK_EXPECT requires MPACK_READER." | |
#endif | |
#ifdef __cplusplus | |
extern "C" { | |
#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. | |
* | |
* The main purpose of the Expect API is convenience, so the API is lax. It | |
* allows overlong / inefficiently encoded sequences, and it automatically | |
* converts between similar types where there is no loss of precision (unless | |
* otherwise noted.) It will convert from unsigned to signed or from float to | |
* double for example. | |
* | |
* 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); | |
/** | |
* 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 that 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); | |
/** | |
* 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 that 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); | |
/** | |
* 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); | |
/** | |
* 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); | |
/** | |
* @} | |
*/ | |
/** | |
* @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); | |
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); | |
} | |
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); | |
} | |
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); | |
} | |
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 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 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 that 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); | |
/** | |
* 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 that 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); | |
/** | |
* @} | |
*/ | |
/** | |
* @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. | |
*/ | |
void mpack_expect_nil(mpack_reader_t* reader); | |
/** | |
* Reads a bool. Note that integers will raise mpack_error_type; the value | |
* must be strictly a bool. | |
*/ | |
bool mpack_expect_bool(mpack_reader_t* reader); | |
/** | |
* Reads a bool, raising a mpack_error_type if it is not true. | |
*/ | |
void mpack_expect_true(mpack_reader_t* reader); | |
/** | |
* Reads a bool, raising a mpack_error_type if it is not false. | |
*/ | |
void mpack_expect_false(mpack_reader_t* reader); | |
/** | |
* @} | |
*/ | |
/** | |
* @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. | |
* | |
* mpack_error_type is raised if the value is not a map. | |
* | |
* Note that 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. | |
*/ | |
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. | |
* | |
* mpack_error_type is raised if the value is not a map or if its size does | |
* not fall within the given range. | |
* | |
* Note that 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. | |
*/ | |
uint32_t mpack_expect_map_range(mpack_reader_t* reader, uint32_t min_count, uint32_t 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. | |
* | |
* @ref mpack_error_type is raised if the value is not a map or if its size | |
* does not match the given count. | |
* | |
* Note that 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. | |
*/ | |
void mpack_expect_map_match(mpack_reader_t* reader, uint32_t count); | |
bool mpack_expect_map_or_nil(mpack_reader_t* reader, 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. | |
*/ | |
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. | |
* | |
* @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); | |
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 fall within the given range. | |
*/ | |
void mpack_expect_array_match(mpack_reader_t* reader, uint32_t count); | |
#ifdef MPACK_MALLOC | |
/** | |
* @hideinitializer | |
* | |
* Reads the start of an array and allocates storage for it, placing its | |
* size in count. A number of objects follow equal to the element count | |
* of the array. | |
*/ | |
#define mpack_expect_array_alloc(reader, Type, max_count, count) \ | |
((Type*)mpack_expect_array_alloc_impl(reader, sizeof(Type), max_count, count)) | |
#endif | |
/** | |
* @} | |
*/ | |
/** @cond */ | |
#ifdef MPACK_MALLOC | |
void* mpack_expect_array_alloc_impl(mpack_reader_t* reader, | |
size_t element_size, uint32_t max_count, size_t* count); | |
#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(). @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. | |
*/ | |
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. | |
* | |
* Note that this does not add a null-terminator! No null-terminator | |
* is written, even if the string fits. Use mpack_expect_cstr() to | |
* get a null-terminator. | |
*/ | |
size_t mpack_expect_str_buf(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. | |
* | |
* mpack_error_type is raised if the value is not a string or if its | |
* length does not match. | |
*/ | |
MPACK_INLINE_SPEED void mpack_expect_str_max(mpack_reader_t* reader, uint32_t maxsize); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED void mpack_expect_str_max(mpack_reader_t* reader, uint32_t maxsize) { | |
if (mpack_expect_str(reader) > maxsize) | |
mpack_reader_flag_error(reader, mpack_error_type); | |
} | |
#endif | |
/** | |
* 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_SPEED void mpack_expect_str_length(mpack_reader_t* reader, uint32_t count); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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); | |
} | |
#endif | |
/** | |
* Reads a string with the given total maximum size, allocating storage for it. | |
* A null-terminator will be added to the string. The length in bytes of the string, | |
* not including the null-terminator, will be written to size. | |
*/ | |
char* mpack_expect_str_alloc(mpack_reader_t* reader, size_t maxsize, size_t* size); | |
/** | |
* Reads a string with the given total maximum size, allocating storage for it | |
* and ensuring it is valid UTF-8. A null-terminator will be added to the string. | |
* The length in bytes of the string, not including the null-terminator, | |
* will be written to size. | |
*/ | |
char* mpack_expect_utf8_alloc(mpack_reader_t* reader, size_t maxsize, size_t* size); | |
/** | |
* Reads a string into the given buffer, ensures it has no null-bytes, | |
* and adds 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, | |
* and adds 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 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, allocates storage for it, ensures it has no null-bytes, | |
* and adds null-terminator at the end. You assume ownership of the | |
* returned pointer if reading succeeds. | |
* | |
* Raises mpack_error_too_big if the string plus null-terminator is larger than the given maxsize. | |
* Raises mpack_error_invalid if the value is not a string or contains a null byte. | |
*/ | |
char* mpack_expect_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! | |
*/ | |
void mpack_expect_cstr_match(mpack_reader_t* reader, const char* str); | |
/** | |
* @} | |
*/ | |
/** | |
* @name Binary Data / Extension Functions | |
* @{ | |
*/ | |
/** | |
* 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_SPEED void mpack_expect_bin_max(mpack_reader_t* reader, uint32_t maxsize); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED void mpack_expect_bin_max(mpack_reader_t* reader, uint32_t maxsize) { | |
if (mpack_expect_str(reader) > maxsize) | |
mpack_reader_flag_error(reader, mpack_error_type); | |
} | |
#endif | |
/** | |
* 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. | |
* | |
* mpack_error_type is raised if the value is not a binary blob or if its | |
* length does not match. | |
*/ | |
MPACK_INLINE_SPEED void mpack_expect_bin_size(mpack_reader_t* reader, uint32_t count); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED void mpack_expect_bin_size(mpack_reader_t* reader, uint32_t count) { | |
if (mpack_expect_str(reader) != count) | |
mpack_reader_flag_error(reader, mpack_error_type); | |
} | |
#endif | |
/** | |
* 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); | |
const char* mpack_expect_bin_inplace(mpack_reader_t* reader, size_t maxsize, size_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); | |
/** | |
* Reads an extension object with the given total maximum size, allocating storage | |
* for it. The extension type will be written to exttype, and the size will be | |
* written to size. | |
*/ | |
char* mpack_expect_ext_alloc(mpack_reader_t* reader, size_t maxsize, uint8_t* exttype, size_t* size); | |
/** | |
* Reads an extension object of the given type with the given total maximum size, | |
* allocating storage for it. The size will be written to size. | |
*/ | |
char* mpack_expect_ext_type_alloc(mpack_reader_t* reader, uint8_t exttype, size_t maxsize, size_t* size); | |
/** | |
* @} | |
*/ | |
/** | |
* @} | |
*/ | |
#ifdef __cplusplus | |
} | |
#endif | |
#endif | |
#endif | |
/* mpack-node.h */ | |
/** | |
* @file | |
* | |
* Declares the MPack dynamic Node API. | |
*/ | |
#ifndef MPACK_NODE_H | |
#define MPACK_NODE_H 1 | |
/* #include "mpack-reader.h" */ | |
#if MPACK_NODE | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
/** | |
* @defgroup node Node API | |
* | |
* The MPack Node API allows you to parse a chunk of MessagePack data | |
* in-place into a dynamically typed data structure. | |
* | |
* @{ | |
*/ | |
/** | |
* A handle to node in a parsed MPack tree. Note that mpack_node_t is passed by value. | |
* | |
* Nodes represent either primitive values or compound types. If a | |
* node is a compound type, it contains a link to its child nodes, or | |
* a pointer to its underlying data. | |
* | |
* Nodes are immutable. | |
*/ | |
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. | |
*/ | |
typedef struct mpack_node_data_t mpack_node_data_t; | |
/** | |
* An MPack tree parsed from a blob of MessagePack. | |
* | |
* 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++ | |
* 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); | |
/** | |
* 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 */ | |
/* | |
* mpack_tree_link_t forms a linked list of node pages. It is allocated | |
* separately from the page so that we can store the first link internally | |
* without a malloc (the only link in a pooled tree), and we don't | |
* affect the size of page pools or violate strict aliasing. | |
*/ | |
typedef struct mpack_tree_link_t { | |
struct mpack_tree_link_t* next; | |
mpack_node_data_t* nodes; | |
size_t pos; | |
size_t left; | |
} mpack_tree_link_t; | |
struct mpack_node_t { | |
mpack_node_data_t* data; | |
mpack_tree_t* tree; | |
}; | |
struct mpack_node_data_t { | |
mpack_type_t type; | |
int8_t exttype; /**< \internal The extension type if the type is mpack_type_ext. */ | |
/* The value for non-compound types. */ | |
union | |
{ | |
bool b; /* The value if the type is bool. */ | |
float f; /* The value if the type is float. */ | |
double d; /* The value if the type is double. */ | |
int64_t i; /* The value if the type is signed int. */ | |
uint64_t u; /* The value if the type is unsigned int. */ | |
struct { | |
uint32_t l; /* The number of bytes if the type is str, bin or ext. */ | |
const char* bytes; | |
} data; | |
struct { | |
/* The element count if the type is an array, or the number of | |
key/value pairs if the type is map. */ | |
uint32_t n; | |
mpack_node_data_t* children; | |
} content; | |
} value; | |
}; | |
struct mpack_tree_t { | |
mpack_tree_error_t error_fn; /* Function to call on error */ | |
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_error_t error; | |
size_t node_count; | |
size_t size; | |
mpack_node_data_t* root; | |
mpack_tree_link_t page; | |
#ifdef MPACK_MALLOC | |
bool owned; | |
#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.content.children + child; | |
} | |
MPACK_INLINE mpack_node_t mpack_tree_nil_node(mpack_tree_t* tree) { | |
return mpack_node(tree, &tree->nil_node); | |
} | |
/** @endcond */ | |
/** | |
* @name Tree Functions | |
* @{ | |
*/ | |
#ifdef MPACK_MALLOC | |
/** | |
* Initializes a tree by parsing the given data buffer. The tree must be destroyed | |
* with mpack_tree_destroy(), even if parsing fails. | |
* | |
* The tree will allocate pages of nodes as needed, and free them when destroyed. | |
* | |
* Any string or blob data types reference the original data, so the data | |
* pointer must remain valid until after the tree is destroyed. | |
*/ | |
void mpack_tree_init(mpack_tree_t* tree, const char* data, size_t length); | |
#endif | |
/** | |
* Initializes a tree by parsing the given data buffer, using the given | |
* node data pool to store the results. | |
* | |
* If the data does not fit in the pool, 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 mpack_tree_init() which can fail its setup. | |
*/ | |
void mpack_tree_init_error(mpack_tree_t* tree, mpack_error_t error); | |
#if MPACK_STDIO | |
/** | |
* Initializes a tree by reading and parsing 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_file(mpack_tree_t* tree, const char* filename, size_t max_bytes); | |
#endif | |
/** | |
* Returns the root node of the tree, if the tree is not in an error state. | |
* Returns a nil node otherwise. | |
*/ | |
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 number of bytes used in the buffer when the tree was | |
* parsed. If there is something in the buffer after the MessagePack | |
* object (such as another object), this can be used to find it. | |
*/ | |
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. | |
*/ | |
MPACK_INLINE void mpack_tree_set_context(mpack_tree_t* tree, void* context) { | |
tree->context = 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 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, jumping if a jump target 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 jump | |
* is performed. | |
*/ | |
void mpack_tree_flag_error(mpack_tree_t* tree, mpack_error_t error); | |
/** | |
* Places the node's tree in the given error state, jumping if a jump target 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 jump | |
* is performed. | |
*/ | |
void mpack_node_flag_error(mpack_node_t node, mpack_error_t error); | |
/** | |
* @} | |
*/ | |
/** | |
* @name Node Core Functions | |
* @{ | |
*/ | |
/** | |
* 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. | |
*/ | |
mpack_tag_t mpack_node_tag(mpack_node_t node); | |
#if MPACK_DEBUG && MPACK_STDIO && !MPACK_NO_PRINT | |
/** | |
* Converts a node to JSON and pretty-prints it to stdout. | |
* | |
* This function is only available in debugging mode. | |
*/ | |
void mpack_node_print(mpack_node_t node); | |
#endif | |
/** | |
* @} | |
*/ | |
/** | |
* @name Node Primitive Value Functions | |
* @{ | |
*/ | |
/** | |
* Returns the type of the node. | |
*/ | |
MPACK_INLINE_SPEED mpack_type_t mpack_node_type(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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; | |
} | |
#endif | |
/** | |
* Checks if the given node is of nil type, raising mpack_error_type otherwise. | |
*/ | |
MPACK_INLINE_SPEED void mpack_node_nil(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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); | |
} | |
#endif | |
/** | |
* Returns the bool value of the node. If this node is not of the correct | |
* type, mpack_error_type is raised, and the return value should be discarded. | |
*/ | |
MPACK_INLINE_SPEED bool mpack_node_bool(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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; | |
} | |
#endif | |
/** | |
* Checks if the given node is of bool type with value true, raising | |
* mpack_error_type otherwise. | |
*/ | |
MPACK_INLINE_SPEED void mpack_node_true(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED void mpack_node_true(mpack_node_t node) { | |
if (mpack_node_bool(node) != true) | |
mpack_node_flag_error(node, mpack_error_type); | |
} | |
#endif | |
/** | |
* Checks if the given node is of bool type with value false, raising | |
* mpack_error_type otherwise. | |
*/ | |
MPACK_INLINE_SPEED void mpack_node_false(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED void mpack_node_false(mpack_node_t node) { | |
if (mpack_node_bool(node) != false) | |
mpack_node_flag_error(node, mpack_error_type); | |
} | |
#endif | |
/** | |
* Returns the 8-bit unsigned value of the node. If this node is not | |
* of a compatible type, mpack_error_type is raised, and the | |
* return value should be discarded. | |
*/ | |
MPACK_INLINE_SPEED uint8_t mpack_node_u8(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED uint8_t mpack_node_u8(mpack_node_t node) { | |
if (mpack_node_error(node) != mpack_ok) | |
return 0; | |
if (node.data->type == mpack_type_uint) { | |
if (node.data->value.u <= UINT8_MAX) | |
return (uint8_t)node.data->value.u; | |
} else if (node.data->type == mpack_type_int) { | |
if (node.data->value.i >= 0 && node.data->value.i <= UINT8_MAX) | |
return (uint8_t)node.data->value.i; | |
} | |
mpack_node_flag_error(node, mpack_error_type); | |
return 0; | |
} | |
#endif | |
/** | |
* Returns the 8-bit signed value of the node. If this node is not | |
* of a compatible type, mpack_error_type is raised, and the | |
* return value should be discarded. | |
*/ | |
MPACK_INLINE_SPEED int8_t mpack_node_i8(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED int8_t mpack_node_i8(mpack_node_t node) { | |
if (mpack_node_error(node) != mpack_ok) | |
return 0; | |
if (node.data->type == mpack_type_uint) { | |
if (node.data->value.u <= INT8_MAX) | |
return (int8_t)node.data->value.u; | |
} else if (node.data->type == mpack_type_int) { | |
if (node.data->value.i >= INT8_MIN && node.data->value.i <= INT8_MAX) | |
return (int8_t)node.data->value.i; | |
} | |
mpack_node_flag_error(node, mpack_error_type); | |
return 0; | |
} | |
#endif | |
/** | |
* Returns the 16-bit unsigned value of the node. If this node is not | |
* of a compatible type, mpack_error_type is raised, and the | |
* return value should be discarded. | |
*/ | |
MPACK_INLINE_SPEED uint16_t mpack_node_u16(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED uint16_t mpack_node_u16(mpack_node_t node) { | |
if (mpack_node_error(node) != mpack_ok) | |
return 0; | |
if (node.data->type == mpack_type_uint) { | |
if (node.data->value.u <= UINT16_MAX) | |
return (uint16_t)node.data->value.u; | |
} else if (node.data->type == mpack_type_int) { | |
if (node.data->value.i >= 0 && node.data->value.i <= UINT16_MAX) | |
return (uint16_t)node.data->value.i; | |
} | |
mpack_node_flag_error(node, mpack_error_type); | |
return 0; | |
} | |
#endif | |
/** | |
* Returns the 16-bit signed value of the node. If this node is not | |
* of a compatible type, mpack_error_type is raised, and the | |
* return value should be discarded. | |
*/ | |
MPACK_INLINE_SPEED int16_t mpack_node_i16(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED int16_t mpack_node_i16(mpack_node_t node) { | |
if (mpack_node_error(node) != mpack_ok) | |
return 0; | |
if (node.data->type == mpack_type_uint) { | |
if (node.data->value.u <= INT16_MAX) | |
return (int16_t)node.data->value.u; | |
} else if (node.data->type == mpack_type_int) { | |
if (node.data->value.i >= INT16_MIN && node.data->value.i <= INT16_MAX) | |
return (int16_t)node.data->value.i; | |
} | |
mpack_node_flag_error(node, mpack_error_type); | |
return 0; | |
} | |
#endif | |
/** | |
* Returns the 32-bit unsigned value of the node. If this node is not | |
* of a compatible type, mpack_error_type is raised, and the | |
* return value should be discarded. | |
*/ | |
MPACK_INLINE_SPEED uint32_t mpack_node_u32(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED uint32_t mpack_node_u32(mpack_node_t node) { | |
if (mpack_node_error(node) != mpack_ok) | |
return 0; | |
if (node.data->type == mpack_type_uint) { | |
if (node.data->value.u <= UINT32_MAX) | |
return (uint32_t)node.data->value.u; | |
} else if (node.data->type == mpack_type_int) { | |
if (node.data->value.i >= 0 && node.data->value.i <= UINT32_MAX) | |
return (uint32_t)node.data->value.i; | |
} | |
mpack_node_flag_error(node, mpack_error_type); | |
return 0; | |
} | |
#endif | |
/** | |
* Returns the 32-bit signed value of the node. If this node is not | |
* of a compatible type, mpack_error_type is raised, and the | |
* return value should be discarded. | |
*/ | |
MPACK_INLINE_SPEED int32_t mpack_node_i32(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED int32_t mpack_node_i32(mpack_node_t node) { | |
if (mpack_node_error(node) != mpack_ok) | |
return 0; | |
if (node.data->type == mpack_type_uint) { | |
if (node.data->value.u <= INT32_MAX) | |
return (int32_t)node.data->value.u; | |
} else if (node.data->type == mpack_type_int) { | |
if (node.data->value.i >= INT32_MIN && node.data->value.i <= INT32_MAX) | |
return (int32_t)node.data->value.i; | |
} | |
mpack_node_flag_error(node, mpack_error_type); | |
return 0; | |
} | |
#endif | |
/** | |
* Returns the 64-bit unsigned value of the node. If this node is not | |
* of a compatible type, mpack_error_type is raised, and the | |
* return value should be discarded. | |
*/ | |
MPACK_INLINE_SPEED uint64_t mpack_node_u64(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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; | |
} | |
#endif | |
/** | |
* Returns the 64-bit signed value of the node. If this node is not | |
* of a compatible type, mpack_error_type is raised, and the | |
* return value should be discarded. | |
*/ | |
MPACK_INLINE_SPEED int64_t mpack_node_i64(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED int64_t mpack_node_i64(mpack_node_t node) { | |
if (mpack_node_error(node) != mpack_ok) | |
return 0; | |
if (node.data->type == mpack_type_uint) { | |
if (node.data->value.u <= (uint64_t)INT64_MAX) | |
return (int64_t)node.data->value.u; | |
} else if (node.data->type == mpack_type_int) { | |
return node.data->value.i; | |
} | |
mpack_node_flag_error(node, mpack_error_type); | |
return 0; | |
} | |
#endif | |
/** | |
* 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 that 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. | |
*/ | |
MPACK_INLINE_SPEED float mpack_node_float(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED float mpack_node_float(mpack_node_t node) { | |
if (mpack_node_error(node) != mpack_ok) | |
return 0.0f; | |
if (node.data->type == mpack_type_uint) | |
return (float)node.data->value.u; | |
else if (node.data->type == mpack_type_int) | |
return (float)node.data->value.i; | |
else if (node.data->type == mpack_type_float) | |
return node.data->value.f; | |
else if (node.data->type == mpack_type_double) | |
return (float)node.data->value.d; | |
mpack_node_flag_error(node, mpack_error_type); | |
return 0.0f; | |
} | |
#endif | |
/** | |
* 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 that 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. | |
*/ | |
MPACK_INLINE_SPEED double mpack_node_double(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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 | |
/** | |
* 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. | |
*/ | |
MPACK_INLINE_SPEED float mpack_node_float_strict(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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 | |
/** | |
* 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. | |
*/ | |
MPACK_INLINE_SPEED double mpack_node_double_strict(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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 | |
/** | |
* @} | |
*/ | |
/** | |
* @name Node Data Functions | |
* @{ | |
*/ | |
/** | |
* Returns the extension type of the given ext node. | |
*/ | |
MPACK_INLINE_SPEED int8_t mpack_node_exttype(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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 node.data->exttype; | |
mpack_node_flag_error(node, mpack_error_type); | |
return 0; | |
} | |
#endif | |
/** | |
* Returns the length of the given str, bin or ext node. | |
*/ | |
MPACK_INLINE_SPEED size_t mpack_node_data_len(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED size_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 || type == mpack_type_ext) | |
return (size_t)node.data->value.data.l; | |
mpack_node_flag_error(node, mpack_error_type); | |
return 0; | |
} | |
#endif | |
/** | |
* Returns the length in bytes of the given string node. This does not | |
* include any null-terminator. | |
*/ | |
MPACK_INLINE_SPEED size_t mpack_node_strlen(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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->value.data.l; | |
mpack_node_flag_error(node, mpack_error_type); | |
return 0; | |
} | |
#endif | |
/** | |
* Returns a pointer to the data contained by this node. | |
* | |
* Note that strings are not null-terminated! Use mpack_node_copy_cstr() or | |
* mpack_node_cstr_alloc() 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 map, mpack_error_type is raised, and | |
* NULL is returned. | |
*/ | |
MPACK_INLINE_SPEED const char* mpack_node_data(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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 || type == mpack_type_ext) | |
return node.data->value.data.bytes; | |
mpack_node_flag_error(node, mpack_error_type); | |
return NULL; | |
} | |
#endif | |
/** | |
* Copies the bytes contained by this node into the given buffer, returning the | |
* number of bytes in the node. | |
* | |
* If this node is not of a str, bin or map, mpack_error_type is raised, and the | |
* buffer and return value should be discarded. If the node's data does not fit | |
* in the given buffer, mpack_error_data is raised, and the buffer and return value | |
* should be discarded. | |
* | |
* @param node The string node from which to copy data | |
* @param buffer A buffer in which to copy the node's bytes | |
* @param size The size of the given buffer | |
*/ | |
size_t mpack_node_copy_data(mpack_node_t node, char* buffer, size_t size); | |
/** | |
* Copies the bytes contained by this string node into the given buffer and adds | |
* a null terminator. If this node is not of a string type, mpack_error_type is raised, | |
* and the buffer should be discarded. If the string does not fit, mpack_error_data is | |
* raised, and the buffer should be discarded. | |
* | |
* If this node is not of a string type, mpack_error_type is raised, and the | |
* buffer and return value should be discarded. If the string and null-terminator | |
* do not fit in the given buffer, mpack_error_data is raised, and the buffer and | |
* return value should be discarded. | |
* | |
* @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); | |
#ifdef MPACK_MALLOC | |
/** | |
* Allocates a new chunk of data using MPACK_MALLOC with the bytes | |
* contained by this node. The returned string should be freed with MPACK_FREE. | |
* | |
* If this node is not a str, bin or ext type, mpack_error_type is raised | |
* and the return value should be discarded. If the string and null-terminator | |
* are longer than the given maximum length, mpack_error_too_big is raised, and | |
* the return value should be discarded. If an allocation failure occurs, | |
* mpack_error_memory is raised and the return value should be discarded. | |
*/ | |
char* mpack_node_data_alloc(mpack_node_t node, size_t maxlen); | |
/** | |
* Allocates a new null-terminated string using MPACK_MALLOC with the string | |
* contained by this node. The returned string should be freed with MPACK_FREE. | |
* | |
* If this node is not a string type, mpack_error_type is raised, and the return | |
* value should be discarded. | |
*/ | |
char* mpack_node_cstr_alloc(mpack_node_t node, size_t maxlen); | |
#endif | |
/** | |
* @} | |
*/ | |
/** | |
* @name Compound Node Functions | |
* @{ | |
*/ | |
// internal implementation of map key lookup functions to support optional flag | |
mpack_node_t mpack_node_map_str_impl(mpack_node_t node, const char* str, size_t length, bool optional); | |
mpack_node_t mpack_node_map_int_impl(mpack_node_t node, int64_t num, bool optional); | |
mpack_node_t mpack_node_map_uint_impl(mpack_node_t node, uint64_t num, bool optional); | |
/** | |
* Returns the length of the given array node. Raises mpack_error_type | |
* and returns 0 if the given node is not an array. | |
*/ | |
MPACK_INLINE_SPEED size_t mpack_node_array_length(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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->value.content.n; | |
} | |
#endif | |
/** | |
* Returns the node in the given array at the given index. If the node | |
* is not an array, mpack_error_type is raised and a nil node is returned. | |
* If the given index is out of bounds, mpack_error_data is raised and | |
* a nil node is returned. | |
*/ | |
MPACK_INLINE_SPEED mpack_node_t mpack_node_array_at(mpack_node_t node, size_t index); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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->value.content.n) { | |
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)); | |
} | |
#endif | |
/** | |
* 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. | |
*/ | |
MPACK_INLINE_SPEED size_t mpack_node_map_count(mpack_node_t node); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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->value.content.n; | |
} | |
#endif | |
// internal node map lookup | |
MPACK_INLINE_SPEED mpack_node_t mpack_node_map_at(mpack_node_t node, size_t index, size_t offset); | |
#if MPACK_DEFINE_INLINE_SPEED | |
MPACK_INLINE_SPEED 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->value.content.n) { | |
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)); | |
} | |
#endif | |
/** | |
* 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_INLINE mpack_node_t mpack_node_map_key_at(mpack_node_t node, size_t index) { | |
return mpack_node_map_at(node, index, 0); | |
} | |
/** | |
* 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_INLINE mpack_node_t mpack_node_map_value_at(mpack_node_t node, size_t index) { | |
return mpack_node_map_at(node, index, 1); | |
} | |
/** | |
* Returns the value node in the given map for the given integer key. If the given | |
* node is not a map, mpack_error_type is raised and a nil node is | |
* returned. If the given key does not exist in the map, mpack_error_data | |
* is raised and a nil node is returned. | |
*/ | |
MPACK_INLINE mpack_node_t mpack_node_map_int(mpack_node_t node, int64_t num) { | |
return mpack_node_map_int_impl(node, num, false); | |
} | |
/** | |
* Returns the value node in the given map for the given integer key, or NULL | |
* if the map does not contain the given key. | |
* | |
* @throws mpack_error_type if the node is not a map | |
*/ | |
MPACK_INLINE mpack_node_t mpack_node_map_int_optional(mpack_node_t node, int64_t num) { | |
return mpack_node_map_int_impl(node, num, true); | |
} | |
/** | |
* Returns the value node in the given map for the given unsigned integer key. If | |
* the given node is not a map, mpack_error_type is raised and a nil node is | |
* returned. If the given key does not exist in the map, mpack_error_data | |
* is raised and a nil node is returned. | |
*/ | |
MPACK_INLINE mpack_node_t mpack_node_map_uint(mpack_node_t node, uint64_t num) { | |
return mpack_node_map_uint_impl(node, num, false); | |
} | |
/** | |
* Returns the value node in the given map for the given unsigned integer | |
* key, or NULL if the map does not contain the given key. | |
* | |
* @throws mpack_error_type if the node is not a map | |
*/ | |
MPACK_INLINE mpack_node_t mpack_node_map_uint_optional(mpack_node_t node, uint64_t num) { | |
return mpack_node_map_uint_impl(node, num, true); | |
} | |
/** | |
* Returns the value node in the given map for the given string key. If the given | |
* node is not a map, mpack_error_type is raised and a nil node is | |
* returned. If the given key does not exist in the map, mpack_error_data | |
* is raised and a nil node is returned. | |
*/ | |
MPACK_INLINE mpack_node_t mpack_node_map_str(mpack_node_t node, const char* str, size_t length) { | |
return mpack_node_map_str_impl(node, str, length, false); | |
} | |
/** | |
* Returns the value node in the given map for the given string key, or NULL | |
* if the map does not contain the given key. | |
* | |
* @throws mpack_error_type if the node is not a map | |
*/ | |
MPACK_INLINE mpack_node_t mpack_node_map_str_optional(mpack_node_t node, const char* str, size_t length) { | |
return mpack_node_map_str_impl(node, str, length, true); | |
} | |
/** | |
* Returns the value node in the given map for the given null-terminated string key. | |
* If the given node is not a map, mpack_error_type is raised and a nil node is | |
* returned. If the given key does not exist in the map, mpack_error_data | |
* is raised and a nil node is returned. | |
*/ | |
MPACK_INLINE mpack_node_t mpack_node_map_cstr(mpack_node_t node, const char* cstr) { | |
return mpack_node_map_str(node, cstr, mpack_strlen(cstr)); | |
} | |
/** | |
* Returns the value node in the given map for the given null-terminated | |
* string key, or NULL if the map does not contain the given key. | |
* | |
* @throws mpack_error_type if the node is not a map | |
*/ | |
MPACK_INLINE mpack_node_t mpack_node_map_cstr_optional(mpack_node_t node, const char* cstr) { | |
return mpack_node_map_str_optional(node, cstr, mpack_strlen(cstr)); | |
} | |
/** | |
* Returns true if the given node map contains a value for the given string key. | |
* If the given node is not a map, mpack_error_type is raised and null is | |
* returned. | |
*/ | |
bool mpack_node_map_contains_str(mpack_node_t node, const char* str, size_t length); | |
/** | |
* Returns true if the given node map contains a value for the given | |
* null-terminated string key. If the given node is not a map, mpack_error_type | |
* is raised and null is returned. | |
*/ | |
MPACK_INLINE bool mpack_node_map_contains_cstr(mpack_node_t node, const char* cstr) { | |
return mpack_node_map_contains_str(node, cstr, mpack_strlen(cstr)); | |
} | |
/** | |
* @} | |
*/ | |
/** | |
* @} | |
*/ | |
#ifdef __cplusplus | |
} | |
#endif | |
#endif | |
#endif | |
#endif | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
http://msgpack-json-editor.com/ | |
https://en.wikibooks.org/wiki/C_Programming/Preprocessor#X-Macros | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <stdint.h> | |
#include <inttypes.h> | |
#include "mpack-config.h" | |
#include <mpack.h> | |
static mpack_writer_t writer; | |
#define SERIALIZE_STRUCT_MEMBER(member, type, _) type member; | |
#define SERIALIZE_CSTR_NULL(writer) \ | |
mpack_write_cstr(writer, "null") | |
#define SERIALIZE_KEY(writer, key) \ | |
mpack_write_cstr(writer, #key) | |
#define SERIALIZE_VALUE(writer, obj, member, Type) \ | |
SERIALIZE_TYPE_##Type(writer, obj, member) | |
#define SERIALIZE_TYPE_uint8_t(writer, obj, member) \ | |
mpack_write_u8(writer, obj->member) | |
#define SERIALIZE_TYPE_uint16_t(writer, obj, member) \ | |
mpack_write_u16(writer, obj->member) | |
#define SERIALIZE_TYPE_str_t(writer, obj, member) \ | |
if (obj->member) \ | |
mpack_write_cstr(writer, obj->member); \ | |
else \ | |
SERIALIZE_CSTR_NULL(writer) | |
#define SERIALIZE_TYPE_sensor_meta_t(writer, obj, member) \ | |
_serialize(sensor_meta_id, &obj->member, writer) | |
#define SERIALIZE_MEMBER_COUNT(_a1, _a2, _a3) 1+ | |
#define SERIALIZE_MEMBER(member, Type, obj, writer) \ | |
SERIALIZE_KEY(writer, member); \ | |
SERIALIZE_VALUE(writer, obj, member, Type); | |
typedef const char * str_t; | |
#define OBJ_ITEM_LIST \ | |
OBJ_ITEM(sensor_meta) \ | |
OBJ_ITEM(star) | |
#define sensor_meta(_, ...) \ | |
_(uid, uint16_t, __VA_ARGS__) \ | |
_(label, str_t, __VA_ARGS__) \ | |
_(descr, str_t, __VA_ARGS__) \ | |
_(type, str_t, __VA_ARGS__) | |
#define star(_, ...) \ | |
_(name, str_t, __VA_ARGS__) \ | |
_(x, uint8_t, __VA_ARGS__) \ | |
_(y, uint8_t, __VA_ARGS__) \ | |
_(z, uint8_t, __VA_ARGS__) \ | |
_(meta, sensor_meta_t, __VA_ARGS__) | |
// Create structs | |
#define OBJ_ITEM(name) \ | |
typedef struct name { \ | |
name(SERIALIZE_STRUCT_MEMBER, _) \ | |
} name##_t; | |
OBJ_ITEM_LIST | |
#undef OBJ_ITEM | |
enum obj_list_ids{ | |
#define OBJ_ITEM(name) name##_id, | |
OBJ_ITEM_LIST | |
#undef OBJ_ITEM | |
}; | |
static const struct _obj_list_item | |
{ | |
uint16_t id; | |
const char *name; | |
} obj_list[] = { | |
#define OBJ_ITEM(Name) { .id = Name##_id, .name = #Name }, | |
OBJ_ITEM_LIST | |
#undef OBJ_ITEM | |
{ .id = 0, .name = NULL } | |
}; | |
#define serialize(name, object, writer) _serialize(name##_id, object, writer) | |
// Create | |
void _serialize(enum obj_list_ids id, void *data, mpack_writer_t *writer) | |
{ | |
switch(id) { | |
#define OBJ_ITEM(name) \ | |
case name##_id: \ | |
printf("serialize struct "#name "(id: %u)\n", obj_list[id].id); \ | |
if (data) { \ | |
mpack_start_map(writer, name(SERIALIZE_MEMBER_COUNT)0); \ | |
name(SERIALIZE_MEMBER, ((struct name *)data), writer); \ | |
mpack_finish_map(writer); \ | |
} \ | |
break; | |
//--- | |
OBJ_ITEM_LIST | |
//--- | |
default: | |
break; | |
} | |
} | |
void test_star(mpack_writer_t *writer) | |
{ | |
struct star s; | |
memset(&s, 0, sizeof(s)); | |
s.name = "Sirius"; | |
s.x = 255; | |
s.y = 127; | |
s.z = 63; | |
s.meta.uid = 1234; | |
s.meta.label = "s.meta.label"; | |
s.meta.descr = "s.meta.descr"; | |
s.meta.type = "coord"; | |
serialize(star, &s, writer); | |
} | |
void test_sensor_meta(mpack_writer_t *writer) | |
{ | |
struct sensor_meta s; | |
memset(&s, 0, sizeof(s)); | |
s.uid = 1; | |
s.label = "bat1-temp"; | |
s.descr = "Battery #1 temp"; | |
s.type = "percent"; | |
serialize(sensor_meta, &s, writer); | |
} | |
int | |
main(void) | |
{ | |
for (unsigned int i = 0; i < sizeof(obj_list)/sizeof(obj_list[0]) - 1; i++) | |
printf(".name = %s, .id = %u\n", obj_list[i].name, obj_list[i].id); | |
mpack_writer_init_file(&writer, "./test.bin"); | |
mpack_start_array(&writer, 2); | |
test_star(&writer); | |
test_sensor_meta(&writer); | |
mpack_finish_array(&writer); | |
if (mpack_writer_destroy(&writer) != mpack_ok) { | |
fprintf(stderr, "An error occurred encoding the data!\n"); | |
return -1; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment