From 1d5e7edbe97762959c8a01c583bf5cb0cbeac532 Mon Sep 17 00:00:00 2001 From: Nicholas Fraser Date: Wed, 4 Oct 2017 00:50:43 -0400 Subject: [PATCH] Implemented timestamp --- CHANGELOG.md | 8 ++- src/mpack/mpack-common.c | 39 +++++++++++- src/mpack/mpack-common.h | 106 +++++++++++++++++++++++++++----- src/mpack/mpack-expect.c | 17 ++++++ src/mpack/mpack-expect.h | 9 +++ src/mpack/mpack-node.c | 127 ++++++++++++++++++++++++++++++--------- src/mpack/mpack-node.h | 27 ++++++++- src/mpack/mpack-reader.c | 66 +++++++++++++++----- src/mpack/mpack-writer.c | 50 +++++++++++++++ src/mpack/mpack-writer.h | 24 ++++++++ test/test-expect.c | 43 +++++++++++++ test/test-file.debug | 4 +- test/test-file.json | 27 --------- test/test-file.mp | Bin 175 -> 196 bytes test/test-node.c | 70 +++++++++++++++++++++ test/test-write.c | 31 ++++++++++ 16 files changed, 555 insertions(+), 93 deletions(-) delete mode 100644 test/test-file.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 313e5fb..8c68c8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,20 +12,26 @@ Breaking Changes: - The layout of fields for ext tags in `mpack_tag_t` has changed. An ext tag no longer uses `.v.l` for its length. The compiler will not warn you about this, so be careful when upgrading. +- `mpack_tag_t` is now considered an opaque type (to prevent future breakage when changing its layout.) Compatibility for all types besides `ext` is maintained for this release, but this may change in future releases. + - The mpack configuration `mpack-config.h` file is now optional, and requires `MPACK_HAS_CONFIG` in order to be included. This means you must define `MPACK_HAS_CONFIG` when upgrading or your config file will be ignored! (It is recommended to delete your config file and use the defaults.) New Features: +- The timestamp type has been implemented. A timestamp is a signed number of nanoseconds since January 1st, 1970 at 12:00 UTC. + - The Node API can now parse multiple messages from a data source. `mpack_tree_parse()` can be called repeatedly to parse each message. - 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` and `bin` types. +- 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. Changes: +- Timestamps (exttype -1) are no longer reported as ext types. If you were reading an ext of exttype -1 and parsing the timestamp from it manually, you will need to switch over to the new timestamp functions. + - The reader's skip function is no longer ignored under `MPACK_OPTIMIZE_FOR_SIZE`. - Fixed an allocation bug when closing a growable writer without having written anything. diff --git a/src/mpack/mpack-common.c b/src/mpack/mpack-common.c index 8347f18..bae331c 100644 --- a/src/mpack/mpack-common.c +++ b/src/mpack/mpack-common.c @@ -90,7 +90,7 @@ int mpack_tag_cmp(mpack_tag_t left, mpack_tag_t right) { } if (left.type != right.type) - return (int)left.type - (int)right.type; + return ((int)left.type < (int)right.type) ? -1 : 1; switch (left.type) { case mpack_type_nil: @@ -129,6 +129,15 @@ int mpack_tag_cmp(mpack_tag_t left, mpack_tag_t right) { } return (int)left.v.ext.exttype - (int)right.v.ext.exttype; + case mpack_type_timestamp: + if (left.v.timestamp.seconds == right.v.timestamp.seconds) { + if (left.v.timestamp.nanoseconds == right.v.timestamp.nanoseconds) { + return 0; + } + return (left.v.timestamp.nanoseconds < right.v.timestamp.nanoseconds) ? -1 : 1; + } + return (left.v.timestamp.seconds < right.v.timestamp.seconds) ? -1 : 1; + // 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 @@ -177,6 +186,7 @@ void mpack_tag_debug_pseudo_json(mpack_tag_t tag, char* buffer, size_t buffer_si case mpack_type_double: mpack_snprintf(buffer, buffer_size, "%f", tag.v.d); break; + case mpack_type_str: mpack_snprintf(buffer, buffer_size, "", tag.v.l); break; @@ -187,12 +197,27 @@ void mpack_tag_debug_pseudo_json(mpack_tag_t tag, char* buffer, size_t buffer_si mpack_snprintf(buffer, buffer_size, "", tag.v.ext.exttype, tag.v.ext.length); break; + case mpack_type_array: mpack_snprintf(buffer, buffer_size, "", tag.v.n); break; case mpack_type_map: mpack_snprintf(buffer, buffer_size, "", tag.v.n); break; + + case mpack_type_timestamp: + { + int64_t seconds = tag.v.timestamp.seconds; + uint32_t nanoseconds = tag.v.timestamp.nanoseconds; + if (nanoseconds == 0) { + mpack_snprintf(buffer, buffer_size, "", seconds); + } else { + mpack_snprintf(buffer, buffer_size, "", + seconds, nanoseconds); + } + } + break; + default: mpack_snprintf(buffer, buffer_size, ""); break; @@ -241,6 +266,18 @@ void mpack_tag_debug_describe(mpack_tag_t tag, char* buffer, size_t buffer_size) case mpack_type_map: mpack_snprintf(buffer, buffer_size, "map of %u key-value pairs", tag.v.n); break; + case mpack_type_timestamp: + { + int64_t seconds = tag.v.timestamp.seconds; + uint32_t nanoseconds = tag.v.timestamp.nanoseconds; + if (nanoseconds == 0) { + mpack_snprintf(buffer, buffer_size, "timestamp %" PRIi64 , seconds); + } else { + mpack_snprintf(buffer, buffer_size, "timestamp %" PRIi64 ".%09u", + seconds, nanoseconds); + } + } + break; default: mpack_snprintf(buffer, buffer_size, "unknown!"); break; diff --git a/src/mpack/mpack-common.h b/src/mpack/mpack-common.h index c20f262..efb3145 100644 --- a/src/mpack/mpack-common.h +++ b/src/mpack/mpack-common.h @@ -98,11 +98,18 @@ MPACK_HEADER_START /** * @def MPACK_MAXIMUM_TAG_SIZE * - * The maximum encoded size of a tag in bytes, as of the "new" MessagePack spec. + * The maximum encoded size of a tag in bytes. */ -#define MPACK_MAXIMUM_TAG_SIZE 9 +#define MPACK_MAXIMUM_TAG_SIZE MPACK_TAG_SIZE_TIMESTAMP12 // 15 /** @endcond */ +/** + * @def MPACK_TIMESTAMP_NANOSECONDS_MAX + * + * The maximum value of nanoseconds for a timestamp. + */ +#define MPACK_TIMESTAMP_NANOSECONDS_MAX 999999999 + #if MPACK_COMPATIBILITY @@ -123,7 +130,8 @@ typedef enum mpack_version_t { mpack_version_v4 = 4, /** - * Version 2.0/v5, supporting the @c str8, @c bin and @c ext types. + * Version 2.0/v5, supporting the @c str8, @c bin and @c ext types + * (including timestamp.) */ mpack_version_v5 = 5, @@ -164,17 +172,18 @@ 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_int, /**< A 64-bit signed integer. */ - mpack_type_uint, /**< A 64-bit unsigned integer. */ - mpack_type_float, /**< A 32-bit IEEE 754 floating point number. */ - mpack_type_double, /**< A 64-bit IEEE 754 floating point number. */ - mpack_type_str, /**< A string. */ - mpack_type_bin, /**< A chunk of binary data. */ - mpack_type_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_nil = 1, /**< A null value. */ + mpack_type_bool, /**< A boolean (true or false.) */ + mpack_type_int, /**< A 64-bit signed integer. */ + mpack_type_uint, /**< A 64-bit unsigned integer. */ + mpack_type_float, /**< A 32-bit IEEE 754 floating point number. */ + mpack_type_double, /**< A 64-bit IEEE 754 floating point number. */ + mpack_type_str, /**< A string. */ + mpack_type_bin, /**< A chunk of binary data. */ + mpack_type_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_timestamp, /**< A timestamp. */ } mpack_type_t; /** @@ -183,6 +192,14 @@ typedef enum mpack_type_t { */ const char* mpack_type_to_string(mpack_type_t type); +/** + * A timestamp. + */ +typedef struct mpack_timestamp_t { + int64_t seconds; /*< The number of seconds (signed) since 1970-01-01T00:00:00Z. */ + uint32_t nanoseconds; /*< The number of additional nanoseconds, between 0 and 999,999,999. */ +} mpack_timestamp_t; + /** * 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 @@ -222,6 +239,9 @@ struct mpack_tag_t { int8_t exttype; /*< The extension type. */ uint32_t length; /*< The number of bytes. */ } ext; + + /* The value if the type is @ref mpack_type_timestamp. */ + mpack_timestamp_t timestamp; } v; /* The type of value. */ @@ -229,6 +249,29 @@ struct mpack_tag_t { }; /** @endcond */ +/** + * @name Other tag functions + * @{ + */ + +/** @cond */ +// Currently defined extension types +#define MPACK_TIMESTAMP_EXTTYPE ((int8_t)(-1)) +/** @endcond */ + +MPACK_INLINE void mpack_tag_assert_valid(mpack_tag_t* tag) { + mpack_assert(tag->type != mpack_type_ext || tag->v.ext.exttype != MPACK_TIMESTAMP_EXTTYPE, + "ext tag of exttype %i is reserved for timestamps.", MPACK_TIMESTAMP_EXTTYPE); + mpack_assert(tag->type != mpack_type_timestamp || + tag->v.timestamp.nanoseconds <= MPACK_TIMESTAMP_NANOSECONDS_MAX, + "timestamp tag is not valid! %u nanoseconds is out of bounds", + tag->v.timestamp.nanoseconds); +} + +/** + * @} + */ + /** * @name Tag Generators * @{ @@ -342,6 +385,36 @@ MPACK_INLINE mpack_tag_t mpack_tag_make_ext(int8_t exttype, uint32_t length) { ret.type = mpack_type_ext; ret.v.ext.exttype = exttype; ret.v.ext.length = length; + mpack_tag_assert_valid(&ret); + return ret; +} + +/** Generates a timestamp tag from a number of seconds and nanoseconds. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_timestamp(int64_t seconds, uint32_t nanoseconds) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_timestamp; + ret.v.timestamp.seconds = seconds; + ret.v.timestamp.nanoseconds = nanoseconds; + mpack_tag_assert_valid(&ret); + return ret; +} + +/** Generates a timestamp tag from a number of seconds (with zero nanoseconds.) */ +MPACK_INLINE mpack_tag_t mpack_tag_make_timestamp_seconds(int64_t seconds) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_timestamp; + ret.v.timestamp.seconds = seconds; + ret.v.timestamp.nanoseconds = 0; + mpack_tag_assert_valid(&ret); + return ret; +} + +/** Generates a timestamp tag from an mpack_timestamp_t. */ +MPACK_INLINE mpack_tag_t mpack_tag_make_timestamp_struct(mpack_timestamp_t timestamp) { + mpack_tag_t ret = MPACK_TAG_ZERO; + ret.type = mpack_type_timestamp; + ret.v.timestamp = timestamp; + mpack_tag_assert_valid(&ret); return ret; } @@ -483,7 +556,7 @@ MPACK_INLINE int8_t mpack_tag_ext_exttype(mpack_tag_t* tag) { */ /** - * @name other tag functions + * @name Other tag functions * @{ */ @@ -820,6 +893,9 @@ MPACK_INLINE void mpack_store_double(char* p, double value) { #define MPACK_TAG_SIZE_EXT8 3 #define MPACK_TAG_SIZE_EXT16 4 #define MPACK_TAG_SIZE_EXT32 6 +#define MPACK_TAG_SIZE_TIMESTAMP4 (MPACK_TAG_SIZE_FIXEXT4 + 4) +#define MPACK_TAG_SIZE_TIMESTAMP8 (MPACK_TAG_SIZE_FIXEXT8 + 8) +#define MPACK_TAG_SIZE_TIMESTAMP12 (MPACK_TAG_SIZE_EXT8 + 12) /** @endcond */ diff --git a/src/mpack/mpack-expect.c b/src/mpack/mpack-expect.c index 174f079..3c53f7e 100644 --- a/src/mpack/mpack-expect.c +++ b/src/mpack/mpack-expect.c @@ -300,6 +300,23 @@ void mpack_expect_false(mpack_reader_t* reader) { mpack_reader_flag_error(reader, mpack_error_type); } +mpack_timestamp_t mpack_expect_timestamp(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_timestamp) + return var.v.timestamp; + mpack_reader_flag_error(reader, mpack_error_type); + mpack_timestamp_t zero = {0, 0}; + return zero; +} + +int64_t mpack_expect_timestamp_truncate(mpack_reader_t* reader) { + mpack_tag_t var = mpack_read_tag(reader); + if (var.type == mpack_type_timestamp) + return var.v.timestamp.seconds; + mpack_reader_flag_error(reader, mpack_error_type); + return 0; +} + // Compound Types diff --git a/src/mpack/mpack-expect.h b/src/mpack/mpack-expect.h index 5fa4a51..ca2fd22 100644 --- a/src/mpack/mpack-expect.h +++ b/src/mpack/mpack-expect.h @@ -553,6 +553,15 @@ void mpack_expect_true(mpack_reader_t* reader); */ void mpack_expect_false(mpack_reader_t* reader); +/** + * Reads a timestamp. + */ +mpack_timestamp_t mpack_expect_timestamp(mpack_reader_t* reader); + +/** + * Reads a timestamp in seconds, truncating the nanoseconds (if any). + */ +int64_t mpack_expect_timestamp_truncate(mpack_reader_t* reader); /** * @} diff --git a/src/mpack/mpack-node.c b/src/mpack/mpack-node.c index c6e3fb4..25f39bb 100644 --- a/src/mpack/mpack-node.c +++ b/src/mpack/mpack-node.c @@ -211,12 +211,6 @@ MPACK_STATIC_INLINE int16_t mpack_tree_i16(mpack_tree_parser_t* parser) {return 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 void mpack_skip_exttype(mpack_tree_parser_t* parser) { - // the exttype is stored right before the data. we - // skip it and get it out of the data when needed. - mpack_tree_i8(parser); -} - MPACK_STATIC_INLINE float mpack_tree_float(mpack_tree_parser_t* parser) { union { float f; @@ -373,6 +367,39 @@ static void mpack_tree_parse_bytes(mpack_tree_parser_t* parser, mpack_node_data_ parser->tree->size += length; } +static void mpack_tree_parse_validate_timestamp(mpack_tree_parser_t* parser, mpack_node_data_t* node) { + uint32_t nanoseconds; + switch (node->len) { + case 4: + return; + case 8: + nanoseconds = mpack_load_u32(parser->tree->data + node->value.offset) >> 2; + break; + case 12: + nanoseconds = mpack_load_u32(parser->tree->data + node->value.offset); + break; + default: + mpack_tree_flag_error(parser->tree, mpack_error_invalid); + return; + } + + if (nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) { + mpack_tree_flag_error(parser->tree, mpack_error_invalid); + } +} + +static void mpack_tree_parse_ext(mpack_tree_parser_t* parser, mpack_node_data_t* node) { + int8_t exttype = mpack_tree_i8(parser); + mpack_tree_parse_bytes(parser, node); + + if (exttype == MPACK_TIMESTAMP_EXTTYPE) { + node->type = mpack_type_timestamp; + mpack_tree_parse_validate_timestamp(parser, node); + } else { + node->type = mpack_type_ext; + } +} + static void mpack_tree_parse_node(mpack_tree_parser_t* parser, mpack_node_data_t* node) { mpack_assert(node != NULL, "null node?"); @@ -523,26 +550,20 @@ static void mpack_tree_parse_node(mpack_tree_parser_t* parser, mpack_node_data_t // ext8 case 0xc7: - node->type = mpack_type_ext; node->len = mpack_tree_u8(parser); - mpack_skip_exttype(parser); - mpack_tree_parse_bytes(parser, node); + mpack_tree_parse_ext(parser, node); return; // ext16 case 0xc8: - node->type = mpack_type_ext; node->len = mpack_tree_u16(parser); - mpack_skip_exttype(parser); - mpack_tree_parse_bytes(parser, node); + mpack_tree_parse_ext(parser, node); return; // ext32 case 0xc9: - node->type = mpack_type_ext; node->len = mpack_tree_u32(parser); - mpack_skip_exttype(parser); - mpack_tree_parse_bytes(parser, node); + mpack_tree_parse_ext(parser, node); return; // float @@ -607,42 +628,32 @@ static void mpack_tree_parse_node(mpack_tree_parser_t* parser, mpack_node_data_t // fixext1 case 0xd4: - node->type = mpack_type_ext; node->len = 1; - mpack_skip_exttype(parser); - mpack_tree_parse_bytes(parser, node); + mpack_tree_parse_ext(parser, node); return; // fixext2 case 0xd5: - node->type = mpack_type_ext; node->len = 2; - mpack_skip_exttype(parser); - mpack_tree_parse_bytes(parser, node); + mpack_tree_parse_ext(parser, node); return; // fixext4 case 0xd6: - node->type = mpack_type_ext; node->len = 4; - mpack_skip_exttype(parser); - mpack_tree_parse_bytes(parser, node); + mpack_tree_parse_ext(parser, node); return; // fixext8 case 0xd7: - node->type = mpack_type_ext; node->len = 8; - mpack_skip_exttype(parser); - mpack_tree_parse_bytes(parser, node); + mpack_tree_parse_ext(parser, node); return; // fixext16 case 0xd8: - node->type = mpack_type_ext; node->len = 16; - mpack_skip_exttype(parser); - mpack_tree_parse_bytes(parser, node); + mpack_tree_parse_ext(parser, node); return; // str8 @@ -1141,6 +1152,10 @@ mpack_tag_t mpack_node_tag(mpack_node_t node) { tag.v.ext.exttype = mpack_node_exttype_unchecked(node); break; + case mpack_type_timestamp: + tag.v.timestamp = mpack_node_timestamp(node); + break; + case mpack_type_array: tag.v.n = node.data->len; break; case mpack_type_map: tag.v.n = node.data->len; break; @@ -1226,6 +1241,58 @@ void mpack_node_print_file(mpack_node_t node, FILE* file) { #endif +/* + * Node Value Functions + */ + +mpack_timestamp_t mpack_node_timestamp(mpack_node_t node) { + mpack_timestamp_t timestamp = {0, 0}; + + if (mpack_node_error(node) != mpack_ok) + return timestamp; + + if (node.data->type != mpack_type_timestamp) { + mpack_node_flag_error(node, mpack_error_type); + return timestamp; + } + + const char* p = mpack_node_data_unchecked(node); + + switch (node.data->len) { + case 4: + timestamp.nanoseconds = 0; + timestamp.seconds = mpack_load_u32(p); + break; + + case 8: { + uint64_t value = mpack_load_u64(p); + timestamp.nanoseconds = (uint32_t)(value >> 34); + timestamp.seconds = value & ((UINT64_C(1) << 34) - 1); + break; + } + + case 12: + timestamp.nanoseconds = mpack_load_u32(p); + timestamp.seconds = mpack_load_i64(p + 4); + break; + + default: + mpack_assert(false, "timestamp with a wrong length should have flagged an error!"); + break; + } + + return timestamp; +} + +int64_t mpack_node_timestamp_seconds(mpack_node_t node) { + return mpack_node_timestamp(node).seconds; +} + +uint32_t mpack_node_timestamp_nanoseconds(mpack_node_t node) { + return mpack_node_timestamp(node).nanoseconds; +} + + /* * Node Data Functions diff --git a/src/mpack/mpack-node.h b/src/mpack/mpack-node.h index ce0081d..2eb772d 100644 --- a/src/mpack/mpack-node.h +++ b/src/mpack/mpack-node.h @@ -148,7 +148,7 @@ struct mpack_node_data_t { 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. */ - size_t offset; /* The byte offset for str, bin and ext */ + size_t offset; /* The byte offset for str, bin, ext and timestamp */ mpack_node_data_t* children; /* The children for map or array */ } value; }; @@ -820,6 +820,28 @@ MPACK_INLINE double mpack_node_double_strict(mpack_node_t node) { return 0.0; } +/** + * Returns a timestamp. + * + * @throws mpack_error_type if the underlying value is not a timestamp. + */ +mpack_timestamp_t mpack_node_timestamp(mpack_node_t node); + +/** + * Returns a timestamp's (signed) seconds since 1970-01-01T00:00:00Z. + * + * @throws mpack_error_type if the underlying value is not a timestamp. + */ +int64_t mpack_node_timestamp_seconds(mpack_node_t node); + +/** + * Returns a timestamp's additional nanoseconds. + * + * @return A nanosecond count between 0 and 999,999,999 inclusive. + * @throws mpack_error_type if the underlying value is not a timestamp. + */ +uint32_t mpack_node_timestamp_nanoseconds(mpack_node_t node); + /** * @} */ @@ -834,7 +856,8 @@ MPACK_INLINE const char* mpack_node_data_unchecked(mpack_node_t node) { mpack_type_t type = node.data->type; MPACK_UNUSED(type); - mpack_assert(type == mpack_type_str || type == mpack_type_bin || type == mpack_type_ext, + mpack_assert(type == mpack_type_str || type == mpack_type_bin || + type == mpack_type_ext || type == mpack_type_timestamp, "node of type %i (%s) is not a data type!", type, mpack_type_to_string(type)); return node.tree->data + node.data->value.offset; diff --git a/src/mpack/mpack-reader.c b/src/mpack/mpack-reader.c index 4dc5173..1d96f16 100644 --- a/src/mpack/mpack-reader.c +++ b/src/mpack/mpack-reader.c @@ -575,6 +575,48 @@ const char* mpack_read_utf8_inplace(mpack_reader_t* reader, size_t count) { return str; } +static void mpack_parse_timestamp(mpack_reader_t* reader, mpack_tag_t* tag, uint32_t length, const char* p) { + mpack_timestamp_t timestamp; + + switch (length) { + case 4: + timestamp.nanoseconds = 0; + timestamp.seconds = mpack_load_u32(p); + break; + case 8: + timestamp.nanoseconds = mpack_load_u32(p) >> 2; + timestamp.seconds = mpack_load_u64(p) & ((UINT64_C(1) << 34) - 1); + break; + case 12: + timestamp.nanoseconds = mpack_load_u32(p); + timestamp.seconds = mpack_load_i64(p + 4); + break; + default: + mpack_reader_flag_error(reader, mpack_error_invalid); + return; + } + + if (timestamp.nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) { + mpack_reader_flag_error(reader, mpack_error_invalid); + return; + } + + *tag = mpack_tag_make_timestamp_struct(timestamp); +} + +static size_t mpack_parse_ext(mpack_reader_t* reader, mpack_tag_t* tag, size_t tagsize, uint32_t length, const char* p) { + int8_t exttype = mpack_load_i8(p++); + + if (exttype == MPACK_TIMESTAMP_EXTTYPE) { + mpack_parse_timestamp(reader, tag, length, p); + tagsize += length; + } else { + *tag = mpack_tag_make_ext(exttype, length); + } + + return tagsize; +} + static size_t mpack_parse_tag(mpack_reader_t* reader, mpack_tag_t* tag) { mpack_assert(reader->error == mpack_ok, "reader cannot be in an error state!"); @@ -713,22 +755,19 @@ static size_t mpack_parse_tag(mpack_reader_t* reader, mpack_tag_t* tag) { case 0xc7: if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT8)) return 0; - *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 2), mpack_load_u8(reader->data + 1)); - return MPACK_TAG_SIZE_EXT8; + return mpack_parse_ext(reader, tag, MPACK_TAG_SIZE_EXT8, mpack_load_u8(reader->data + 1), reader->data + 2); // ext16 case 0xc8: if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT16)) return 0; - *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 3), mpack_load_u16(reader->data + 1)); - return MPACK_TAG_SIZE_EXT16; + return mpack_parse_ext(reader, tag, MPACK_TAG_SIZE_EXT16, mpack_load_u16(reader->data + 1), reader->data + 3); // ext32 case 0xc9: if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT32)) return 0; - *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 5), mpack_load_u32(reader->data + 1)); - return MPACK_TAG_SIZE_EXT32; + return mpack_parse_ext(reader, tag, MPACK_TAG_SIZE_EXT32, mpack_load_u32(reader->data + 1), reader->data + 5); // float case 0xca: @@ -804,36 +843,31 @@ static size_t mpack_parse_tag(mpack_reader_t* reader, mpack_tag_t* tag) { case 0xd4: if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT1)) return 0; - *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 1); - return MPACK_TAG_SIZE_FIXEXT1; + return mpack_parse_ext(reader, tag, MPACK_TAG_SIZE_FIXEXT1, 1, reader->data + 1); // fixext2 case 0xd5: if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT2)) return 0; - *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 2); - return MPACK_TAG_SIZE_FIXEXT2; + return mpack_parse_ext(reader, tag, MPACK_TAG_SIZE_FIXEXT2, 2, reader->data + 1); // fixext4 case 0xd6: if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT4)) return 0; - *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 4); - return 2; + return mpack_parse_ext(reader, tag, MPACK_TAG_SIZE_FIXEXT4, 4, reader->data + 1); // fixext8 case 0xd7: if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT8)) return 0; - *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 8); - return MPACK_TAG_SIZE_FIXEXT8; + return mpack_parse_ext(reader, tag, MPACK_TAG_SIZE_FIXEXT8, 8, reader->data + 1); // fixext16 case 0xd8: if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT16)) return 0; - *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 16); - return MPACK_TAG_SIZE_FIXEXT16; + return mpack_parse_ext(reader, tag, MPACK_TAG_SIZE_FIXEXT16, 16, reader->data + 1); // str8 case 0xd9: diff --git a/src/mpack/mpack-writer.c b/src/mpack/mpack-writer.c index a605c6c..1cba69d 100644 --- a/src/mpack/mpack-writer.c +++ b/src/mpack/mpack-writer.c @@ -473,6 +473,10 @@ void mpack_write_tag(mpack_writer_t* writer, mpack_tag_t value) { case mpack_type_array: mpack_start_array(writer, value.v.n); break; case mpack_type_map: mpack_start_map(writer, value.v.n); break; + case mpack_type_timestamp: + mpack_write_timestamp(writer, value.v.timestamp.seconds, value.v.timestamp.nanoseconds); + break; + default: mpack_assert(0, "unrecognized type %i", (int)value.type); break; @@ -701,6 +705,25 @@ MPACK_STATIC_INLINE void mpack_encode_ext32(char* p, int8_t exttype, uint32_t co mpack_store_i8(p + 5, exttype); } +MPACK_STATIC_INLINE void mpack_encode_timestamp_4(char* p, uint32_t seconds) { + mpack_encode_fixext4(p, MPACK_TIMESTAMP_EXTTYPE); + mpack_store_u32(p + MPACK_TAG_SIZE_FIXEXT4, seconds); +} + +MPACK_STATIC_INLINE void mpack_encode_timestamp_8(char* p, int64_t seconds, uint32_t nanoseconds) { + mpack_assert(nanoseconds <= MPACK_TIMESTAMP_NANOSECONDS_MAX); + mpack_encode_fixext8(p, MPACK_TIMESTAMP_EXTTYPE); + uint64_t encoded = ((uint64_t)nanoseconds << 34) | (uint64_t)seconds; + mpack_store_u64(p + MPACK_TAG_SIZE_FIXEXT8, encoded); +} + +MPACK_STATIC_INLINE void mpack_encode_timestamp_12(char* p, int64_t seconds, uint32_t nanoseconds) { + mpack_assert(nanoseconds <= MPACK_TIMESTAMP_NANOSECONDS_MAX); + mpack_encode_ext8(p, MPACK_TIMESTAMP_EXTTYPE, 12); + mpack_store_u32(p + MPACK_TAG_SIZE_EXT8, nanoseconds); + mpack_store_i64(p + MPACK_TAG_SIZE_EXT8 + 4, seconds); +} + /* @@ -881,11 +904,38 @@ void mpack_write_float(mpack_writer_t* writer, float value) { mpack_writer_track_element(writer); MPACK_WRITE_ENCODED(mpack_encode_float, MPACK_TAG_SIZE_FLOAT, value); } + void mpack_write_double(mpack_writer_t* writer, double value) { mpack_writer_track_element(writer); MPACK_WRITE_ENCODED(mpack_encode_double, MPACK_TAG_SIZE_DOUBLE, value); } +void mpack_write_timestamp(mpack_writer_t* writer, int64_t seconds, uint32_t nanoseconds) { + #if MPACK_COMPATIBILITY + if (writer->version <= mpack_version_v4) { + mpack_break("Timestamps require spec version v5 or later. This writer is in v%i mode.", (int)writer->version); + mpack_writer_flag_error(writer, mpack_error_bug); + return; + } + #endif + + if (nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) { + mpack_break("timestamp nanoseconds out of bounds: %u", nanoseconds); + mpack_writer_flag_error(writer, mpack_error_bug); + return; + } + + mpack_writer_track_element(writer); + + if (seconds < 0 || seconds >= (INT64_C(1) << 34)) { + MPACK_WRITE_ENCODED(mpack_encode_timestamp_12, MPACK_TAG_SIZE_TIMESTAMP12, seconds, nanoseconds); + } else if (seconds > UINT32_MAX || nanoseconds > 0) { + MPACK_WRITE_ENCODED(mpack_encode_timestamp_8, MPACK_TAG_SIZE_TIMESTAMP8, seconds, nanoseconds); + } else { + MPACK_WRITE_ENCODED(mpack_encode_timestamp_4, MPACK_TAG_SIZE_TIMESTAMP4, (uint32_t)seconds); + } +} + void mpack_start_array(mpack_writer_t* writer, uint32_t count) { mpack_writer_track_element(writer); diff --git a/src/mpack/mpack-writer.h b/src/mpack/mpack-writer.h index d0637c9..ccf65a1 100644 --- a/src/mpack/mpack-writer.h +++ b/src/mpack/mpack-writer.h @@ -541,6 +541,30 @@ void mpack_write_nil(mpack_writer_t* writer); /** Write a pre-encoded messagepack object */ void mpack_write_object_bytes(mpack_writer_t* writer, const char* data, size_t bytes); +/** + * Writes a timestamp. + * + * @param seconds The (signed) number of seconds since 1970-01-01T00:00:00Z. + * @param nanoseconds The additional number of nanoseconds from 0 to 999,999,999 inclusive. + */ +void mpack_write_timestamp(mpack_writer_t* writer, int64_t seconds, uint32_t nanoseconds); + +/** + * Writes a timestamp with the given number of seconds (and zero nanoseconds). + * + * @param seconds The (signed) number of seconds since 1970-01-01T00:00:00Z. + */ +MPACK_INLINE void mpack_write_timestamp_seconds(mpack_writer_t* writer, int64_t seconds) { + mpack_write_timestamp(writer, seconds, 0); +} + +/** + * Writes a timestamp. + */ +MPACK_INLINE void mpack_write_timestamp_struct(mpack_writer_t* writer, mpack_timestamp_t timestamp) { + mpack_write_timestamp(writer, timestamp.seconds, timestamp.nanoseconds); +} + /** * @} */ diff --git a/test/test-expect.c b/test/test-expect.c index 0e18ff4..d95f6a4 100644 --- a/test/test-expect.c +++ b/test/test-expect.c @@ -1142,6 +1142,48 @@ static void test_expect_streaming() { } } +static bool test_timestamp_match(int64_t seconds, uint32_t nanoseconds, mpack_timestamp_t timestamp) { + TEST_TRUE(seconds == timestamp.seconds); + TEST_TRUE(nanoseconds == timestamp.nanoseconds); + return true; +} + +static void test_expect_timestamp() { + TEST_SIMPLE_READ("\xd6\xff\x00\x00\x00\x00", 0 == mpack_expect_timestamp_truncate(&reader)); + TEST_SIMPLE_READ("\xd6\xff\x00\x00\x01\x00", test_timestamp_match(256, 0, mpack_expect_timestamp(&reader))); + TEST_SIMPLE_READ("\xd6\xff\xfe\xdc\xba\x98", 4275878552u == mpack_expect_timestamp_truncate(&reader)); + TEST_SIMPLE_READ("\xd6\xff\xff\xff\xff\xff", UINT32_MAX == mpack_expect_timestamp_truncate(&reader)); + + TEST_SIMPLE_READ("\xd7\xff\x00\x00\x00\x03\x00\x00\x00\x00", + INT64_C(12884901888) == mpack_expect_timestamp_truncate(&reader)); + TEST_SIMPLE_READ("\xd7\xff\x00\x00\x00\x00\x00\x00\x00\x00", + test_timestamp_match(0, 0, mpack_expect_timestamp(&reader))); + TEST_SIMPLE_READ("\xd7\xff\xee\x6b\x27\xfc\x00\x00\x00\x00", + test_timestamp_match(0, MPACK_TIMESTAMP_NANOSECONDS_MAX, mpack_expect_timestamp(&reader))); + TEST_SIMPLE_READ("\xd7\xff\xee\x6b\x27\xff\xff\xff\xff\xff", + test_timestamp_match(INT64_C(17179869183), MPACK_TIMESTAMP_NANOSECONDS_MAX, mpack_expect_timestamp(&reader))); + + TEST_SIMPLE_READ("\xc7\x0c\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", + 1 == mpack_expect_timestamp_truncate(&reader)); + TEST_SIMPLE_READ("\xc7\x0c\xff\x3b\x9a\xc9\xff\x00\x00\x00\x00\x00\x00\x00\x00", + test_timestamp_match(0, MPACK_TIMESTAMP_NANOSECONDS_MAX, mpack_expect_timestamp(&reader))); + TEST_SIMPLE_READ("\xc7\x0c\xff\x00\x00\x00\x01\xff\xff\xff\xff\xff\xff\xff\xff", + test_timestamp_match(-1, 1, mpack_expect_timestamp(&reader))); + TEST_SIMPLE_READ("\xc7\x0c\xff\x3b\x9a\xc9\xff\x7f\xff\xff\xff\xff\xff\xff\xff", + test_timestamp_match(INT64_MAX, MPACK_TIMESTAMP_NANOSECONDS_MAX, mpack_expect_timestamp(&reader))); + TEST_SIMPLE_READ("\xc7\x0c\xff\x3b\x9a\xc9\xff\x80\x00\x00\x00\x00\x00\x00\x00", + test_timestamp_match(INT64_MIN, MPACK_TIMESTAMP_NANOSECONDS_MAX, mpack_expect_timestamp(&reader))); + + TEST_SIMPLE_READ_ERROR("\xd7\xff\xff\xff\xff\xff\x00\x00\x00\x00", + (mpack_expect_timestamp(&reader), true), mpack_error_invalid); + TEST_SIMPLE_READ_ERROR("\xd7\xff\xee\x6b\x28\x00\xff\xff\xff\xff", + (mpack_expect_timestamp(&reader), true), mpack_error_invalid); + TEST_SIMPLE_READ_ERROR("\xc7\x0c\xff\x3b\x9a\xca\x00\x00\x00\x00\x00\x00\x00\x00\x00", + (mpack_expect_timestamp(&reader), true), mpack_error_invalid); + TEST_SIMPLE_READ_ERROR("\xc7\x0c\xff\x40\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff", + (mpack_expect_timestamp(&reader), true), mpack_error_invalid); +} + void test_expect() { test_expect_example_read(); @@ -1181,6 +1223,7 @@ void test_expect() { test_expect_bad_type(); test_expect_pre_error(); test_expect_streaming(); + test_expect_timestamp(); } #endif diff --git a/test/test-file.debug b/test/test-file.debug index 5adbd1c..bdc122b 100644 --- a/test/test-file.debug +++ b/test/test-file.debug @@ -21,5 +21,7 @@ "\"\n\\", "The quick brown fox jumps over the lazy dog.", , - + , + , + ] diff --git a/test/test-file.json b/test/test-file.json deleted file mode 100644 index 3b35217..0000000 --- a/test/test-file.json +++ /dev/null @@ -1,27 +0,0 @@ -[ - null, - false, - true, - [ - 0, - 1, - -1, - -32768, - 65535, - -2147483648, - 4294967295, - //-9223372036854775808, - //18446744073709551615, - 0.0, - ], - { - "a": 1, - "b": 2, - "c": 3, - "d": 4, - }, - "\"\n\\", - "The quick brown fox jumps over the lazy dog.", - "base64:VGhlIGZpdmUgYm94aW5nIHdpemFyZHMganVtcCBxdWlja2x5Lg==", - "ext:1:base64:U3BoaW54IG9mIGJsYWNrIHF1YXJ0eiwganVkZ2UgbXkgdm93Lg==", -] diff --git a/test/test-file.mp b/test/test-file.mp index c8e70367d6988d4725ca0fa06b3d278d1cc8d0d1..258c33a61cb27a00fd517f8113a50ec3e61442ad 100644 GIT binary patch delta 31 jcmZ3_c!ZI0_C&^YqSyZayR&P?ai0IyvrhhRV1NJs@MR5G delta 9 QcmX@YxSo-5=0wJI01%J^Pyhe` diff --git a/test/test-node.c b/test/test-node.c index 8d88490..d9647c5 100644 --- a/test/test-node.c +++ b/test/test-node.c @@ -769,6 +769,75 @@ static void test_node_read_enum() { TEST_SIMPLE_TREE_READ_ERROR("\x01", (mpack_node_nil(node), COUNT == (fruit_t)mpack_node_enum(node, fruits, COUNT)), mpack_error_type); } +static void test_node_read_timestamp() { + mpack_node_data_t pool[1]; + + TEST_SIMPLE_TREE_READ("\xd6\xff\x00\x00\x00\x00", + 0 == mpack_node_timestamp_seconds(node) && + 0 == mpack_node_timestamp_nanoseconds(node) && + mpack_tag_equal(mpack_tag_make_timestamp(0, 0), mpack_node_tag(node))); + TEST_SIMPLE_TREE_READ("\xd6\xff\x00\x00\x01\x00", + 256 == mpack_node_timestamp_seconds(node) && + 0 == mpack_node_timestamp_nanoseconds(node) && + mpack_tag_equal(mpack_tag_make_timestamp_seconds(256), mpack_node_tag(node))); + TEST_SIMPLE_TREE_READ("\xd6\xff\xfe\xdc\xba\x98", + 4275878552u == mpack_node_timestamp_seconds(node) && + 0 == mpack_node_timestamp_nanoseconds(node) && + mpack_tag_equal(mpack_tag_make_timestamp(4275878552u, 0), mpack_node_tag(node))); + TEST_SIMPLE_TREE_READ("\xd6\xff\xff\xff\xff\xff", + UINT32_MAX == mpack_node_timestamp_seconds(node) && + 0 == mpack_node_timestamp_nanoseconds(node) && + mpack_tag_equal(mpack_tag_make_timestamp_seconds(UINT32_MAX), mpack_node_tag(node))); + + TEST_SIMPLE_TREE_READ("\xd7\xff\x00\x00\x00\x03\x00\x00\x00\x00", + INT64_C(12884901888) == mpack_node_timestamp_seconds(node) && + 0 == mpack_node_timestamp_nanoseconds(node) && + mpack_tag_equal(mpack_tag_make_timestamp_seconds(INT64_C(12884901888)), mpack_node_tag(node))); + TEST_SIMPLE_TREE_READ("\xd7\xff\x00\x00\x00\x00\x00\x00\x00\x00", + 0 == mpack_node_timestamp_seconds(node) && + 0 == mpack_node_timestamp_nanoseconds(node) && + mpack_tag_equal(mpack_tag_make_timestamp_seconds(0), mpack_node_tag(node))); + TEST_SIMPLE_TREE_READ("\xd7\xff\xee\x6b\x27\xfc\x00\x00\x00\x00", + 0 == mpack_node_timestamp_seconds(node) && + MPACK_TIMESTAMP_NANOSECONDS_MAX == mpack_node_timestamp_nanoseconds(node) && + mpack_tag_equal(mpack_tag_make_timestamp(0, MPACK_TIMESTAMP_NANOSECONDS_MAX), mpack_node_tag(node))); + mpack_timestamp_t timestamp_max = {INT64_C(17179869183), MPACK_TIMESTAMP_NANOSECONDS_MAX}; + TEST_SIMPLE_TREE_READ("\xd7\xff\xee\x6b\x27\xff\xff\xff\xff\xff", + INT64_C(17179869183) == mpack_node_timestamp_seconds(node) && + MPACK_TIMESTAMP_NANOSECONDS_MAX == mpack_node_timestamp_nanoseconds(node) && + mpack_tag_equal(mpack_tag_make_timestamp_struct(timestamp_max), mpack_node_tag(node))); + + TEST_SIMPLE_TREE_READ("\xc7\x0c\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", + 1 == mpack_node_timestamp_seconds(node) && + 0 == mpack_node_timestamp_nanoseconds(node) && + mpack_tag_equal(mpack_tag_make_timestamp_seconds(1), mpack_node_tag(node))); + TEST_SIMPLE_TREE_READ("\xc7\x0c\xff\x3b\x9a\xc9\xff\x00\x00\x00\x00\x00\x00\x00\x00", + 0 == mpack_node_timestamp_seconds(node) && + MPACK_TIMESTAMP_NANOSECONDS_MAX == mpack_node_timestamp_nanoseconds(node) && + mpack_tag_equal(mpack_tag_make_timestamp(0, MPACK_TIMESTAMP_NANOSECONDS_MAX), mpack_node_tag(node))); + TEST_SIMPLE_TREE_READ("\xc7\x0c\xff\x00\x00\x00\x01\xff\xff\xff\xff\xff\xff\xff\xff", + -1 == mpack_node_timestamp_seconds(node) && + 1 == mpack_node_timestamp_nanoseconds(node) && + mpack_tag_equal(mpack_tag_make_timestamp(-1, 1), mpack_node_tag(node))); + TEST_SIMPLE_TREE_READ("\xc7\x0c\xff\x3b\x9a\xc9\xff\x7f\xff\xff\xff\xff\xff\xff\xff", + INT64_MAX == mpack_node_timestamp_seconds(node) && + MPACK_TIMESTAMP_NANOSECONDS_MAX == mpack_node_timestamp_nanoseconds(node) && + mpack_tag_equal(mpack_tag_make_timestamp(INT64_MAX, MPACK_TIMESTAMP_NANOSECONDS_MAX), mpack_node_tag(node))); + TEST_SIMPLE_TREE_READ("\xc7\x0c\xff\x3b\x9a\xc9\xff\x80\x00\x00\x00\x00\x00\x00\x00", + INT64_MIN == mpack_node_timestamp_seconds(node) && + MPACK_TIMESTAMP_NANOSECONDS_MAX == mpack_node_timestamp_nanoseconds(node) && + mpack_tag_equal(mpack_tag_make_timestamp(INT64_MIN, MPACK_TIMESTAMP_NANOSECONDS_MAX), mpack_node_tag(node))); + + TEST_SIMPLE_TREE_READ_ERROR("\xd7\xff\xff\xff\xff\xff\x00\x00\x00\x00", + (MPACK_UNUSED(node), true), mpack_error_invalid); + TEST_SIMPLE_TREE_READ_ERROR("\xd7\xff\xee\x6b\x28\x00\xff\xff\xff\xff", + (MPACK_UNUSED(node), true), mpack_error_invalid); + TEST_SIMPLE_TREE_READ_ERROR("\xc7\x0c\xff\x3b\x9a\xca\x00\x00\x00\x00\x00\x00\x00\x00\x00", + (MPACK_UNUSED(node), true), mpack_error_invalid); + TEST_SIMPLE_TREE_READ_ERROR("\xc7\x0c\xff\x40\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff", + (MPACK_UNUSED(node), true), mpack_error_invalid); +} + static void test_node_read_array() { static const char test[] = "\x93\x90\x91\xc3\x92\xc3\xc3"; mpack_tree_t tree; @@ -1150,6 +1219,7 @@ void test_node(void) { test_node_read_pre_error(); test_node_read_strings(); test_node_read_enum(); + test_node_read_timestamp(); // compound types test_node_read_array(); diff --git a/test/test-write.c b/test/test-write.c index 73ea0c2..2076886 100644 --- a/test/test-write.c +++ b/test/test-write.c @@ -357,6 +357,36 @@ static void test_write_simple_misc() { #endif } +static void test_write_timestamp() { + char buf[4096]; + + TEST_SIMPLE_WRITE("\xd6\xff\x00\x00\x00\x00", mpack_write_timestamp_seconds(&writer, 0)); + TEST_SIMPLE_WRITE("\xd6\xff\x00\x00\x01\x00", mpack_write_timestamp(&writer, 256, 0)); + TEST_SIMPLE_WRITE("\xd6\xff\xfe\xdc\xba\x98", mpack_write_timestamp_seconds(&writer, 4275878552u)); + TEST_SIMPLE_WRITE("\xd6\xff\xff\xff\xff\xff", mpack_write_timestamp(&writer, UINT32_MAX, 0)); + + TEST_SIMPLE_WRITE("\xd7\xff\x00\x00\x00\x03\x00\x00\x00\x00", + mpack_write_timestamp_seconds(&writer, INT64_C(12884901888))); + TEST_SIMPLE_WRITE("\xd7\xff\xee\x6b\x27\xfc\x00\x00\x00\x00", + mpack_write_timestamp(&writer, 0, MPACK_TIMESTAMP_NANOSECONDS_MAX)); + TEST_SIMPLE_WRITE("\xd7\xff\xee\x6b\x27\xff\xff\xff\xff\xff", + mpack_write_timestamp(&writer, INT64_C(17179869183), MPACK_TIMESTAMP_NANOSECONDS_MAX)); + + TEST_SIMPLE_WRITE("\xc7\x0c\xff\x00\x00\x00\x01\xff\xff\xff\xff\xff\xff\xff\xff", + mpack_write_timestamp(&writer, -1, 1)); + mpack_timestamp_t timestamp = {INT64_MAX, MPACK_TIMESTAMP_NANOSECONDS_MAX}; + TEST_SIMPLE_WRITE("\xc7\x0c\xff\x3b\x9a\xc9\xff\x7f\xff\xff\xff\xff\xff\xff\xff", + mpack_write_timestamp_struct(&writer, timestamp)); + TEST_SIMPLE_WRITE("\xc7\x0c\xff\x3b\x9a\xc9\xff\x80\x00\x00\x00\x00\x00\x00\x00", + mpack_write_timestamp(&writer, INT64_MIN, MPACK_TIMESTAMP_NANOSECONDS_MAX)); + + mpack_writer_t writer; + mpack_writer_init(&writer, buf, sizeof(buf)); + TEST_BREAK((mpack_write_timestamp(&writer, 0, 1000000000), true)); + TEST_BREAK((mpack_write_timestamp(&writer, 0, UINT32_MAX), true)); + TEST_WRITER_DESTROY_ERROR(&writer, mpack_error_bug); +} + #ifdef MPACK_MALLOC static void test_write_tag_tracking() { char* buf; @@ -1199,6 +1229,7 @@ void test_writes() { #endif test_write_simple_misc(); test_write_utf8(); + test_write_timestamp(); #if MPACK_COMPATIBILITY test_write_compatibility();