diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eda1a9..70e772c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,9 @@ New Features: - The Node API can now parse messages indefinitely from a continuous stream. A tree can be initialized with `mpack_tree_init_stream()` to receive a callback for more data. -- The writer now supports a v4 compatibility mode. Call `mpack_writer_set_version(writer, mpack_version_v4);` to encode without using the `raw8`, `bin` and `ext` types. +- The Node API can now parse messages incrementally from a non-blocking stream. Call `mpack_tree_try_parse()` to continue parsing. It will return true when a complete message has become available. + +- The writer now supports a v4 compatibility mode. Call `mpack_writer_set_version(writer, mpack_version_v4);` to encode without using the `raw8`, `bin`, `ext` and `timestamp` types. - The stdio helpers now allow reading from a `FILE*`. `_init_file()` functions have been renamed to `_init_filename()`. The old names will continue to work for a few more versions. diff --git a/src/mpack-config.h.sample b/src/mpack-config.h.sample index e6450df..591b13e 100644 --- a/src/mpack-config.h.sample +++ b/src/mpack-config.h.sample @@ -329,7 +329,6 @@ /** * The maximum depth for the node parser if @ref MPACK_MALLOC is not available. - * The parsing stack is placed on the call stack. */ #ifndef MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC #define MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC 32 diff --git a/src/mpack/mpack-common.h b/src/mpack/mpack-common.h index 75f2ee7..4c1cc89 100644 --- a/src/mpack/mpack-common.h +++ b/src/mpack/mpack-common.h @@ -788,6 +788,7 @@ MPACK_INLINE void mpack_store_i64(char* p, int64_t val) {mpack_store_u64(p, (uin MPACK_INLINE float mpack_load_float(const char* p) { MPACK_CHECK_FLOAT_ORDER(); + MPACK_STATIC_ASSERT(sizeof(float) == sizeof(uint32_t), "float is wrong size??"); union { float f; uint32_t u; @@ -798,6 +799,7 @@ MPACK_INLINE float mpack_load_float(const char* p) { MPACK_INLINE double mpack_load_double(const char* p) { MPACK_CHECK_FLOAT_ORDER(); + MPACK_STATIC_ASSERT(sizeof(double) == sizeof(uint64_t), "double is wrong size??"); union { double d; uint64_t u; diff --git a/src/mpack/mpack-node.c b/src/mpack/mpack-node.c index d3b9f1a..6d403ae 100644 --- a/src/mpack/mpack-node.c +++ b/src/mpack/mpack-node.c @@ -68,55 +68,23 @@ MPACK_STATIC_INLINE int8_t mpack_node_exttype_unchecked(mpack_node_t node) { #endif -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; - - // We keep track of the number of "possible nodes" left in the data rather - // than the number of bytes. - // - // When a map or array is parsed, we ensure at least one byte for each child - // exists and subtract them right away. This ensures that if ever a map or - // array declares more elements than could possibly be contained in the data, - // we will error out immediately rather than allocating storage for them. - // - // For example malicious data that repeats 0xDE 0xFF 0xFF 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 - // parsing stack. - // - // Once an entire message has been parsed (and there are no nodes left to - // parse whose bytes have been subtracted), this matches the number of left - // over bytes in the data. - size_t possible_nodes_left; - - mpack_node_data_t* nodes; // next node in current page/pool - size_t nodes_left; // nodes left in current page/pool - - size_t level; - size_t depth; - mpack_level_t* stack; - bool stack_owned; -} mpack_tree_parser_t; - #ifdef MPACK_MALLOC -static bool mpack_tree_reserve_fill(mpack_tree_parser_t* parser, size_t bytes) { - mpack_assert(bytes > parser->possible_nodes_left, - "there are already enough bytes! call mpack_tree_reserve_bytes() instead."); +/* + * Fills the tree until we have at least enough bytes for the current node. + */ +static bool mpack_tree_reserve_fill(mpack_tree_t* tree) { + mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress); + + size_t bytes = tree->parser.current_node_reserved; + mpack_assert(bytes > tree->parser.possible_nodes_left, + "there are already enough bytes! call mpack_tree_ensure() instead."); mpack_log("filling to reserve %i bytes\n", (int)bytes); - mpack_tree_t* tree = parser->tree; // if the necessary bytes would put us over the maximum tree // size, fail right away. // TODO: check for overflow? if (tree->data_length + bytes > tree->max_size) { - mpack_tree_flag_error(parser->tree, mpack_error_too_big); + mpack_tree_flag_error(tree, mpack_error_too_big); return false; } @@ -125,7 +93,7 @@ static bool mpack_tree_reserve_fill(mpack_tree_parser_t* parser, size_t bytes) { // (or messages), so we flag it as invalid. if (tree->read_fn == NULL) { mpack_log("tree has no read function!\n"); - mpack_tree_flag_error(parser->tree, mpack_error_invalid); + mpack_tree_flag_error(tree, mpack_error_invalid); return false; } @@ -148,7 +116,7 @@ static bool mpack_tree_reserve_fill(mpack_tree_parser_t* parser, size_t bytes) { new_buffer = (char*)mpack_realloc(tree->buffer, tree->data_length, new_capacity); if (new_buffer == NULL) { - mpack_tree_flag_error(parser->tree, mpack_error_memory); + mpack_tree_flag_error(tree, mpack_error_memory); return false; } @@ -162,133 +130,117 @@ static bool mpack_tree_reserve_fill(mpack_tree_parser_t* parser, size_t bytes) { do { size_t read = tree->read_fn(tree, tree->buffer + tree->data_length, tree->buffer_capacity - tree->data_length); - // As with the reader, node fill functions can flag an error or return - // 0 on failure. We also guard against functions that -1 just in case. + // If the fill function encounters an error, it should flag an error on + // the tree. if (mpack_tree_error(tree) != mpack_ok) return false; - if (read == 0 || read == (size_t)(-1)) { - mpack_tree_flag_error(parser->tree, mpack_error_io); + + // We guard against fill functions that return -1 just in case. + if (read == (size_t)(-1)) { + mpack_tree_flag_error(tree, mpack_error_io); + return false; + } + + // If the fill function returns 0, the data is not available yet. We + // return false to stop parsing the current node. + if (read == 0) { + mpack_log("not enough data.\n"); return false; } + mpack_log("read %u more bytes\n", (uint32_t)read); tree->data_length += read; - parser->possible_nodes_left += read; - } while (parser->possible_nodes_left < bytes); + tree->parser.possible_nodes_left += read; + } while (tree->parser.possible_nodes_left < bytes); - parser->possible_nodes_left -= bytes; return true; } #endif -// Reserving bytes reads into the buffer if needed, and adjusts the -// tree length and possible nodes left, but does not adjust the -// tree size until the bytes are actually used. -MPACK_STATIC_INLINE bool mpack_tree_reserve_bytes(mpack_tree_parser_t* parser, size_t bytes) { - if (bytes <= parser->possible_nodes_left) { - parser->possible_nodes_left -= bytes; - return true; +/* + * Ensures there are enough additional bytes in the tree for the current node + * (including reserved bytes for the children of this node, and in addition to + * the reserved bytes for children of previous compound nodes), reading more + * data if needed. + * + * Returns false if not enough bytes could be read. + */ +MPACK_STATIC_INLINE bool mpack_tree_reserve_bytes(mpack_tree_t* tree, size_t bytes) { + mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress); + + // We guard against overflow here. A compound type could declare more than + // UINT32_MAX contents which overflows SIZE_MAX on 32-bit platforms. We + // flag mpack_error_invalid instead of mpack_error_too_big since it's far + // more likely that the message is corrupt than that the data is valid but + // not parseable on this architecture (see test_read_node_possible() in + // test-node.c .) + if ((uint64_t)tree->parser.current_node_reserved + (uint64_t)bytes > (uint64_t)SIZE_MAX) { + mpack_tree_flag_error(tree, mpack_error_invalid); + return false; } + tree->parser.current_node_reserved += bytes; + + // Note that possible_nodes_left already accounts for reserved bytes for + // children of previous compound nodes. So even if there are hundreds of + // bytes left in the buffer, we might need to read anyway. + if (tree->parser.current_node_reserved <= tree->parser.possible_nodes_left) + return true; + #ifdef MPACK_MALLOC - return mpack_tree_reserve_fill(parser, bytes); + return mpack_tree_reserve_fill(tree); #else - mpack_tree_flag_error(parser->tree, mpack_error_invalid); return false; #endif } -MPACK_STATIC_INLINE uint8_t mpack_tree_u8(mpack_tree_parser_t* parser) { - if (!mpack_tree_reserve_bytes(parser, sizeof(uint8_t))) - return 0; - uint8_t val = mpack_load_u8(parser->tree->data + parser->tree->size); - parser->tree->size += sizeof(uint8_t); - return val; -} - -MPACK_STATIC_INLINE uint16_t mpack_tree_u16(mpack_tree_parser_t* parser) { - if (!mpack_tree_reserve_bytes(parser, sizeof(uint16_t))) - return 0; - uint16_t val = mpack_load_u16(parser->tree->data + parser->tree->size); - parser->tree->size += sizeof(uint16_t); - return val; -} - -MPACK_STATIC_INLINE uint32_t mpack_tree_u32(mpack_tree_parser_t* parser) { - if (!mpack_tree_reserve_bytes(parser, sizeof(uint32_t))) - return 0; - uint32_t val = mpack_load_u32(parser->tree->data + parser->tree->size); - parser->tree->size += sizeof(uint32_t); - return val; -} - -MPACK_STATIC_INLINE uint64_t mpack_tree_u64(mpack_tree_parser_t* parser) { - if (!mpack_tree_reserve_bytes(parser, sizeof(uint64_t))) - return 0; - uint64_t val = mpack_load_u64(parser->tree->data + parser->tree->size); - parser->tree->size += 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 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 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; +MPACK_STATIC_INLINE size_t mpack_tree_parser_stack_capacity(mpack_tree_t* tree) { + #ifdef MPACK_MALLOC + return tree->parser.stack_capacity; + #else + return sizeof(tree->parser.stack) / sizeof(tree->parser.stack[0]); + #endif } -static void mpack_tree_push_stack(mpack_tree_parser_t* parser, mpack_node_data_t* first_child, size_t total) { +static bool mpack_tree_push_stack(mpack_tree_t* tree, mpack_node_data_t* first_child, size_t total) { + mpack_tree_parser_t* parser = &tree->parser; + mpack_assert(parser->state == mpack_tree_parse_state_in_progress); // No need to push empty containers if (total == 0) - return; + return true; // Make sure we have enough room in the stack - if (parser->level + 1 == parser->depth) { + if (parser->level + 1 == mpack_tree_parser_stack_capacity(tree)) { #ifdef MPACK_MALLOC - size_t new_depth = parser->depth * 2; - mpack_log("growing stack to depth %i\n", (int)new_depth); + size_t new_capacity = parser->stack_capacity * 2; + mpack_log("growing parse stack to capacity %i\n", (int)new_capacity); // Replace the stack-allocated parsing stack if (!parser->stack_owned) { - mpack_level_t* new_stack = (mpack_level_t*)MPACK_MALLOC(sizeof(mpack_level_t) * new_depth); + mpack_level_t* new_stack = (mpack_level_t*)MPACK_MALLOC(sizeof(mpack_level_t) * new_capacity); if (!new_stack) { - mpack_tree_flag_error(parser->tree, mpack_error_memory); - return; + mpack_tree_flag_error(tree, mpack_error_memory); + return false; } - mpack_memcpy(new_stack, parser->stack, sizeof(mpack_level_t) * parser->depth); + mpack_memcpy(new_stack, parser->stack, sizeof(mpack_level_t) * parser->stack_capacity); parser->stack = new_stack; parser->stack_owned = true; // Realloc the allocated parsing stack } else { mpack_level_t* new_stack = (mpack_level_t*)mpack_realloc(parser->stack, - sizeof(mpack_level_t) * parser->depth, sizeof(mpack_level_t) * new_depth); + sizeof(mpack_level_t) * parser->stack_capacity, sizeof(mpack_level_t) * new_capacity); if (!new_stack) { - mpack_tree_flag_error(parser->tree, mpack_error_memory); - return; + mpack_tree_flag_error(tree, mpack_error_memory); + return false; } parser->stack = new_stack; } - parser->depth = new_depth; + parser->stack_capacity = new_capacity; #else - mpack_tree_flag_error(parser->tree, mpack_error_too_big); - return; + mpack_tree_flag_error(tree, mpack_error_too_big); + return false; #endif } @@ -296,25 +248,29 @@ static void mpack_tree_push_stack(mpack_tree_parser_t* parser, mpack_node_data_t ++parser->level; parser->stack[parser->level].child = first_child; parser->stack[parser->level].left = total; + return true; } -static void mpack_tree_parse_children(mpack_tree_parser_t* parser, mpack_node_data_t* node) { +static bool mpack_tree_parse_children(mpack_tree_t* tree, mpack_node_data_t* node) { + mpack_tree_parser_t* parser = &tree->parser; + mpack_assert(parser->state == mpack_tree_parse_state_in_progress); + mpack_type_t type = node->type; size_t total = node->len; // Calculate total elements to read if (type == mpack_type_map) { if ((uint64_t)total * 2 > (uint64_t)SIZE_MAX) { - mpack_tree_flag_error(parser->tree, mpack_error_too_big); - return; + mpack_tree_flag_error(tree, mpack_error_too_big); + return false; } total *= 2; } // Each node is at least one byte. Count these bytes now to make // sure there is enough data left. - if (!mpack_tree_reserve_bytes(parser, total)) - return; + if (!mpack_tree_reserve_bytes(tree, total)) + return false; // If there are enough nodes left in the current page, no need to grow if (total <= parser->nodes_left) { @@ -327,9 +283,9 @@ static void mpack_tree_parse_children(mpack_tree_parser_t* parser, mpack_node_da #ifdef MPACK_MALLOC // We can't grow if we're using a fixed pool (i.e. we didn't start with a page) - if (!parser->tree->next) { - mpack_tree_flag_error(parser->tree, mpack_error_too_big); - return; + if (!tree->next) { + mpack_tree_flag_error(tree, mpack_error_too_big); + return false; } // Otherwise we need to grow, and the node's children need to be contiguous. @@ -344,11 +300,12 @@ static void mpack_tree_parse_children(mpack_tree_parser_t* parser, mpack_node_da mpack_tree_page_t* page; if (total > MPACK_NODES_PER_PAGE || parser->nodes_left > MPACK_NODES_PER_PAGE / 8) { + // TODO: this should check for overflow page = (mpack_tree_page_t*)MPACK_MALLOC( sizeof(mpack_tree_page_t) + sizeof(mpack_node_data_t) * (total - 1)); if (page == NULL) { - mpack_tree_flag_error(parser->tree, mpack_error_memory); - return; + mpack_tree_flag_error(tree, mpack_error_memory); + return false; } mpack_log("allocated seperate page %p for %i children, %i left in page of %i total\n", page, (int)total, (int)parser->nodes_left, (int)MPACK_NODES_PER_PAGE); @@ -358,8 +315,8 @@ static void mpack_tree_parse_children(mpack_tree_parser_t* parser, mpack_node_da } else { page = (mpack_tree_page_t*)MPACK_MALLOC(MPACK_PAGE_ALLOC_SIZE); if (page == NULL) { - mpack_tree_flag_error(parser->tree, mpack_error_memory); - return; + mpack_tree_flag_error(tree, mpack_error_memory); + return false; } mpack_log("allocated new page %p for %i children, wasting %i in page of %i total\n", page, (int)total, (int)parser->nodes_left, (int)MPACK_NODES_PER_PAGE); @@ -369,41 +326,44 @@ static void mpack_tree_parse_children(mpack_tree_parser_t* parser, mpack_node_da parser->nodes_left = MPACK_NODES_PER_PAGE - total; } - page->next = parser->tree->next; - parser->tree->next = page; + page->next = tree->next; + tree->next = page; #else // We can't grow if we don't have an allocator - mpack_tree_flag_error(parser->tree, mpack_error_too_big); - return; + mpack_tree_flag_error(tree, mpack_error_too_big); + return false; #endif } - mpack_tree_push_stack(parser, node->value.children, total); + return mpack_tree_push_stack(tree, node->value.children, total); } -static void mpack_tree_parse_bytes(mpack_tree_parser_t* parser, mpack_node_data_t* node) { - size_t length = node->len; - if (!mpack_tree_reserve_bytes(parser, length)) - return; - node->value.offset = parser->tree->size; - parser->tree->size += length; +static bool mpack_tree_parse_bytes(mpack_tree_t* tree, mpack_node_data_t* node) { + node->value.offset = tree->size + tree->parser.current_node_reserved + 1; + return mpack_tree_reserve_bytes(tree, node->len); } -MPACK_STATIC_INLINE void mpack_tree_parse_ext(mpack_tree_parser_t* parser, mpack_node_data_t* node) { +static bool mpack_tree_parse_ext(mpack_tree_t* tree, mpack_node_data_t* node) { + // reserve space for exttype + tree->parser.current_node_reserved += sizeof(int8_t); node->type = mpack_type_ext; - mpack_tree_i8(parser); // exttype - mpack_tree_parse_bytes(parser, node); + return mpack_tree_parse_bytes(tree, node); } -static void mpack_tree_parse_node(mpack_tree_parser_t* parser, mpack_node_data_t* node) { +static bool mpack_tree_parse_node_contents(mpack_tree_t* tree, mpack_node_data_t* node) { + mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress); mpack_assert(node != NULL, "null node?"); // read the type. we've already accounted for this byte in - // possible_nodes_left, so we know it is in bounds and don't - // need to subtract it. - uint8_t type = mpack_load_u8(parser->tree->data + parser->tree->size); - parser->tree->size += sizeof(uint8_t); + // possible_nodes_left, so we already know it is in bounds, and we don't + // need to reserve it for this node. + mpack_assert(tree->data_length > tree->size); + const char* p = tree->data + tree->size; + uint8_t type = mpack_load_u8(p); + mpack_log("node type %x\n", type); + p += sizeof(uint8_t); + tree->parser.current_node_reserved = 0; // as with mpack_read_tag(), the fastest way to parse a node is to switch // on the first byte, and to explicitly list every possible byte. we switch @@ -417,34 +377,31 @@ static void mpack_tree_parse_node(mpack_tree_parser_t* parser, mpack_node_data_t case 0x4: case 0x5: case 0x6: case 0x7: node->type = mpack_type_uint; node->value.u = type; - return; + return true; // negative fixnum case 0xe: case 0xf: node->type = mpack_type_int; node->value.i = (int8_t)type; - return; + return true; // fixmap case 0x8: node->type = mpack_type_map; node->len = (uint32_t)(type & ~0xf0); - mpack_tree_parse_children(parser, node); - return; + return mpack_tree_parse_children(tree, node); // fixarray case 0x9: node->type = mpack_type_array; node->len = (uint32_t)(type & ~0xf0); - mpack_tree_parse_children(parser, node); - return; + return mpack_tree_parse_children(tree, node); // fixstr case 0xa: case 0xb: node->type = mpack_type_str; node->len = (uint32_t)(type & ~0xe0); - mpack_tree_parse_bytes(parser, node); - return; + return mpack_tree_parse_bytes(tree, node); // not one of the common infix types default: @@ -474,7 +431,7 @@ static void mpack_tree_parse_node(mpack_tree_parser_t* parser, mpack_node_data_t case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f: node->type = mpack_type_uint; node->value.u = type; - return; + return true; // negative fixnum case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7: @@ -483,23 +440,21 @@ static void mpack_tree_parse_node(mpack_tree_parser_t* parser, mpack_node_data_t case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff: node->type = mpack_type_int; node->value.i = (int8_t)type; - return; + return true; // fixmap case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: node->type = mpack_type_map; node->len = (uint32_t)(type & ~0xf0); - mpack_tree_parse_children(parser, node); - return; + return mpack_tree_parse_children(tree, node); // fixarray case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f: node->type = mpack_type_array; node->len = (uint32_t)(type & ~0xf0); - mpack_tree_parse_children(parser, node); - return; + return mpack_tree_parse_children(tree, node); // fixstr case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7: @@ -508,228 +463,299 @@ static void mpack_tree_parse_node(mpack_tree_parser_t* parser, mpack_node_data_t case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: node->type = mpack_type_str; node->len = (uint32_t)(type & ~0xe0); - mpack_tree_parse_bytes(parser, node); - return; + return mpack_tree_parse_bytes(tree, node); #endif // nil case 0xc0: node->type = mpack_type_nil; - return; + return true; // bool case 0xc2: case 0xc3: node->type = mpack_type_bool; node->value.b = type & 1; - return; + return true; // bin8 case 0xc4: node->type = mpack_type_bin; - node->len = mpack_tree_u8(parser); - mpack_tree_parse_bytes(parser, node); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t))) + return false; + node->len = mpack_load_u8(p); + return mpack_tree_parse_bytes(tree, node); // bin16 case 0xc5: node->type = mpack_type_bin; - node->len = mpack_tree_u16(parser); - mpack_tree_parse_bytes(parser, node); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t))) + return false; + node->len = mpack_load_u16(p); + return mpack_tree_parse_bytes(tree, node); // bin32 case 0xc6: node->type = mpack_type_bin; - node->len = mpack_tree_u32(parser); - mpack_tree_parse_bytes(parser, node); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t))) + return false; + node->len = mpack_load_u32(p); + return mpack_tree_parse_bytes(tree, node); // ext8 case 0xc7: - node->len = mpack_tree_u8(parser); - mpack_tree_parse_ext(parser, node); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t))) + return false; + node->len = mpack_load_u8(p); + return mpack_tree_parse_ext(tree, node); // ext16 case 0xc8: - node->len = mpack_tree_u16(parser); - mpack_tree_parse_ext(parser, node); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t))) + return false; + node->len = mpack_load_u16(p); + return mpack_tree_parse_ext(tree, node); // ext32 case 0xc9: - node->len = mpack_tree_u32(parser); - mpack_tree_parse_ext(parser, node); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t))) + return false; + node->len = mpack_load_u32(p); + return mpack_tree_parse_ext(tree, node); // float case 0xca: + if (!mpack_tree_reserve_bytes(tree, sizeof(float))) + return false; + node->value.f = mpack_load_float(p); node->type = mpack_type_float; - node->value.f = mpack_tree_float(parser); - return; + return true; // double case 0xcb: + if (!mpack_tree_reserve_bytes(tree, sizeof(double))) + return false; + node->value.d = mpack_load_double(p); node->type = mpack_type_double; - node->value.d = mpack_tree_double(parser); - return; + return true; // uint8 case 0xcc: node->type = mpack_type_uint; - node->value.u = mpack_tree_u8(parser); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t))) + return false; + node->value.u = mpack_load_u8(p); + return true; // uint16 case 0xcd: node->type = mpack_type_uint; - node->value.u = mpack_tree_u16(parser); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t))) + return false; + node->value.u = mpack_load_u16(p); + return true; // uint32 case 0xce: node->type = mpack_type_uint; - node->value.u = mpack_tree_u32(parser); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t))) + return false; + node->value.u = mpack_load_u32(p); + return true; // uint64 case 0xcf: node->type = mpack_type_uint; - node->value.u = mpack_tree_u64(parser); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint64_t))) + return false; + node->value.u = mpack_load_u64(p); + return true; // int8 case 0xd0: node->type = mpack_type_int; - node->value.i = mpack_tree_i8(parser); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(int8_t))) + return false; + node->value.i = mpack_load_i8(p); + return true; // int16 case 0xd1: node->type = mpack_type_int; - node->value.i = mpack_tree_i16(parser); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(int16_t))) + return false; + node->value.i = mpack_load_i16(p); + return true; // int32 case 0xd2: node->type = mpack_type_int; - node->value.i = mpack_tree_i32(parser); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(int32_t))) + return false; + node->value.i = mpack_load_i32(p); + return true; // int64 case 0xd3: node->type = mpack_type_int; - node->value.i = mpack_tree_i64(parser); - return; + if (!mpack_tree_reserve_bytes(tree, sizeof(int64_t))) + return false; + node->value.i = mpack_load_i64(p); + return true; // fixext1 case 0xd4: node->len = 1; - mpack_tree_parse_ext(parser, node); - return; + return mpack_tree_parse_ext(tree, node); // fixext2 case 0xd5: node->len = 2; - mpack_tree_parse_ext(parser, node); - return; + return mpack_tree_parse_ext(tree, node); // fixext4 case 0xd6: node->len = 4; - mpack_tree_parse_ext(parser, node); - return; + return mpack_tree_parse_ext(tree, node); // fixext8 case 0xd7: node->len = 8; - mpack_tree_parse_ext(parser, node); - return; + return mpack_tree_parse_ext(tree, node); // fixext16 case 0xd8: node->len = 16; - mpack_tree_parse_ext(parser, node); - return; + return mpack_tree_parse_ext(tree, node); // str8 case 0xd9: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t))) + return false; + node->len = mpack_load_u8(p); node->type = mpack_type_str; - node->len = mpack_tree_u8(parser); - mpack_tree_parse_bytes(parser, node); - return; + return mpack_tree_parse_bytes(tree, node); // str16 case 0xda: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t))) + return false; + node->len = mpack_load_u16(p); node->type = mpack_type_str; - node->len = mpack_tree_u16(parser); - mpack_tree_parse_bytes(parser, node); - return; + return mpack_tree_parse_bytes(tree, node); // str32 case 0xdb: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t))) + return false; + node->len = mpack_load_u32(p); node->type = mpack_type_str; - node->len = mpack_tree_u32(parser); - mpack_tree_parse_bytes(parser, node); - return; + return mpack_tree_parse_bytes(tree, node); // array16 case 0xdc: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t))) + return false; + node->len = mpack_load_u16(p); node->type = mpack_type_array; - node->len = mpack_tree_u16(parser); - mpack_tree_parse_children(parser, node); - return; + return mpack_tree_parse_children(tree, node); // array32 case 0xdd: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t))) + return false; + node->len = mpack_load_u32(p); node->type = mpack_type_array; - node->len = mpack_tree_u32(parser); - mpack_tree_parse_children(parser, node); - return; + return mpack_tree_parse_children(tree, node); // map16 case 0xde: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t))) + return false; + node->len = mpack_load_u16(p); node->type = mpack_type_map; - node->len = mpack_tree_u16(parser); - mpack_tree_parse_children(parser, node); - return; + return mpack_tree_parse_children(tree, node); // map32 case 0xdf: + if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t))) + return false; + node->len = mpack_load_u32(p); node->type = mpack_type_map; - node->len = mpack_tree_u32(parser); - mpack_tree_parse_children(parser, node); - return; + return mpack_tree_parse_children(tree, node); // reserved case 0xc1: - mpack_tree_flag_error(parser->tree, mpack_error_invalid); - return; + mpack_tree_flag_error(tree, mpack_error_invalid); + return false; #if MPACK_OPTIMIZE_FOR_SIZE // any other bytes should have been handled by the infix switch default: - mpack_assert(0, "unreachable"); - return; + break; #endif } + mpack_assert(0, "unreachable"); + return false; } -static void mpack_tree_parse_elements(mpack_tree_parser_t* parser) { - mpack_log("parsing tree elements\n"); +static bool mpack_tree_parse_node(mpack_tree_t* tree, mpack_node_data_t* node) { + mpack_log("parsing a node at position %i in level %i\n", + (int)tree->size, (int)tree->parser.level); + + if (!mpack_tree_parse_node_contents(tree, node)) { + mpack_log("node parsing returned false\n"); + return false; + } + + tree->parser.possible_nodes_left -= tree->parser.current_node_reserved; + + // The reserve for the current node does not include the initial byte + // previously reserved as part of its parent. + size_t node_size = tree->parser.current_node_reserved + 1; + + // If the parsed type is a map or array, the reserve includes one byte for + // each child. We want to subtract these out of possible_nodes_left, but + // not out of the current size of the tree. + if (node->type == mpack_type_array) + node_size -= node->len; + else if (node->type == mpack_type_map) + node_size -= node->len * 2; + tree->size += node_size; + + mpack_log("parsed a node of type %s of %i bytes and " + "%i additional bytes reserved for children.\n", + mpack_type_to_string(node->type), (int)node_size, + (int)tree->parser.current_node_reserved + 1 - (int)node_size); + + return true; +} + +/* + * We read nodes in a loop instead of recursively for maximum performance. The + * stack holds the amount of children left to read in each level of the tree. + * Parsing can pause and resume when more data becomes available. + */ +static bool mpack_tree_continue_parsing(mpack_tree_t* tree) { + if (mpack_tree_error(tree) != mpack_ok) + return false; + + mpack_tree_parser_t* parser = &tree->parser; + mpack_assert(parser->state == mpack_tree_parse_state_in_progress); + mpack_log("parsing tree elements, %i bytes in buffer\n", (int)tree->data_length); // we loop parsing nodes until the parse stack is empty. we break // by returning out of the function. while (true) { mpack_node_data_t* node = parser->stack[parser->level].child; - --parser->stack[parser->level].left; - ++parser->stack[parser->level].child; - - mpack_tree_parse_node(parser, node); + size_t level = parser->level; + if (!mpack_tree_parse_node(tree, node)) + return false; + --parser->stack[level].left; + ++parser->stack[level].child; - if (mpack_tree_error(parser->tree) != mpack_ok) - break; + mpack_assert(mpack_tree_error(tree) == mpack_ok, + "mpack_tree_parse_node() should have returned false due to error!"); // pop empty stack levels, exiting the outer loop when the stack is empty. // (we could tail-optimize containers by pre-emptively popping empty @@ -739,7 +765,7 @@ static void mpack_tree_parse_elements(mpack_tree_parser_t* parser) { // it needs to be complete.) while (parser->stack[parser->level].left == 0) { if (parser->level == 0) - return; + return true; --parser->level; } } @@ -749,6 +775,12 @@ static void mpack_tree_cleanup(mpack_tree_t* tree) { MPACK_UNUSED(tree); #ifdef MPACK_MALLOC + if (tree->parser.stack_owned) { + MPACK_FREE(tree->parser.stack); + tree->parser.stack = NULL; + tree->parser.stack_owned = false; + } + mpack_tree_page_t* page = tree->next; while (page != NULL) { mpack_tree_page_t* next = page->next; @@ -760,12 +792,59 @@ static void mpack_tree_cleanup(mpack_tree_t* tree) { #endif } -static void mpack_tree_parser_setup(mpack_tree_parser_t* parser, mpack_tree_t* tree) { - mpack_memset(parser, 0, sizeof(*parser)); - parser->tree = tree; +static bool mpack_tree_parse_start(mpack_tree_t* tree) { + if (mpack_tree_error(tree) != mpack_ok) + return false; + + mpack_tree_parser_t* parser = &tree->parser; + mpack_assert(parser->state != mpack_tree_parse_state_in_progress, + "previous parsing was not finished!"); + + if (parser->state == mpack_tree_parse_state_parsed) + mpack_tree_cleanup(tree); + + mpack_log("starting parse\n"); + tree->parser.state = mpack_tree_parse_state_in_progress; + tree->parser.current_node_reserved = 0; + + // check if we previously parsed a tree + if (tree->size > 0) { + #ifdef MPACK_MALLOC + // if we're buffered, move the remaining data back to the + // start of the buffer + // TODO: This is not ideal performance-wise. We should only move data + // when we need to call the fill function. + // TODO: We could consider shrinking the buffer here, especially if we + // determine that the fill function is providing less than a quarter of + // the buffer size or if messages take up less than a quarter of the + // buffer size. Maybe this should be configurable. + if (tree->buffer != NULL) { + mpack_memmove(tree->buffer, tree->buffer + tree->size, tree->data_length - tree->size); + } + else + #endif + // otherwise advance past the parsed data + { + tree->data += tree->size; + } + tree->data_length -= tree->size; + tree->size = 0; + } + + // make sure we have at least one byte available before allocating anything parser->possible_nodes_left = tree->data_length; + if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t))) { + tree->parser.state = mpack_tree_parse_state_not_started; + return false; + } + mpack_log("parsing tree at %p starting with byte %x\n", tree->data, (uint8_t)tree->data[0]); + parser->possible_nodes_left -= 1; #ifdef MPACK_MALLOC + parser->stack = parser->stack_local; + parser->stack_owned = false; + parser->stack_capacity = sizeof(parser->stack_local) / sizeof(*parser->stack_local); + if (tree->pool == NULL) { // allocate first page @@ -774,98 +853,80 @@ static void mpack_tree_parser_setup(mpack_tree_parser_t* parser, mpack_tree_t* t page, (int)MPACK_PAGE_ALLOC_SIZE, (int)MPACK_NODES_PER_PAGE); if (page == NULL) { tree->error = mpack_error_memory; - return; + return false; } page->next = NULL; tree->next = page; parser->nodes = page->nodes; parser->nodes_left = MPACK_NODES_PER_PAGE; - tree->root = page->nodes; - return; } + else #endif + { + // otherwise use the provided pool + mpack_assert(tree->pool != NULL, "no pool provided?"); + parser->nodes = tree->pool; + parser->nodes_left = tree->pool_count; + } + + tree->root = parser->nodes; + ++parser->nodes; + --parser->nodes_left; - // otherwise use the provided pool - mpack_assert(tree->pool != NULL, "no pool provided?"); - parser->nodes = tree->pool; - parser->nodes_left = tree->pool_count; + parser->level = 0; + parser->stack[0].child = tree->root; + parser->stack[0].left = 1; + + return true; } void mpack_tree_parse(mpack_tree_t* tree) { if (mpack_tree_error(tree) != mpack_ok) return; - tree->parsed = true; - - mpack_tree_cleanup(tree); - - mpack_log("starting parse\n"); - // check if we previously parsed a tree - if (tree->size > 0) { - #ifdef MPACK_MALLOC - // if we're buffered, move the remaining data back to the - // start of the buffer - // TODO: shrink buffer? - if (tree->buffer != NULL) { - mpack_memmove(tree->buffer, tree->buffer + tree->size, tree->data_length - tree->size); - } - else - #endif - // otherwise advance past the parsed data - { - tree->data += tree->size; + if (tree->parser.state != mpack_tree_parse_state_in_progress) { + if (!mpack_tree_parse_start(tree)) { + mpack_tree_flag_error(tree, (tree->read_fn == NULL) ? + mpack_error_invalid : mpack_error_io); + return; } - tree->data_length -= tree->size; - tree->size = 0; } - // setup parser - mpack_tree_parser_t parser; - mpack_tree_parser_setup(&parser, tree); - if (mpack_tree_error(tree) != mpack_ok) - return; + if (!mpack_tree_continue_parsing(tree)) { + if (mpack_tree_error(tree) != mpack_ok) + return; - // allocate the root node - if (!mpack_tree_reserve_bytes(&parser, sizeof(uint8_t))) + // We're parsing synchronously on a blocking fill function. If we + // didn't completely finish parsing the tree, it's an error. + mpack_log("tree parsing incomplete. flagging error.\n"); + mpack_tree_flag_error(tree, (tree->read_fn == NULL) ? + mpack_error_invalid : mpack_error_io); return; - tree->root = parser.nodes; - ++parser.nodes; - --parser.nodes_left; - tree->node_count = 1; - - // 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 - #define MPACK_NODE_STACK_LOCAL_DEPTH MPACK_NODE_INITIAL_DEPTH - parser.stack_owned = false; - #else - #define MPACK_NODE_STACK_LOCAL_DEPTH MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC - #endif - mpack_level_t stack_local[MPACK_NODE_STACK_LOCAL_DEPTH]; // no VLAs in VS 2013 - parser.depth = MPACK_NODE_STACK_LOCAL_DEPTH; - parser.stack = stack_local; - #undef MPACK_NODE_STACK_LOCAL_DEPTH - parser.level = 0; - parser.stack[0].child = tree->root; - parser.stack[0].left = 1; + } - mpack_tree_parse_elements(&parser); + mpack_assert(mpack_tree_error(tree) == mpack_ok); + mpack_assert(tree->parser.level == 0); + tree->parser.state = mpack_tree_parse_state_parsed; + mpack_log("parsed tree of %i bytes, %i bytes left\n", (int)tree->size, (int)tree->parser.possible_nodes_left); + mpack_log("%i nodes in final page\n", (int)tree->parser.nodes_left); +} - #ifdef MPACK_MALLOC - if (parser.stack_owned) - MPACK_FREE(parser.stack); - #endif +bool mpack_tree_try_parse(mpack_tree_t* tree) { + if (mpack_tree_error(tree) != mpack_ok) + return false; - if (mpack_tree_error(tree) == mpack_ok) { - mpack_log("parsed tree of %i bytes, %i bytes left\n", (int)tree->size, (int)parser.possible_nodes_left); - mpack_log("%i nodes in final page\n", (int)parser.nodes_left); - } + if (tree->parser.state != mpack_tree_parse_state_in_progress) + if (!mpack_tree_parse_start(tree)) + return false; + + if (!mpack_tree_continue_parsing(tree)) + return false; + + mpack_assert(mpack_tree_error(tree) == mpack_ok); + mpack_assert(tree->parser.level == 0); + tree->parser.state = mpack_tree_parse_state_parsed; + return true; } @@ -878,13 +939,12 @@ mpack_node_t mpack_tree_root(mpack_tree_t* tree) { if (mpack_tree_error(tree) != mpack_ok) return mpack_tree_nil_node(tree); - // We check that mpack_tree_parse() was called at least once, and - // assert if not. This is to facilitate the transition to requiring - // a call to mpack_tree_parse(), since it used to be automatic on - // initialization. - if (!tree->parsed) { - mpack_break("Tree has not been parsed! You must call mpack_tree_parse()" - " after initialization before accessing the root node."); + // We check that a tree was parsed successfully and assert if not. You must + // call mpack_tree_parse() (or mpack_tree_try_parse() with a success + // result) in order to access the root node. + if (tree->parser.state != mpack_tree_parse_state_parsed) { + mpack_break("Tree has not been parsed! " + "Did you call mpack_tree_parse() or mpack_tree_try_parse()?"); mpack_tree_flag_error(tree, mpack_error_bug); return mpack_tree_nil_node(tree); } diff --git a/src/mpack/mpack-node.h b/src/mpack/mpack-node.h index dec43c9..06206c3 100644 --- a/src/mpack/mpack-node.h +++ b/src/mpack/mpack-node.h @@ -109,10 +109,18 @@ typedef void (*mpack_tree_error_t)(mpack_tree_t* tree, mpack_error_t error); * In case of error, it should flag an appropriate error on the reader * (usually @ref mpack_error_io.) * - * @note You should only copy and return the bytes that are immediately - * available. It is always safe to return less than the requested count - * as long as some non-zero number of bytes are read; if more bytes are - * needed, the read function will simply be called again. + * The blocking or non-blocking behaviour of the read should match whether + * mpack_tree_parse() or mpack_tree_try_parse() is called. + * + * If you are using mpack_tree_parse(), the read should block until at least + * one byte is read. If you return 0, mpack_tree_parse() will raise @ref + * mpack_error_io. + * + * If you are using mpack_tree_try_parse(), the read function can always + * return 0, and must never block waiting for data (otherwise + * mpack_tree_try_parse() would be equivalent to mpack_tree_parse().) + * mpack_tree_try_parse() will return @ref mpack_try_later if not enough bytes + * were read. */ typedef size_t (*mpack_tree_read_t)(mpack_tree_t* tree, char* buffer, size_t count); @@ -158,6 +166,60 @@ typedef struct mpack_tree_page_t { mpack_node_data_t nodes[1]; // variable size } mpack_tree_page_t; +typedef enum mpack_tree_parse_state_t { + mpack_tree_parse_state_not_started, + mpack_tree_parse_state_in_progress, + mpack_tree_parse_state_parsed, +} mpack_tree_parse_state_t; + +typedef struct mpack_level_t { + mpack_node_data_t* child; + size_t left; // children left in level +} mpack_level_t; + +typedef struct mpack_tree_parser_t { + mpack_tree_parse_state_t state; + + // We keep track of the number of "possible nodes" left in the data rather + // than the number of bytes. + // + // When a map or array is parsed, we ensure at least one byte for each child + // exists and subtract them right away. This ensures that if ever a map or + // array declares more elements than could possibly be contained in the data, + // we will error out immediately rather than allocating storage for them. + // + // For example malicious data that repeats 0xDE 0xFF 0xFF (start of a map + // with 65536 key-value pairs) would otherwise cause us to run out of + // memory. With this, the parser can allocate at most as many nodes as + // there are bytes in the data (plus the paging overhead, 12%.) An error + // will be flagged immediately if and when there isn't enough data left to + // fully read all children of all open compound types on the parsing stack. + // + // Once an entire message has been parsed (and there are no nodes left to + // parse whose bytes have been subtracted), this matches the number of left + // over bytes in the data. + size_t possible_nodes_left; + + mpack_node_data_t* nodes; // next node in current page/pool + size_t nodes_left; // nodes left in current page/pool + + size_t current_node_reserved; + size_t level; + + #ifdef MPACK_MALLOC + // It's much faster to allocate the initial parsing stack inline within the + // parser. We replace it with a heap allocation if we need to grow it. + mpack_level_t* stack; + size_t stack_capacity; + bool stack_owned; + mpack_level_t stack_local[MPACK_NODE_INITIAL_DEPTH]; + #else + // Without malloc(), we have to reserve a parsing stack the maximum allowed + // parsing depth. + mpack_level_t stack[MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC]; + #endif +} mpack_tree_parser_t; + struct mpack_tree_t { mpack_tree_error_t error_fn; /* Function to call on error */ mpack_tree_read_t read_fn; /* Function to call to read more data */ @@ -176,14 +238,13 @@ struct mpack_tree_t { const char* data; size_t data_length; // length of data (and content of buffer, if used) - size_t node_count; // total node count of tree size_t size; // size in bytes of tree (usually matches length, but not if tree has trailing data) size_t max_size; // maximum message size size_t max_nodes; // maximum nodes in a message + mpack_tree_parser_t parser; mpack_node_data_t* root; - bool parsed; mpack_node_data_t* pool; // pool, or NULL if no pool provided size_t pool_count; @@ -243,7 +304,9 @@ void mpack_tree_init(mpack_tree_t* tree, const char* data, size_t length); * * The parser can be used to read a single message from a stream of unknown * length, or multiple messages from an unbounded stream, allowing it to - * be used for RPC communication. + * be used for RPC communication. Call @ref mpack_tree_parse() to parse + * a message from a blocking stream, or @ref mpack_tree_try_parse() for a + * non-blocking stream. * * The stream will use a growable internal buffer to store the most recent * message, as well as allocated pages of nodes for the parse tree. @@ -327,7 +390,7 @@ void mpack_tree_init_stdfile(mpack_tree_t* tree, FILE* stdfile, size_t max_bytes #endif /** - * Parses a MessagePack message. + * Parses a MessagePack message into a tree of immutable nodes. * * If successful, the root node will be available under @ref mpack_tree_root(). * If not, an appropriate error will be flagged. @@ -335,9 +398,40 @@ void mpack_tree_init_stdfile(mpack_tree_t* tree, FILE* stdfile, size_t max_bytes * This can be called repeatedly to parse a series of messages from a data * source. When this is called, all previous nodes from this tree and their * contents (including the root node) are invalidated. + * + * If this is called with a stream (see @ref mpack_tree_init_stream()), the + * stream must block until data is available. (Otherwise, if this is called on + * a non-blocking stream, parsing will fail with @ref mpack_error_io when the + * fill function returns 0.) + * + * There is no way to recover a tree in an error state. It must be destroyed. */ void mpack_tree_parse(mpack_tree_t* tree); +/** + * Attempts to parse a MessagePack message from a non-blocking stream into a + * tree of immutable nodes. + * + * A non-blocking read function must have been passed to the tree in + * mpack_tree_init_stream(). + * + * If this returns true, a message is available under + * @ref mpack_tree_root(). The tree nodes and data will be valid until + * the next time a parse is started. + * + * If this returns false, no message is available, because either not enough + * data is available yet or an error has occurred. You must check the tree for + * errors whenever this returns false. If there is no error, you should try + * again later when more data is available. (You will want to select()/poll() + * on the underlying socket or use some other asynchronous mechanism to + * determine when it has data.) + * + * There is no way to recover a tree in an error state. It must be destroyed. + * + * @see mpack_tree_init_stream() + */ +bool mpack_tree_try_parse(mpack_tree_t* tree); + /** * Returns the root node of the tree, if the tree is not in an error state. * Returns a nil node otherwise. diff --git a/src/mpack/mpack-platform.h b/src/mpack/mpack-platform.h index daf15cb..0647ae3 100644 --- a/src/mpack/mpack-platform.h +++ b/src/mpack/mpack-platform.h @@ -748,6 +748,7 @@ size_t mpack_strlen(const char* s); /* Debug logging */ #if 0 + #include #define mpack_log(...) (printf(__VA_ARGS__), fflush(stdout)) #else #define mpack_log(...) ((void)0)